vPDF
Introduction
From wikipedia:
The Portable Document Format (PDF) is a file format developed by Adobe in the 1990s to present documents, including text formatting and images, in a manner independent of application software, hardware, and operating systems. Based on the PostScript language, each PDF file encapsulates a complete description of a fixed-layout flat document, including the text, fonts, vector graphics, raster images and other information needed to display it. PDF was standardized as ISO 32000 in 2008, and no longer requires any royalties for its implementation.
PDF is a commonly used file format, but producing a PDF file is not a simple task.
This module was created to simplify PDF file creation using the
V programming language
vPDF
Low level layer
At the lower level, it is possible to create a PDF and write directly to it. This layer is intended for users that are familiar with the PDF format, and wish to create files with as little overhead as possible. You will, however, have to keep track of all the details yourself.
High level layer
At the higher level, there are various functions which simplify creating a PDF, and no knowledge of the PDF format is required to use them.
Installation
v install pdf
QuickStart
Let's start with a reasonably simple example: creating a PDF with only one page and with a simple string on it. For this example we will use the high level layer of
vPDF
Complete source (example 06)
import pdf
import os
fn main() {
mut doc := pdf.Pdf{}
doc.init()
page_n := doc.create_page(pdf.Page_params{
format: 'A4'
gen_content_obj: true
compress: false
})
mut page := &doc.page_list[page_n]
page.user_unit = pdf.mm_unit
mut fnt_params := pdf.Text_params{
font_size: 22.0
font_name: 'Helvetica'
s_color: pdf.RGB{
r: 0
g: 0
b: 0
}
f_color: pdf.RGB{
r: 0
g: 0
b: 0
}
}
// Declare the base (Type1 font) we want use
if !doc.use_base_font(fnt_params.font_name) {
eprintln('ERROR: Font $fnt_params.font_name not available!')
return
}
// write the string
page.push_content(page.draw_base_text('My first string.', 10, 10, fnt_params))
// render the PDF
txt := doc.render()!
// write it to a file
os.write_file_array('example06.pdf', txt)!
}
Now we'll break down the code:
PDF creation
First we need to create a PDF and initialize the necessary structures:
mut doc := pdf.Pdf{}
doc.init()
Page format and creation
Once we have the PDF structure, we need to create the page or pages. The page can have several parameters such as: dimensions, a flag that indicates if we want
vPDF
For this example we use the simplest configuration possible. We want to create an
A4
page_n := doc.create_page(pdf.Page_params{
format: 'A4'
gen_content_obj: true
compress: false
})
When the page is created, the
create_page
To modify the page, we need to set millimeters as the working unit size for the page:
mut page := &doc.page_list[page_n] // get the page struct
page.user_unit = pdf.mm_unit // set millimeters for all operations
Font selection and use
To write a simple string we need to tell the page which font we want to use, and its properties.
First we create a
Text_params
struct
We want a 22pt Helvetica font, and we set the font (stroke) color and fill color:
mut fnt_params := pdf.Text_params{
font_size : 22.0
font_name : "Helvetica"
s_color : {r:0,g:0,b:0}
f_color : {r:0,g:0,b:0}
}
At present,
vPDF
['Courier-Bold', 'Courier-BoldOblique', 'Courier-Oblique', 'Courier', 'Helvetica-Bold', 'Helvetica-BoldOblique', 'Helvetica-Oblique', 'Helvetica', 'Symbol', 'Times-Bold', 'Times-BoldItalic', 'Times-Italic', 'Times-Roman', 'ZapfDingbats']
Then tell
vPDF
doc.use_base_font(fnt_params.font_name)
Write a string
A PDF page is written in FIFO order, like a queue. Using the high level layer we don't need to care about creation of the objects or their indexing.
To write a string at 10 mm from the left border and 10 mm from the top border of the page we only need to do:
page.push_content(
page.draw_base_text("My first string.", 10, 10, fnt_params)
)
Render the PDF
At this point our example PDF is complete, so tell
vPDF
strings.Builder
txt := doc.render()
Save the file
With the returned
strings.builder
In this quickstart we will write the PDF file to disk:
os.write_file_array('example06.pdf', txt.buf)
Resulting PDF
Since we chose not to compress the PDF, it can be opened in a simple text editor so we can read its raw form Note that you would normally open the file with a PDF reader - this is for illustrative purposes only. The contents of the PDF file as written by this example should be:
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids[ 3 0 R ] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [ 0 0 595.274 841.888 ] /CropBox [ 0 0 595.274 841.888 ] /Resources << /ProcSet [/PDF/ImageB/ImageC/ImageI/Text] /Font << /F0 5 0 R >> >> /Contents 4 0 R >>
endobj
4 0 obj
<< /Length 77 >>
stream
BT
/F0 22 Tf
0 0 0 RG 0 0 0 rg
28.3464 813.542 Td
(My first string.) Tj
ET
endstream
endobj
5 0 obj
<< /Name /F0 /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /MacRomanEncoding >>
endobj
xref
0 1
0000000000 65535 f
1 1
0000000009 00000 n
2 1
0000000061 00000 n
3 1
0000000124 00000 n
4 1
0000000351 00000 n
5 1
0000000479 00000 n
trailer
<</Size 6/Root 1 0 R>>
startxref
589
%%EOF
Add a JPEG image
Now we can add an image to the page, it can be simply done adding the following lines just before the rendering operation:
// read a jpeg image from the disk
jpeg_data := os.read_bytes("data/v.jpg") or { panic(err) }
jpeg_id := doc.add_jpeg_resource(jpeg_data)
// tell the page we want use a this jpeg in the page
page.use_jpeg(jpeg_id)
// get width and height in pixel of the jpeg image
_, w, h := pdf.get_jpeg_info(jpeg_data)
h_scale := h / w
page.push_content(
page.draw_jpeg(jpeg_id, {x:10, y:60, w:30, h:30 * h_scale})
)
First we need to load in memory the image, this task is achieved with:
os.read_bytes("data/v.jpg")
Now we must add the jpeg to the resources of the PDF:
doc.add_jpeg_resource(jpeg_data)
Note: All the resources of a PDF file like images, fonts etc must be loaded and stored inside the PDF itself as PDF objects.
The
vPDF
Now we must use the jpeg, this usage belong to the page and we must tell to the page that we want use a specific image, we do this with:
page.use_jpeg(jpeg_id)
Before draw the image we can collect some info on it using:
_, w, h := pdf.get_jpeg_info(jpeg_data)
Now we can draw our jpeg in the pdf using:
page.push_content(
page.draw_jpeg(jpeg_id, {x:10, y:60, w:30, h:30 * h_scale})
)
where we specify the
jpeg_id
add_jpeg_resource
Box
In this case we will draw the jpeg at 10 mm from the left border and 60 mm from the top border with a width of 30mm and a height proportional to the source.
Complete source (example 07)
import pdf
import os
fn main() {
mut doc := pdf.Pdf{}
doc.init()
page_n := doc.create_page(pdf.Page_params{
format: 'A4'
gen_content_obj: true
compress: false
})
mut page := &doc.page_list[page_n]
page.user_unit = pdf.mm_unit
mut fnt_params := pdf.Text_params{
font_size: 22.0
font_name: 'Helvetica'
s_color: pdf.RGB{
r: 0
g: 0
b: 0
}
f_color: pdf.RGB{
r: 0
g: 0
b: 0
}
}
// Declare the base (Type1 font) we want use
if !doc.use_base_font(fnt_params.font_name) {
eprintln('ERROR: Font $fnt_params.font_name not available!')
return
}
// write the string
page.push_content(page.draw_base_text('My first string.', 10, 10, fnt_params))
// read a jpeg image from the disk
jpeg_data := os.read_bytes('data/v.jpg') or { panic(err) }
jpeg_id := doc.add_jpeg_resource(jpeg_data)
// tell the page we want use a this jpeg in the page
page.use_jpeg(jpeg_id)
// get width and height in pixel of the jpeg image
_, w, h := pdf.get_jpeg_info(jpeg_data)
h_scale := h / w
page.push_content(page.draw_jpeg(jpeg_id, pdf.Box{
x: 10
y: 60
w: 30
h: 30 * h_scale
}))
// render the PDF
txt := doc.render()!
// write it to a file
os.write_file_array('example07.pdf', txt)!
}
Text Box
A text box is a page's box where the text is fitted.
It is an utility function that help write indented text.
As input you need :
- the source text
- a Box with the coordinates and dimensions of the container box
- a
Text_params
structure, the possible text_align
values are: left, center, right, justify
source_txt := "text to write"
text_bx := pdf.Box{x:10, y:10, w:40, h:60}
nt_params := pdf.Text_params{
font_size : 22.0
font_name : "Helvetica"
text_align : .left
}
res, left_over_txt, bottom_y = page.text_box(source_txt, text_bx, fnt_params)
As output you will obtain:
-
res
is true if all the source_txt
lay inside the text_bx
otherwise false. -
left_over_txt
is the text that was not possible to fit inside text_bx
-
bottom_y
is the bottom y coordinate where the text was written by text_box
function.
You can use the
left_over_txt
text_box
Complete source (example 03)
import pdf
import os
fn main() {
mut doc := pdf.Pdf{}
doc.init()
page_n := doc.create_page(pdf.Page_params{
format: 'A4'
gen_content_obj: true
compress: true
})
mut page := &doc.page_list[page_n]
page.user_unit = pdf.mm_unit
mut fnt_params := pdf.Text_params{
font_size: 22.0
font_name: 'Helvetica'
render_mode: -1
word_spacing: -1
s_color: pdf.RGB{
r: 0
g: 0
b: 0
}
f_color: pdf.RGB{
r: 0
g: 0
b: 0
}
}
// Declare the base (Type1 font) we want use
if !doc.use_base_font(fnt_params.font_name) {
eprintln('ERROR: Font $fnt_params.font_name not available!')
return
}
//----- test box text -----
fnt_params.word_spacing = 0
fnt_params.font_size = 12
mut my_str := "Quicksort (sometimes called partition-exchange sort) is an efficient sorting algorithm.
Developed by British computer scientist Tony Hoare in 1959 and published in 1961, it is still a commonly used algorithm for sorting. When implemented well, it can be about two or three times faster than its main competitors, merge sort and heapsort.
Quicksort is a divide-and-conquer algorithm. It works by selecting a 'pivot' element from the array and partitioning the other elements into two sub-arrays, according to whether they are less than or greater than the pivot. The sub-arrays are then sorted recursively. This can be done in-place, requiring small additional amounts of memory to perform the sorting.
Quicksort is a comparison sort, meaning that it can sort items of any type for which a 'less-than' relation (formally, a total order) is defined. Efficient implementations of Quicksort are not a stable sort, meaning that the relative order of equal sort items is not preserved.
Mathematical analysis of quicksort shows that, on average, the algorithm takes O[n log n] comparisons to sort n items. In the worst case, it makes O[n2] comparisons, though this behavior is rare.
Best solutions can be available!
Soon or later they will be available."
my_str = my_str + my_str
my_str = my_str + my_str
//----- Text Area -----
tb := pdf.Box{
x: page.media_box.x / page.user_unit + 10
y: 20
w: page.media_box.w / page.user_unit - 20
h: page.media_box.h / page.user_unit - 20
}
// justify align
fnt_params.text_align = .justify
mut tmp_txt := my_str
mut tmp_res := false
mut lo_txt := ' '
// set two columns
boxes := [
pdf.Box{
x: tb.x
y: tb.y
w: tb.w / 2 - 10
h: tb.h - 20
},
pdf.Box{
x: tb.x + tb.w / 2 + 5
y: tb.y
w: tb.w / 2 - 10
h: tb.h - 20
},
]
for bx in boxes {
if lo_txt.len > 0 {
tmp_res, lo_txt, _ = page.text_box(tmp_txt, bx, fnt_params)
if tmp_res {
break
}
tmp_txt = lo_txt
// println("leftover: [${lo_txt}]")
}
}
// println("res: ${tmp_res} left_over: [${lo_txt}]")
// render the PDF
txt := doc.render()!
// write it to a file
os.write_file_array('example03.pdf', txt)!
}