diff --git a/lib/font/embedded.js b/lib/font/embedded.js index 5b5c91edf..33eed16cc 100644 --- a/lib/font/embedded.js +++ b/lib/font/embedded.js @@ -4,6 +4,23 @@ const toHex = function (num) { return `0000${num.toString(16)}`.slice(-4); }; +// fontkit's readString() can return a raw Uint8Array instead of a string when the +// runtime's TextDecoder doesn't support the font's encoding (e.g. x-mac-roman, +// utf-16be) - notably on Hermes (React Native), which only supports utf-8/utf-16le. +const postscriptNameToString = function (postscriptName) { + if (typeof postscriptName === 'string') { + return postscriptName; + } + if (postscriptName instanceof Uint8Array) { + let str = ''; + for (let i = 0; i < postscriptName.length; i++) { + str += String.fromCharCode(postscriptName[i]); + } + return str; + } + return String(postscriptName || ''); +}; + class EmbeddedFont extends PDFFont { constructor(document, font, id) { super(); @@ -14,7 +31,7 @@ class EmbeddedFont extends PDFFont { this.unicode = [[0]]; this.widths = [this.font.getGlyph(0).advanceWidth]; - this.name = this.font.postscriptName; + this.name = postscriptNameToString(this.font.postscriptName); this.scale = 1000 / this.font.unitsPerEm; this.ascender = this.font.ascent * this.scale; this.descender = this.font.descent * this.scale; @@ -153,7 +170,8 @@ class EmbeddedFont extends PDFFont { const tag = [1, 2, 3, 4, 5, 6] .map((i) => String.fromCharCode((this.id.charCodeAt(i) || 73) + 17)) .join(''); - const name = tag + '+' + this.font.postscriptName?.replaceAll(' ', '_'); + const name = + tag + '+' + postscriptNameToString(this.font.postscriptName).replace(/ /g, '_'); const { bbox } = this.font; const descriptor = this.document.ref({ diff --git a/tests/unit/font.spec.js b/tests/unit/font.spec.js index eec5dc660..e1ef78bd3 100644 --- a/tests/unit/font.spec.js +++ b/tests/unit/font.spec.js @@ -54,6 +54,32 @@ describe('EmbeddedFont', () => { expect(dictionary.data.BaseFont).toBe('BAJJZZ+Roboto-Regular'); }); + + // https://github.com/foliojs/pdfkit/issues/1687 + // fontkit's readString() returns a raw Uint8Array instead of a string when the + // runtime's TextDecoder doesn't support the font's encoding - this happens on + // Hermes (React Native), which only supports utf-8/utf-16le. + test('does not crash when postscriptName is a Uint8Array', () => { + const document = new PDFDocument(); + const font = PDFFontFactory.open( + document, + 'tests/fonts/Roboto-Regular.ttf', + undefined, + 'F1099', + ); + const original = font.font.postscriptName; + Object.defineProperty(font.font, 'postscriptName', { + value: new Uint8Array(Array.from(original, (c) => c.charCodeAt(0))), + }); + + const dictionary = { + end: () => {}, + }; + font.dictionary = dictionary; + + expect(() => font.embed()).not.toThrow(); + expect(dictionary.data.BaseFont).toBe('BAJJZZ+Roboto-Regular'); + }); }); describe('toUnicodeMap', () => {