Skip to content

PDF Exports Move To PDFKit

Ed Mozley edited this page Nov 7, 2026 · 1 revision

PDF exports moved from jsPDF to PDFKit - why, and what was decided

Issue: #254 (Polish letters broken in the Knowledge PDF) Β· Modules: every module that makes a PDF Β· From: the release after 4.10.0 (changelog #2745)

This is the decision record. If anyone asks "why does FreeITSM use PDFKit and not jsPDF?" or "why not just add a font?", the answer is here.


In one paragraph

FreeITSM made every PDF with a library called jsPDF, using its built-in fonts - which contain only Western European letters. So Polish came out garbled, and text measured with the wrong widths ran past the margins. A better font would have fixed Polish, but not Hindi, Tamil, Bengali and the other languages whose letters join and change shape - FreeITSM ships nine Indian languages - because jsPDF has no shaping engine, the part that does that joining. PDFKit has one. So every PDF export now goes through one shared writer built on PDFKit with bundled Noto fonts, and every language FreeITSM ships prints correctly - including on servers with no internet.


The problem

What the reporter saw

A Knowledge article containing ZaΕΌΓ³Ε‚Δ‡ gΔ™Ε›lΔ… jaΕΊΕ„ exported to PDF with the Polish letters missing or wrong, the text running past the right margin, and FreeITSM's logo instead of the organisation's.

What was actually wrong

  1. The fonts. jsPDF's built-in fonts (Helvetica, Times, Courier - the 14 "standard" PDF fonts) only contain the WinAnsi character set: English and Western European letters. Δ… Δ™ Ε‚ Ε› ΕΌ are not in them. This is true of every jsPDF version - FreeITSM was already on the latest (4.2.1), so upgrading could not help.
  2. The widths. Text is wrapped by measuring it. Letters a font does not have are measured wrongly, so lines were wrapped at the wrong place and ran off the page.
  3. The logo. A separate bug: the Knowledge export hard-coded ../assets/images/CompanyLogo.png - FreeITSM's own file.
  4. Not just Knowledge. Eleven places in FreeITSM make PDFs, all with jsPDF: Knowledge, Report Packs, form submissions, Morning Checks, the data tables (Assets), asset labels, Change Management, Network Mapper, Process Mapper and the organisation chart. Report Packs went further and deliberately replaced every non-Western character with ?.

Why "just add a font" was not enough

A font is two things: a box of letter shapes, and an instruction booklet of rules - "when these two letters meet, use this joined shape", "draw this mark in front of the letter before it".

Languages What they need jsPDF + a full font
Polish, Czech, Turkish, Russian, Ukrainian, Greek, Chinese, Japanese Shapes only - each typed letter is one shape βœ… Fixed
Hindi, Marathi, Bengali, Tamil, Telugu, Gujarati, Kannada, Malayalam, Punjabi, Thai Shapes and the booklet - letters join and reorder. ΰ€• + ΰ₯ + ΰ€· must print as ΰ€•ΰ₯ΰ€·; in ΰ€•ΰ€Ώ the ΰ€Ώ is drawn before the letter it follows ❌ Right letters, wrong arrangement - like "teh wrod sepllign"

Reading the booklet is the job of a shaping engine. Every browser has one (it is why the article looks right on screen). jsPDF does not, and no font can give it one - it is a missing ability, not a missing shape. FreeITSM ships nine Indian languages, so a font alone would have fixed Polish and left a third of FreeITSM's languages broken.


What was considered

Option Every language? Cost
1 Bundle one full font into jsPDF ❌ Polish yes, Indian scripts no Small - but the Indian scripts would stay wrong forever
2 Use the browser's own "Print β†’ Save as PDF" βœ… A print dialog instead of a download; little control over layout; no good for Report Packs or labels, which need exact page sizes
3 Put a picture of each page in the PDF (html2canvas) βœ… Text cannot be searched or copied; large files
4 Make PDFs on the server with a PHP library (mPDF) βœ… A Composer dependency (FreeITSM has none), big fonts on the server, every export rewritten in PHP
5 A different browser library with a shaping engine βœ… Every export rewritten - three candidates, below

Within option 5:

Library Verdict
pdfnative Very new (about six months, one author), Indian scripts added weeks before. It lays pages out itself and cannot place things at exact positions, has no bold or italic, and refuses transparent PNGs - so it could not do Report Packs
HarfBuzz + pdf-lib HarfBuzz is the engine inside Chrome and Firefox - the best shaping there is - but the most wiring to build
PDFKit Long-established and widely used. Shaping through fontkit, exact positioning, can measure text after shaping (which Report Packs needs), built-in tables, images with transparency, bold and italic from any font

