PDF Exports Move To PDFKit - edmozley/freeitsm GitHub Wiki
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.
- How the PDF code works now: PDF writer - Developer Guide
- What module, bundle and vendoring mean: JavaScript modules and vendoring explained
- The bug as reported: Issue #254 write-up
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
- 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. - 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.
- The logo. A separate bug: the Knowledge export hard-coded
../assets/images/CompanyLogo.png- FreeITSM's own file. - 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).