From f9ba4bf776e246f43594c675a70359381510f115 Mon Sep 17 00:00:00 2001 From: ybai08 <271969568+ybai08@users.noreply.github.com> Date: Sat, 17 Oct 2026 13:35:48 -0500 Subject: [PATCH] Write two hex digits for standard font character codes below 0x10 --- CHANGELOG.md | 1 + lib/font/afm.js | 2 +- tests/unit/afm.spec.js | 12 ++++++++++++ tests/unit/text.spec.js | 9 +++++++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad6b9729..195738fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Unreleased +- Fix text in a standard font garbling or dropping the characters after a tab, a carriage return or any other character code below `0x10`, which was written into the hex string of the text as a single digit, so the following digits paired up wrongly: a `\r` before a line break showed as `Ð`, and `Tab\there` was read back as `Tab–†W&P`. Fixes #1425, #606 - Fix `doc.table()` parsing a font passed as binary data (e.g. registered with `registerFont(name, uint8Array)`) again for every cell, when it restores the font after measuring and drawing the cell, which made tables with such fonts slower. A font loaded from the same `Uint8Array` is now reused without parsing it again - Add `lineBreak: 'whitespace'` to `doc.text()`, which breaks a line only at whitespace, a soft hyphen or a zero-width space. The Unicode line breaking rules the wrapper follows also allow a break after a hyphen or a slash, so `design-system` or `https://github.com/foliojs/pdfkit` could be cut at a line end, and a text extractor that joins the lines, such as a résumé parser, reads two words - Add an `objectStreams` option, which packs the objects that are not streams into compressed object streams and writes a cross-reference stream instead of the xref table, making documents with many such objects, e.g. tagged ones, much smaller. It requires a `pdfVersion` of `1.5` or higher, and cannot be used with encryption or PDF/A-1. Fixes #1810 diff --git a/lib/font/afm.js b/lib/font/afm.js index f3a9f482..cdeb66fa 100644 --- a/lib/font/afm.js +++ b/lib/font/afm.js @@ -148,7 +148,7 @@ class AFMFont { for (let i = 0, len = text.length; i < len; i++) { let char = text.charCodeAt(i); char = WIN_ANSI_MAP[char] || char; - res.push(char.toString(16)); + res.push(char.toString(16).padStart(2, '0')); } return res; diff --git a/tests/unit/afm.spec.js b/tests/unit/afm.spec.js index b372f5eb..ba4eb1c1 100644 --- a/tests/unit/afm.spec.js +++ b/tests/unit/afm.spec.js @@ -230,6 +230,18 @@ describe('AFMFont', () => { expect(font.getKernPair('A', '.notdef')).toBe(0); }); + test('encodes character codes below 0x10 as two hex digits', () => { + const font = new AFMFont(Helvetica); + + expect(font.encodeText('a\tb\r\x01')).toEqual([ + '61', + '09', + '62', + '0d', + '01', + ]); + }); + test('does not mutate generated data while decoding it', () => { const originalData = JSON.stringify(Helvetica); const font = new AFMFont(Helvetica); diff --git a/tests/unit/text.spec.js b/tests/unit/text.spec.js index acdbde3a..b325e0ce 100644 --- a/tests/unit/text.spec.js +++ b/tests/unit/text.spec.js @@ -21,6 +21,15 @@ describe('Text', () => { expect(docData).toContainText({ text: 'simple text' }); }); + test('with a tab or carriage return keeps the following characters', () => { + const docData = logData(document); + + document.text('a\tb\rc', { lineBreak: false }); + document.end(); + + expect(docData).toContainText({ text: 'a\tb\rc' }); + }); + test('with destination', () => { // just check that there is no exception document.text('simple text', { destination: 'anchor' });