Two ideas that were raised and turned down, with the reasons, because they will come up again:

  • "Ship two or three libraries and let each install choose in System settings." Each library is used differently, so every export would be written two or three times and kept working that many times - and nobody running a helpdesk can judge "jsPDF or PDFKit?" except by finding broken text. Instead there is one writer that every export uses (guide); if PDFKit is ever replaced, only the writer changes.
  • "Fork jsPDF and add shaping to it." That would mean bolting a shaping engine onto jsPDF's internals and then maintaining a PDF library alone, copying every jsPDF security fix by hand. PDFKit is "a PDF library with a shaping engine", maintained by other people.

What was chosen: PDFKit, behind one shared writer

  • PDFKit 0.20.2, vendored as one self-contained file (assets/js/vendor/pdfkit/pdfkit.bundle.js - what that means).
  • Bundled Noto fonts - Google's Noto family, made to cover every script: Noto Sans, Serif and Sans Mono for Latin, Greek and Cyrillic, plus Noto Sans for Devanagari, Bengali, Gujarati, Gurmukhi, Kannada, Malayalam, Tamil, Telugu, Arabic, Hebrew and Thai. A PDF downloads only the fonts its text needs.
  • One writer, assets/js/pdf-writer.js, that every export goes through. It looks like jsPDF to the exports, so each export changed by a few lines, not a rewrite.

Licensing

Checked against the published packages before anything was copied in:

Part Licence
PDFKit and fontkit, linebreak, fflate, png-js, @noble/hashes and ciphers, restructure, and fontkit's helpers MIT
@swc/helpers (a tiny helper inside fontkit) Apache 2.0
Noto fonts SIL Open Font License 1.1

All permissive - no copyleft - so they can ship inside MIT-licensed FreeITSM. Each one's notice ships beside it (assets/js/vendor/pdfkit/LICENSE.txt, OFL.txt in each font folder). PDFKit's browser build leaves out Adobe's standard-font metrics, so Adobe's notice does not apply.

Air-gapped installs

Everything is in the repository and served by the FreeITSM server: PDFKit, every font. The browser fetches nothing from the internet to make a PDF. The writer's test checks this on every run - every file loaded must come from the server's own address.


What changed in each export

Export Before Now
Knowledge article Western letters only; text past the margin; FreeITSM's logo; tables as cell | cell text Every language; wrapped within the margins; the organisation's logo, or none; real tables with a header row
Report Packs Non-Western characters replaced with ?; widths from Helvetica/Times/Courier, added up a letter at a time Every language; widths measured after shaping, so lines break where they print; the "unsupported" warning only for Chinese, Japanese, Korean
Form submissions (single, bundle, separate) Western letters only Every language, table questions as tables
Morning Checks Western letters only Every language
Data tables (Assets) Western letters only Every language
Asset labels Text drawn as a picture by the browser (already every language) Unchanged - text stays a picture on purpose (uploaded label fonts, preview = print); boxes, lines, QR codes and images through the writer
Change Management, Network Mapper, Process Mapper, organisation chart A picture of the page in a jsPDF document The same picture, in a writer document

Nothing changes in how anyone exports anything.

Known limits

Chinese, Japanese, Korean No bundled font - each is 5-20 MB - so they print as empty boxes. Report Packs' designer warns. A font could be added later (or uploaded, an idea raised during the discussion)
Arabic, Hebrew Letters join correctly, but punctuation and spacing in a line that mixes right-to-left and left-to-right text can be out of order - there is no bidirectional-text pass. FreeITSM ships no right-to-left language
Copying Indian-script text out of a PDF Can give the letters in a different order. What is printed is right
Italic in Indian scripts, italic monospace The fonts have no italic; regular is used

How it was proved

Every export was run in a real (headless) Chrome against this install, and each PDF was opened and looked at - not just checked for existing:

  • The writer on its own - php tests/pdf-writer.php, 20 checks: the right font per script, a joined Hindi letter measured narrower than its parts, wrapping, every drawing call, a table over three pages with a coloured status column, page numbers, an image stored once, and nothing fetched from the internet. Its PDF shows Polish, Russian, Hindi, Tamil and Bengali correctly.
  • Knowledge - an article with Polish, Hindi and Tamil, lists, code and a table: everything correct, inside the margins, the organisation's logo.
  • Report Packs - a copy of a real pack with Polish, Hindi and Tamil added: headings, paragraphs, a list mixing bold and italic across scripts, KPI tiles, a chart, header and footer.
  • Form submission, Morning Checks, the Assets table (41 pages), asset labels and a real change - each exported and checked.
  • Organisation chart, Network Mapper, Process Mapper - their pictures could not be produced in headless Chrome (the organisation chart's PNG export fails there too, and that code was not touched); their PDF code is the same three lines as Change Management's, which was proved.

Three gaps found by looking - at the output, and at which characters each font has - all fixed with a test: a : the Bengali font does not have printed as a box (now taken from the main font); a ΰ₯€ ending a Hindi sentence printed as a box (now kept in the Devanagari font); and Noto Sans has no β—¦, Report Packs' nested list bullet (now taken from Noto Sans Mono).

Related

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally