diff --git a/CHANGELOG.md b/CHANGELOG.md index d9e97eb7..5dfd7b78 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 - Reuse the glyph width and kerning tables of a standard font across documents instead of building them again for every document that uses it, which made a short document using two standard fonts more than twice as slow to create - 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 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' });