diff --git a/src/core/cff_parser.js b/src/core/cff_parser.js index 1a0bd4634..c12839e31 100644 --- a/src/core/cff_parser.js +++ b/src/core/cff_parser.js @@ -228,7 +228,7 @@ class CFFParser { parse() { const properties = this.properties; - const cff = new CFF(); + const cff = new CFF(this.bytes.length); this.cff = cff; // The first five sections must be in order, all the others are reached @@ -1017,6 +1017,10 @@ class CFF { charStringCount = 0; + constructor(rawFileLength = 0) { + this.rawFileLength = rawFileLength; + } + duplicateFirstGlyph() { // Browsers will not display a glyph at position 0. Typically glyph 0 is // notdef, but a number of fonts put a valid glyph there so it must be @@ -1372,6 +1376,57 @@ class CFFOffsetTracker { } } +class CompilerOutput { + #buf; + + #bufLength = 1024; + + #pos = 0; + + constructor(minLength) { + // Note: Usually the compiled size is smaller than the initial data, + // however in some cases it may increase slightly. + this.#initBuf(minLength); + } + + #initBuf(minLength) { + // Compute the first power of two that is as big as the `minLength`. + while (this.#bufLength < minLength) { + this.#bufLength *= 2; + } + const newBuf = new Uint8Array(this.#bufLength); + + if (this.#buf) { + newBuf.set(this.#buf, 0); + } + this.#buf = newBuf; + } + + get data() { + return this.#buf.subarray(0, this.#pos); + } + + get finalData() { + const data = this.#buf.slice(0, this.#pos); + this.#buf = null; + return data; + } + + get length() { + return this.#pos; + } + + add(data) { + const newPos = this.#pos + data.length; + if (newPos > this.#bufLength) { + // It should be very rare that the buffer needs to grow. + this.#initBuf(newPos); + } + this.#buf.set(data, this.#pos); + this.#pos = newPos; + } +} + // Takes a CFF and converts it to the binary representation. class CFFCompiler { constructor(cff) { @@ -1380,21 +1435,7 @@ class CFFCompiler { compile() { const cff = this.cff; - const output = { - data: [], - length: 0, - add(data) { - try { - // It's possible to exceed the call stack maximum size when trying - // to push too much elements. - // In case of failure, we fallback to the `concat` method. - this.data.push(...data); - } catch { - this.data = this.data.concat(data); - } - this.length = this.data.length; - }, - }; + const output = new CompilerOutput(cff.rawFileLength); // Compile the five entries that must be in order. const header = this.compileHeader(cff.header); @@ -1499,7 +1540,7 @@ class CFFCompiler { // the sanitizer will bail out. Add a dummy byte to avoid that. output.add([0]); - return output.data; + return output.finalData; } encodeNumber(value) { @@ -1781,7 +1822,7 @@ class CFFCompiler { } else { const length = 1 + numGlyphsLessNotDef * 2; out = new Uint8Array(length); - out[0] = 0; // format 0 + // format 0, skip redundant `out[0] = 0;` assignment. let charsetIndex = 0; const numCharsets = charset.charset.length; let warned = false; @@ -1802,11 +1843,11 @@ class CFFCompiler { out[i + 1] = sid & 0xff; } } - return this.compileTypedArray(out); + return out; } compileEncoding(encoding) { - return this.compileTypedArray(encoding.raw); + return encoding.raw; } compileFDSelect(fdSelect) { @@ -1847,11 +1888,7 @@ class CFFCompiler { out = new Uint8Array(ranges); break; } - return this.compileTypedArray(out); - } - - compileTypedArray(data) { - return Array.from(data); + return out; } compileIndex(index, trackers = []) { @@ -1915,9 +1952,8 @@ class CFFCompiler { for (i = 0; i < count; i++) { // Notify the tracker where the object will be offset in the data. - if (trackers[i]) { - trackers[i].offset(data.length); - } + trackers[i]?.offset(data.length); + data.push(...objects[i]); } return data; diff --git a/src/core/opentype_file_builder.js b/src/core/opentype_file_builder.js index e02a0ee04..f51be45c0 100644 --- a/src/core/opentype_file_builder.js +++ b/src/core/opentype_file_builder.js @@ -35,11 +35,6 @@ function writeData(dest, offset, data) { for (let i = 0, ii = data.length; i < ii; i++) { dest[offset++] = data.charCodeAt(i) & 0xff; } - } else { - // treating everything else as array - for (const num of data) { - dest[offset++] = num & 0xff; - } } } diff --git a/src/core/type1_font.js b/src/core/type1_font.js index 94f81056c..919157e6e 100644 --- a/src/core/type1_font.js +++ b/src/core/type1_font.js @@ -153,6 +153,8 @@ function getEexecBlock(stream, suggestedLength) { * Type1Font is also a CIDFontType0. */ class Type1Font { + #rawFileLength; + constructor(name, file, properties) { // Some bad generators embed pfb file as is, we have to strip 6-byte header. // Also, length1 and length2 might be off by 6 bytes as well. @@ -200,6 +202,7 @@ class Type1Font { for (const key in data.properties) { properties[key] = data.properties[key]; } + this.#rawFileLength = headerBlock.length + eexecBlock.length; const charstrings = data.charstrings; const type2Charstrings = this.getType2Charstrings(charstrings); @@ -323,7 +326,7 @@ class Type1Font { } wrap(name, glyphs, charstrings, subrs, properties) { - const cff = new CFF(); + const cff = new CFF(this.#rawFileLength); cff.header = new CFFHeader(1, 0, 4, 4); cff.names = [name]; diff --git a/test/unit/cff_parser_spec.js b/test/unit/cff_parser_spec.js index 82b9ba4cd..c58fd6551 100644 --- a/test/unit/cff_parser_spec.js +++ b/test/unit/cff_parser_spec.js @@ -430,47 +430,53 @@ describe("CFFCompiler", function () { const fdSelect = new CFFFDSelect(0, [3, 2, 1]); const c = new CFFCompiler(); const out = c.compileFDSelect(fdSelect); - expect(out).toEqual([ - 0, // format - 3, // gid: 0 fd 3 - 2, // gid: 1 fd 3 - 1, // gid: 2 fd 3 - ]); + expect(out).toEqual( + new Uint8Array([ + 0, // format + 3, // gid: 0 fd 3 + 2, // gid: 1 fd 3 + 1, // gid: 2 fd 3 + ]) + ); }); it("compiles fdselect format 3", function () { const fdSelect = new CFFFDSelect(3, [0, 0, 1, 1]); const c = new CFFCompiler(); const out = c.compileFDSelect(fdSelect); - expect(out).toEqual([ - 3, // format - 0, // nRanges (high) - 2, // nRanges (low) - 0, // range struct 0 - first (high) - 0, // range struct 0 - first (low) - 0, // range struct 0 - fd - 0, // range struct 0 - first (high) - 2, // range struct 0 - first (low) - 1, // range struct 0 - fd - 0, // sentinel (high) - 4, // sentinel (low) - ]); + expect(out).toEqual( + new Uint8Array([ + 3, // format + 0, // nRanges (high) + 2, // nRanges (low) + 0, // range struct 0 - first (high) + 0, // range struct 0 - first (low) + 0, // range struct 0 - fd + 0, // range struct 0 - first (high) + 2, // range struct 0 - first (low) + 1, // range struct 0 - fd + 0, // sentinel (high) + 4, // sentinel (low) + ]) + ); }); it("compiles fdselect format 3, single range", function () { const fdSelect = new CFFFDSelect(3, [0, 0]); const c = new CFFCompiler(); const out = c.compileFDSelect(fdSelect); - expect(out).toEqual([ - 3, // format - 0, // nRanges (high) - 1, // nRanges (low) - 0, // range struct 0 - first (high) - 0, // range struct 0 - first (low) - 0, // range struct 0 - fd - 0, // sentinel (high) - 2, // sentinel (low) - ]); + expect(out).toEqual( + new Uint8Array([ + 3, // format + 0, // nRanges (high) + 1, // nRanges (low) + 0, // range struct 0 - first (high) + 0, // range struct 0 - first (low) + 0, // range struct 0 - fd + 0, // sentinel (high) + 2, // sentinel (low) + ]) + ); }); it("compiles charset of CID font", function () { @@ -479,13 +485,15 @@ describe("CFFCompiler", function () { const numGlyphs = 7; const out = c.compileCharset(charset, numGlyphs, new CFFStrings(), true); // All CID charsets get turned into a simple format 2. - expect(out).toEqual([ - 2, // format - 0, // cid (high) - 1, // cid (low) - 0, // nLeft (high) - numGlyphs - 2, // nLeft (low) - ]); + expect(out).toEqual( + new Uint8Array([ + 2, // format + 0, // cid (high) + 1, // cid (low) + 0, // nLeft (high) + numGlyphs - 2, // nLeft (low) + ]) + ); }); it("compiles charset of non CID font", function () { @@ -494,13 +502,15 @@ describe("CFFCompiler", function () { const numGlyphs = 3; const out = c.compileCharset(charset, numGlyphs, new CFFStrings(), false); // All non-CID fonts use a format 0 charset. - expect(out).toEqual([ - 0, // format - 0, // sid of 'space' (high) - 1, // sid of 'space' (low) - 0, // sid of 'exclam' (high) - 2, // sid of 'exclam' (low) - ]); + expect(out).toEqual( + new Uint8Array([ + 0, // format + 0, // sid of 'space' (high) + 1, // sid of 'space' (low) + 0, // sid of 'exclam' (high) + 2, // sid of 'exclam' (low) + ]) + ); }); // TODO a lot more compiler tests