diff --git a/CHANGELOG.md b/CHANGELOG.md index 4384a1f1..068859ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Unreleased +- Fix links to a named destination going nowhere in readers that look the name up in sorted order, such as Chrome (PDFium) and qpdf, because name tree keys were sorted by locale with `localeCompare` instead of by their bytes: with destinations named `appendix` and `Chapter1`, `Chapter1` was written second and could not be found. The keys of the `EmbeddedFiles` and `JavaScript` name trees are sorted the same way - Escape document information in XMP metadata and omit characters XML 1.0 cannot represent, without changing the PDF Info dictionary. Fixes #1817 - 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 diff --git a/lib/name_tree.js b/lib/name_tree.js index 6daf9d8b..ef2e1599 100644 --- a/lib/name_tree.js +++ b/lib/name_tree.js @@ -6,7 +6,10 @@ import PDFTree from './tree'; class PDFNameTree extends PDFTree { _compareKeys(a, b) { - return a.localeCompare(b); + // Readers look a key up expecting the keys in ascending byte order + // (ISO 32000-1, 7.9.6). Character codes compare in that order for ASCII + // keys, while `localeCompare` puts `appendix` before `Chapter1`. + return a < b ? -1 : a > b ? 1 : 0; } _keysName() { diff --git a/tests/unit/tree.spec.js b/tests/unit/tree.spec.js index 84b94842..4ce702e3 100644 --- a/tests/unit/tree.spec.js +++ b/tests/unit/tree.spec.js @@ -73,6 +73,24 @@ describe.each([ }); }); +describe('PDFNameTree key order', () => { + test('sorts the keys by their bytes rather than by locale', () => { + const tree = new PDFNameTree(); + tree.add('appendix', new String('one')); + tree.add('Chapter1', new String('two')); + + // readers look keys up in byte order, in which `Chapter1` (0x43) comes + // before `appendix` (0x61); in locale order they could not find it + expect(PDFObject.convert(tree)).toEqual(`<< + /Limits [(Chapter1) (appendix)] + /Names [ + (Chapter1) (two) + (appendix) (one) +] +>>`); + }); +}); + describe('name trees in a document', () => { const names = ['(heading)', '(data.txt)', 'app.alert'];