From aa0bc24e95ddee69f5797811a3ce588334de71f3 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Mon, 13 Apr 2026 15:12:38 +0200 Subject: [PATCH 1/3] Use a TypedArray in the `createPostTable` function --- src/core/fonts.js | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/core/fonts.js b/src/core/fonts.js index 465d206f2..a4a1d22ad 100644 --- a/src/core/fonts.js +++ b/src/core/fonts.js @@ -340,6 +340,17 @@ function setSafeInt16(view, pos, val) { return pos + 2; } +function setInt32(view, pos, val) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + typeof val === "number" && Math.abs(val) < 2 ** 32, + `setInt32: Unexpected input "${val}".` + ); + } + view.setInt32(pos, val); + return pos + 4; +} + function isTrueTypeFile(file) { const header = file.peekBytes(4), str = bytesToString(header); @@ -888,18 +899,21 @@ function createOS2Table(properties, charstrings, override) { } function createPostTable(properties) { - const angle = Math.floor(properties.italicAngle * 2 ** 16); - return ( - "\x00\x03\x00\x00" + // Version number - string32(angle) + // italicAngle - "\x00\x00" + // underlinePosition - "\x00\x00" + // underlineThickness - string32(properties.fixedPitch ? 1 : 0) + // isFixedPitch - "\x00\x00\x00\x00" + // minMemType42 - "\x00\x00\x00\x00" + // maxMemType42 - "\x00\x00\x00\x00" + // minMemType1 - "\x00\x00\x00\x00" // maxMemType1 - ); + const data = new Uint8Array(32), + view = new DataView(data.buffer); + let pos = 0; + + pos = setArray(data, pos, [0x00, 0x03, 0x00, 0x00]); // Version number + pos = setInt32(view, pos, Math.floor(properties.italicAngle * 2 ** 16)); // italicAngle + pos += 2; // underlinePosition, skip redundant "\x00\x00" + pos += 2; // underlineThickness, skip redundant "\x00\x00" + setInt32(view, pos, properties.fixedPitch ? 1 : 0); // isFixedPitch + // minMemType42, skip redundant "\x00\x00\x00\x00" + // maxMemType42, skip redundant "\x00\x00\x00\x00" + // minMemType1, skip redundant "\x00\x00\x00\x00" + // maxMemType1, skip redundant "\x00\x00\x00\x00" + + return data; } function createPostscriptName(name) { From e8ed6c6e245995718da57694dcfc5cbfd9358f74 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Mon, 13 Apr 2026 15:29:02 +0200 Subject: [PATCH 2/3] Use a TypedArray in the `createOS2Table` function --- src/core/fonts.js | 95 +++++++++++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 41 deletions(-) diff --git a/src/core/fonts.js b/src/core/fonts.js index a4a1d22ad..2f9533966 100644 --- a/src/core/fonts.js +++ b/src/core/fonts.js @@ -855,47 +855,60 @@ function createOS2Table(properties, charstrings, override) { const winAscent = override.yMax || typoAscent; const winDescent = -override.yMin || -typoDescent; - return ( - "\x00\x03" + // version - "\x02\x24" + // xAvgCharWidth - "\x01\xF4" + // usWeightClass - "\x00\x05" + // usWidthClass - "\x00\x00" + // fstype (0 to let the font loads via font-face on IE) - "\x02\x8A" + // ySubscriptXSize - "\x02\xBB" + // ySubscriptYSize - "\x00\x00" + // ySubscriptXOffset - "\x00\x8C" + // ySubscriptYOffset - "\x02\x8A" + // ySuperScriptXSize - "\x02\xBB" + // ySuperScriptYSize - "\x00\x00" + // ySuperScriptXOffset - "\x01\xDF" + // ySuperScriptYOffset - "\x00\x31" + // yStrikeOutSize - "\x01\x02" + // yStrikeOutPosition - "\x00\x00" + // sFamilyClass - "\x00\x00\x06" + - String.fromCharCode(properties.fixedPitch ? 0x09 : 0x00) + - "\x00\x00\x00\x00\x00\x00" + // Panose - string32(ulUnicodeRange1) + // ulUnicodeRange1 (Bits 0-31) - string32(ulUnicodeRange2) + // ulUnicodeRange2 (Bits 32-63) - string32(ulUnicodeRange3) + // ulUnicodeRange3 (Bits 64-95) - string32(ulUnicodeRange4) + // ulUnicodeRange4 (Bits 96-127) - "\x2A\x32\x31\x2A" + // achVendID - string16(properties.italicAngle ? 1 : 0) + // fsSelection - string16(firstCharIndex || properties.firstChar) + // usFirstCharIndex - string16(lastCharIndex || properties.lastChar) + // usLastCharIndex - string16(typoAscent) + // sTypoAscender - string16(typoDescent) + // sTypoDescender - "\x00\x64" + // sTypoLineGap (7%-10% of the unitsPerEM value) - string16(winAscent) + // usWinAscent - string16(winDescent) + // usWinDescent - "\x00\x00\x00\x00" + // ulCodePageRange1 (Bits 0-31) - "\x00\x00\x00\x00" + // ulCodePageRange2 (Bits 32-63) - string16(properties.xHeight) + // sxHeight - string16(properties.capHeight) + // sCapHeight - string16(0) + // usDefaultChar - string16(firstCharIndex || properties.firstChar) + // usBreakChar - "\x00\x03" // usMaxContext - ); + const data = new Uint8Array(96), + view = new DataView(data.buffer); + let pos = 0; + + pos = setArray(data, pos, [0x00, 0x03]); // version + pos = setArray(data, pos, [0x02, 0x24]); // xAvgCharWidth + pos = setArray(data, pos, [0x01, 0xf4]); // usWeightClass + pos = setArray(data, pos, [0x00, 0x05]); // usWidthClass + pos += 2; // fstype (0 to improve browser compatibility), skip redundant "\x00\x00" + pos = setArray(data, pos, [0x02, 0x8a]); // ySubscriptXSize + pos = setArray(data, pos, [0x02, 0xbb]); // ySubscriptYSize + pos += 2; // ySubscriptXOffset, skip redundant "\x00\x00" + pos = setArray(data, pos, [0x00, 0x8c]); // ySubscriptYOffset + pos = setArray(data, pos, [0x02, 0x8a]); // ySuperScriptXSize + pos = setArray(data, pos, [0x02, 0xbb]); // ySuperScriptYSize + pos += 2; // ySuperScriptXOffset, skip redundant "\x00\x00" + pos = setArray(data, pos, [0x01, 0xdf]); // ySuperScriptYOffset + pos = setArray(data, pos, [0x00, 0x31]); // yStrikeOutSize + pos = setArray(data, pos, [0x01, 0x02]); // yStrikeOutPosition + pos += 2; // sFamilyClass, skip redundant "\x00\x00" + pos = setArray(data, pos, [ + 0x00, + 0x00, + 0x06, + properties.fixedPitch ? 0x09 : 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ]); // Panose + pos = setInt32(view, pos, ulUnicodeRange1); // ulUnicodeRange1 (Bits 0-31) + pos = setInt32(view, pos, ulUnicodeRange2); // ulUnicodeRange2 (Bits 32-63) + pos = setInt32(view, pos, ulUnicodeRange3); // ulUnicodeRange3 (Bits 64-95) + pos = setInt32(view, pos, ulUnicodeRange4); // ulUnicodeRange4 (Bits 96-127) + pos = setArray(data, pos, [0x2a, 0x32, 0x31, 0x2a]); // achVendID + pos = setInt16(view, pos, properties.italicAngle ? 1 : 0); // fsSelection + pos = setInt16(view, pos, firstCharIndex || properties.firstChar); // usFirstCharIndex + pos = setInt16(view, pos, lastCharIndex || properties.lastChar); // usLastCharIndex + pos = setInt16(view, pos, typoAscent); // sTypoAscender + pos = setInt16(view, pos, typoDescent); // sTypoDescender + pos = setArray(data, pos, [0x00, 0x64]); // sTypoLineGap (7%-10% of the unitsPerEM value) + pos = setInt16(view, pos, winAscent); // usWinAscent + pos = setInt16(view, pos, winDescent); // usWinDescent + pos += 4; // ulCodePageRange1 (Bits 0-31), skip redundant "\x00\x00\x00\x00" + pos += 4; // ulCodePageRange2 (Bits 32-63), skip redundant "\x00\x00\x00\x00" + pos = setInt16(view, pos, properties.xHeight); // sxHeight + pos = setInt16(view, pos, properties.capHeight); // sCapHeight + pos += 2; // usDefaultChar, skip redundant "\x00\x00" + pos = setInt16(view, pos, firstCharIndex || properties.firstChar); // usBreakChar + setArray(data, pos, [0x00, 0x03]); // usMaxContext + + return data; } function createPostTable(properties) { From 634ce3c163f268290bba81b55be6a8a7cc1a0082 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Tue, 14 Apr 2026 12:18:56 +0200 Subject: [PATCH 3/3] Convert the return value in `createCmapTable` and `createNameTable` to a TypedArray Compared to the other TrueType table building functions, see previous patches, these ones are not trivial to convert to use TypedArrays properly. However, in order to simplify the `OpenTypeFileBuilder` implementation a little bit we can at least have these functions return TypedArray data. --- src/core/fonts.js | 16 ++++++++-------- src/core/opentype_file_builder.js | 10 +--------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/core/fonts.js b/src/core/fonts.js index 2f9533966..27bcd9fe4 100644 --- a/src/core/fonts.js +++ b/src/core/fonts.js @@ -21,6 +21,7 @@ import { info, shadow, string32, + stringToBytes, warn, } from "../shared/util.js"; import { CFFCompiler, CFFParser } from "./cff_parser.js"; @@ -741,13 +742,13 @@ function createCmapTable(glyphs, toUnicodeExtraMap, numGlyphs) { string32(format31012.length / 12); // nGroups } - return ( + return stringToBytes( cmap + - "\x00\x04" + // format - string16(format314.length + 4) + // length - format314 + - header31012 + - format31012 + "\x00\x04" + // format + string16(format314.length + 4) + // length + format314 + + header31012 + + format31012 ); } @@ -995,8 +996,7 @@ function createNameTable(name, proto) { } } - nameTable += strings.join("") + stringsUnicode.join(""); - return nameTable; + return stringToBytes(nameTable + strings.join("") + stringsUnicode.join("")); } /** diff --git a/src/core/opentype_file_builder.js b/src/core/opentype_file_builder.js index 9d8b1f625..c7d4f6b6c 100644 --- a/src/core/opentype_file_builder.js +++ b/src/core/opentype_file_builder.js @@ -63,15 +63,7 @@ class OpenTypeFileBuilder { // write the table data first (mostly for checksum) for (let i = 0; i < numTables; i++) { const table = tables.get(tablesNames[i]); - let tableOffset = tableOffsets[i]; - - if (table instanceof Uint8Array) { - file.set(table, tableOffset); - } else if (typeof table === "string") { - for (let j = 0, jj = table.length; j < jj; j++) { - file[tableOffset++] = table.charCodeAt(j) & 0xff; - } - } + file.set(table, tableOffsets[i]); } // sfnt version (4 bytes)