{"id":13804104,"url":"https://github.com/vlang/pdf","last_synced_at":"2026-03-06T18:36:50.908Z","repository":{"id":40490355,"uuid":"280003897","full_name":"vlang/pdf","owner":"vlang","description":null,"archived":false,"fork":false,"pushed_at":"2023-12-19T17:45:33.000Z","size":458,"stargazers_count":83,"open_issues_count":0,"forks_count":10,"subscribers_count":31,"default_branch":"master","last_synced_at":"2025-01-16T07:26:51.030Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"V","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vlang.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-07-15T23:27:55.000Z","updated_at":"2024-12-31T05:41:05.000Z","dependencies_parsed_at":"2023-01-20T03:04:30.080Z","dependency_job_id":"8750aff7-9019-46dc-898a-9c4d5a3ac94b","html_url":"https://github.com/vlang/pdf","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vlang%2Fpdf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vlang%2Fpdf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vlang%2Fpdf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vlang%2Fpdf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vlang","download_url":"https://codeload.github.com/vlang/pdf/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242099585,"owners_count":20071565,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-04T01:00:41.847Z","updated_at":"2026-03-06T18:36:45.883Z","avatar_url":"https://github.com/vlang.png","language":"V","funding_links":[],"categories":["Applications"],"sub_categories":["Editors"],"readme":"# vPDF\n\n## Introduction\n\nFrom wikipedia:\n\n*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.*\n\nPDF is a commonly used file format, but producing a PDF file is not a simple task.\n\nThis module was created to simplify PDF file creation using the [V programming language](https://vlang.io/).\n\n**vPDF** is structured in two layers, low and high.\n\n### Low level layer\n\nAt 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.\n\n### High level layer\n\nAt the higher level, there are various functions which simplify creating a PDF, and no knowledge of the PDF format is required to use them.\n\n## Installation\n\n```\nv install pdf\n```\n\n## QuickStart\n\nLet'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**:\n\n### Complete source (example 06)\n\n```v\nimport pdf\nimport os\n\nfn main() {\n\tmut doc := pdf.Pdf{}\n\tdoc.init()\n\n\tpage_n := doc.create_page(pdf.Page_params{\n\t\tformat: 'A4'\n\t\tgen_content_obj: true\n\t\tcompress: false\n\t})\n\tmut page := \u0026doc.page_list[page_n]\n\tpage.user_unit = pdf.mm_unit\n\n\tmut fnt_params := pdf.Text_params{\n\t\tfont_size: 22.0\n\t\tfont_name: 'Helvetica'\n\t\ts_color: pdf.RGB{\n\t\t\tr: 0\n\t\t\tg: 0\n\t\t\tb: 0\n\t\t}\n\t\tf_color: pdf.RGB{\n\t\t\tr: 0\n\t\t\tg: 0\n\t\t\tb: 0\n\t\t}\n\t}\n\n\t// Declare the base (Type1 font) we want use\n\tif !doc.use_base_font(fnt_params.font_name) {\n\t\teprintln('ERROR: Font $fnt_params.font_name not available!')\n\t\treturn\n\t}\n\n\t// write the string\n\tpage.push_content(page.draw_base_text('My first string.', 10, 10, fnt_params))\n\n\t// render the PDF\n\ttxt := doc.render()!\n\n\t// write it to a file\n\tos.write_file_array('example06.pdf', txt)!\n}\n```\n\nNow we'll break down the code:\n\n### PDF creation\n\nFirst we need to create a PDF and initialize the necessary structures:\n\n```v\nmut doc := pdf.Pdf{}\ndoc.init()\n```\n\n### Page format and creation\n\nOnce 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** to automatically create the objects or we want do it manually, etc.\n\nFor this example we use the simplest configuration possible.  We want to create an `A4` page (210x297 mm), we want **vPDF** to handle object creation and tracking for us, and we do not want to compress the output:\n\n```v\npage_n := doc.create_page(pdf.Page_params{\n\tformat: 'A4'\n\tgen_content_obj: true\n\tcompress: false\n})\n```\n\nWhen the page is created, the `create_page` routine returns the index of the page in the **vPDF** page array.\n\nTo modify the page, we need to set millimeters as the working unit size for the page:\n\n```v\nmut page := \u0026doc.page_list[page_n] // get the page struct\npage.user_unit = pdf.mm_unit       // set millimeters for all operations\n```\n\n### Font selection and use\n\nTo write a simple string we need to tell the page which font we want to use, and its properties.\n\nFirst we create a `Text_params` *struct* that contains all the information **vPDF** needs to instantiate and use a font:\n\nWe want a 22pt Helvetica font, and we set the font (stroke) color and fill color:\n\n```v\nmut fnt_params := pdf.Text_params{\n\tfont_size    : 22.0\n\tfont_name    : \"Helvetica\"\n\ts_color : {r:0,g:0,b:0}\n\tf_color : {r:0,g:0,b:0}\n}\n```\n\nAt present, **vPDF** only supports Type1 fonts. The fonts available in this module are: `['Courier-Bold', 'Courier-BoldOblique', 'Courier-Oblique', 'Courier', 'Helvetica-Bold', 'Helvetica-BoldOblique', 'Helvetica-Oblique', 'Helvetica', 'Symbol', 'Times-Bold', 'Times-BoldItalic', 'Times-Italic', 'Times-Roman', 'ZapfDingbats']`\n\nThen tell **vPDF** the font we want use in the PDF file:\n\n```v\ndoc.use_base_font(fnt_params.font_name)\n```\n\n### Write a string\n\nA 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.\n\nTo 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:\n\n```v\npage.push_content(\n\tpage.draw_base_text(\"My first string.\", 10, 10, fnt_params)\n)\n```\n\n### Render the PDF\n\nAt this point our example PDF is complete, so tell **vPDF** to render the result.  The rendering function returns a `strings.Builder`:\n\n```v\ntxt := doc.render()\n```\n\n### Save the file\n\nWith the returned `strings.builder`, we can do whatever we want - save the PDF to disk, return it as a HTTP response, or whatever else is needed.\n\nIn this quickstart we will write the PDF file to disk:\n\n```v\nos.write_file_array('example06.pdf', txt.buf)\n```\n\n## Resulting PDF\n\nSince 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:\n\n```\n%PDF-1.4\n1 0 obj\n\u003c\u003c /Type /Catalog /Pages  2 0 R  \u003e\u003e\nendobj\n\n2 0 obj\n\u003c\u003c /Type /Pages  /Kids[ 3 0 R  ]  /Count 1  \u003e\u003e\nendobj\n\n3 0 obj\n\u003c\u003c /Type /Page /Parent 2 0 R /MediaBox  [ 0 0 595.274 841.888 ] /CropBox   [ 0 0 595.274 841.888 ] /Resources  \u003c\u003c  /ProcSet  [/PDF/ImageB/ImageC/ImageI/Text]  /Font  \u003c\u003c  /F0  5 0 R  \u003e\u003e  \u003e\u003e  /Contents  4 0 R  \u003e\u003e\nendobj\n\n4 0 obj\n\u003c\u003c /Length 77 \u003e\u003e\nstream\n\nBT\n/F0 22 Tf\n0 0 0 RG 0 0 0 rg\n28.3464 813.542 Td\n(My first string.) Tj\nET\n\nendstream\nendobj\n\n5 0 obj\n\u003c\u003c /Name /F0 /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /MacRomanEncoding  \u003e\u003e\nendobj\n\nxref\n0 1\n0000000000 65535 f\n1 1\n0000000009 00000 n\n2 1\n0000000061 00000 n\n3 1\n0000000124 00000 n\n4 1\n0000000351 00000 n\n5 1\n0000000479 00000 n\ntrailer\n\u003c\u003c/Size 6/Root 1 0 R\u003e\u003e\nstartxref\n589\n%%EOF\n```\n\n## Add a JPEG image\n\nNow we can add an image to the page, it can be simply done adding the following lines just before the rendering operation:\n\n```v\n// read a jpeg image from the disk\njpeg_data := os.read_bytes(\"data/v.jpg\") or { panic(err) }\njpeg_id := doc.add_jpeg_resource(jpeg_data)\n// tell the page we want use a this jpeg in the page\npage.use_jpeg(jpeg_id)\n\n// get width and height in pixel of the jpeg image\n_, w, h := pdf.get_jpeg_info(jpeg_data)\nh_scale := h / w\n\npage.push_content(\npage.draw_jpeg(jpeg_id, {x:10, y:60, w:30, h:30 * h_scale})\n)\n```\n\nFirst we need to load in memory the image, this task is achieved with: `os.read_bytes(\"data/v.jpg\")`\n\nNow we must add the jpeg to the resources of the PDF: `doc.add_jpeg_resource(jpeg_data)` that return an id that we will use to identify the jpeg as PDF resource.\n\n**Note:** *All the resources of a PDF file like images, fonts etc must be loaded and stored inside the PDF itself as PDF objects.*\n\nThe  **vPDF** module take care about the creation of the objects and their indexing.\n\nNow 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)`\n\nBefore draw the image we can collect some info on it using: `_, w, h := pdf.get_jpeg_info(jpeg_data)` in this case we need only the width and the height of the jpeg.\n\nNow we can draw our jpeg in the pdf using:\n\n```v\npage.push_content(\n\tpage.draw_jpeg(jpeg_id, {x:10, y:60, w:30, h:30 * h_scale})\n)\n```\n\nwhere we specify the `jpeg_id` returned by the `add_jpeg_resource` call and a `Box` with the position and dimension where we want the jpeg.\n\nIn 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.\n\n### Complete source (example 07)\n\n```v\nimport pdf\nimport os\n\nfn main() {\n\tmut doc := pdf.Pdf{}\n\tdoc.init()\n\n\tpage_n := doc.create_page(pdf.Page_params{\n\t\tformat: 'A4'\n\t\tgen_content_obj: true\n\t\tcompress: false\n\t})\n\tmut page := \u0026doc.page_list[page_n]\n\tpage.user_unit = pdf.mm_unit\n\n\tmut fnt_params := pdf.Text_params{\n\t\tfont_size: 22.0\n\t\tfont_name: 'Helvetica'\n\t\ts_color: pdf.RGB{\n\t\t\tr: 0\n\t\t\tg: 0\n\t\t\tb: 0\n\t\t}\n\t\tf_color: pdf.RGB{\n\t\t\tr: 0\n\t\t\tg: 0\n\t\t\tb: 0\n\t\t}\n\t}\n\n\t// Declare the base (Type1 font) we want use\n\tif !doc.use_base_font(fnt_params.font_name) {\n\t\teprintln('ERROR: Font $fnt_params.font_name not available!')\n\t\treturn\n\t}\n\n\t// write the string\n\tpage.push_content(page.draw_base_text('My first string.', 10, 10, fnt_params))\n\n\t// read a jpeg image from the disk\n\tjpeg_data := os.read_bytes('data/v.jpg') or { panic(err) }\n\tjpeg_id := doc.add_jpeg_resource(jpeg_data)\n\t// tell the page we want use a this jpeg in the page\n\tpage.use_jpeg(jpeg_id)\n\n\t// get width and height in pixel of the jpeg image\n\t_, w, h := pdf.get_jpeg_info(jpeg_data)\n\th_scale := h / w\n\n\tpage.push_content(page.draw_jpeg(jpeg_id, pdf.Box{\n\t\tx: 10\n\t\ty: 60\n\t\tw: 30\n\t\th: 30 * h_scale\n\t}))\n\n\t// render the PDF\n\ttxt := doc.render()!\n\n\t// write it to a file\n\tos.write_file_array('example07.pdf', txt)!\n}\n```\n\n## Text Box\n\nA text box is a page's box where the text is fitted.\n\nIt is an utility function that help write indented text.\n\nAs input you need :\n\n- the source text\n- a Box with the coordinates and dimensions of the container box\n- a `Text_params` structure, the possible `text_align` values are: `left, center, right, justify`\n\n```v\nsource_txt := \"text to write\"\ntext_bx   := pdf.Box{x:10, y:10, w:40, h:60}\nnt_params  := pdf.Text_params{\n\t\tfont_size    : 22.0\n\t\tfont_name    : \"Helvetica\"\n\t\ttext_align   : .left\n}\nres, left_over_txt, bottom_y  = page.text_box(source_txt, text_bx, fnt_params)\n```\n\nAs output you will obtain:\n\n- `res` is true if all the `source_txt` lay inside the `text_bx` otherwise false.\n-  `left_over_txt` is the text that was not possible to fit inside `text_bx`\n- `bottom_y` is the bottom y coordinate where the text was written by `text_box` function.\n\nYou can use the `left_over_txt` as input for other  `text_box` function like in the example 03 or example 05.\n\n### Complete source (example 03)\n\n```v\nimport pdf\nimport os\n\nfn main() {\n\tmut doc := pdf.Pdf{}\n\tdoc.init()\n\n\tpage_n := doc.create_page(pdf.Page_params{\n\t\tformat: 'A4'\n\t\tgen_content_obj: true\n\t\tcompress: true\n\t})\n\tmut page := \u0026doc.page_list[page_n]\n\tpage.user_unit = pdf.mm_unit\n\n\tmut fnt_params := pdf.Text_params{\n\t\tfont_size: 22.0\n\t\tfont_name: 'Helvetica'\n\t\trender_mode: -1\n\t\tword_spacing: -1\n\t\ts_color: pdf.RGB{\n\t\t\tr: 0\n\t\t\tg: 0\n\t\t\tb: 0\n\t\t}\n\t\tf_color: pdf.RGB{\n\t\t\tr: 0\n\t\t\tg: 0\n\t\t\tb: 0\n\t\t}\n\t}\n\n\t// Declare the base (Type1 font) we want use\n\tif !doc.use_base_font(fnt_params.font_name) {\n\t\teprintln('ERROR: Font $fnt_params.font_name not available!')\n\t\treturn\n\t}\n\n\t//----- test box text -----\n\tfnt_params.word_spacing = 0\n\tfnt_params.font_size = 12\n\n\tmut my_str := \"Quicksort (sometimes called partition-exchange sort) is an efficient sorting algorithm.\n\tDeveloped 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.\n\tQuicksort 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.\n\tQuicksort 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.\n\tMathematical 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.\n\tBest solutions can be available!\n\tSoon or later they will be available.\"\n\n\tmy_str = my_str + my_str\n\tmy_str = my_str + my_str\n\n\t//----- Text Area -----\n\ttb := pdf.Box{\n\t\tx: page.media_box.x / page.user_unit + 10\n\t\ty: 20\n\t\tw: page.media_box.w / page.user_unit - 20\n\t\th: page.media_box.h / page.user_unit - 20\n\t}\n\n\t// justify align\n\tfnt_params.text_align = .justify\n\tmut tmp_txt := my_str\n\tmut tmp_res := false\n\tmut lo_txt := ' '\n\n\t// set two columns\n\tboxes := [\n\t\tpdf.Box{\n\t\t\tx: tb.x\n\t\t\ty: tb.y\n\t\t\tw: tb.w / 2 - 10\n\t\t\th: tb.h - 20\n\t\t},\n\t\tpdf.Box{\n\t\t\tx: tb.x + tb.w / 2 + 5\n\t\t\ty: tb.y\n\t\t\tw: tb.w / 2 - 10\n\t\t\th: tb.h - 20\n\t\t},\n\t]\n\n\tfor bx in boxes {\n\t\tif lo_txt.len \u003e 0 {\n\t\t\ttmp_res, lo_txt, _ = page.text_box(tmp_txt, bx, fnt_params)\n\t\t\tif tmp_res {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\ttmp_txt = lo_txt\n\t\t\t// println(\"leftover: [${lo_txt}]\")\n\t\t}\n\t}\n\t// println(\"res: ${tmp_res} left_over: [${lo_txt}]\")\n\n\t// render the PDF\n\ttxt := doc.render()!\n\n\t// write it to a file\n\tos.write_file_array('example03.pdf', txt)!\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvlang%2Fpdf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvlang%2Fpdf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvlang%2Fpdf/lists"}