Merge pull request #21097 from Snuffleupagus/convert-tables-TypedArray

Use TypedArrays when building more TrueType tables
This commit is contained in:
Tim van der Meij 2026-04-13 21:07:07 +02:00 committed by GitHub
commit 7aca886f2b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -61,6 +61,7 @@ import { compileFontInfo } from "./obj_bin_transform_core.js";
import { FontRendererFactory } from "./font_renderer.js"; import { FontRendererFactory } from "./font_renderer.js";
import { getFontBasicMetrics } from "./metrics.js"; import { getFontBasicMetrics } from "./metrics.js";
import { GlyfTable } from "./glyf.js"; import { GlyfTable } from "./glyf.js";
import { MathClamp } from "../shared/math_clamp.js";
import { OpenTypeFileBuilder } from "./opentype_file_builder.js"; import { OpenTypeFileBuilder } from "./opentype_file_builder.js";
import { Stream } from "./stream.js"; import { Stream } from "./stream.js";
import { Type1Font } from "./type1_font.js"; import { Type1Font } from "./type1_font.js";
@ -311,29 +312,32 @@ function string16(value) {
return String.fromCharCode((value >> 8) & 0xff, value & 0xff); return String.fromCharCode((value >> 8) & 0xff, value & 0xff);
} }
function safeString16(value) { function setArray(data, pos, arr) {
data.set(arr, pos);
return pos + arr.length;
}
function setInt16(view, pos, val) {
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
assert( assert(
typeof value === "number" && !Number.isNaN(value), typeof val === "number" && Math.abs(val) < 2 ** 16,
`safeString16: Unexpected input "${value}".` `setInt16: Unexpected input "${val}".`
);
}
view.setInt16(pos, val);
return pos + 2;
}
function setSafeInt16(view, pos, val) {
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
assert(
typeof val === "number" && !Number.isNaN(val),
`safeString16: Unexpected input "${val}".`
); );
} }
// clamp value to the 16-bit int range // clamp value to the 16-bit int range
if (value > 0x7fff) { view.setInt16(pos, MathClamp(val, -0x8000, 0x7fff));
value = 0x7fff; return pos + 2;
} else if (value < -0x8000) {
value = -0x8000;
}
return String.fromCharCode((value >> 8) & 0xff, value & 0xff);
}
function ensureInt16(v) {
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
assert(
typeof v === "number" && Math.abs(v) < 2 ** 16,
`ensureInt16: Unexpected input "${v}".`
);
}
} }
function isTrueTypeFile(file) { function isTrueTypeFile(file) {
@ -3266,55 +3270,77 @@ class Font {
// Font header // Font header
builder.addTable( builder.addTable(
"head", "head",
"\x00\x01\x00\x00" + // Version number (function fontTableHead() {
"\x00\x00\x10\x00" + // fontRevision const dateArr = [0x00, 0x00, 0x00, 0x00, 0x9e, 0x0b, 0x7e, 0x27];
"\x00\x00\x00\x00" + // checksumAdjustement const data = new Uint8Array(54),
"\x5F\x0F\x3C\xF5" + // magicNumber view = new DataView(data.buffer);
"\x00\x00" + // Flags let pos = 0;
safeString16(unitsPerEm) + // unitsPerEM
"\x00\x00\x00\x00\x9e\x0b\x7e\x27" + // creation date pos = setArray(data, pos, [0x00, 0x01, 0x00, 0x00]); // Version number
"\x00\x00\x00\x00\x9e\x0b\x7e\x27" + // modifification date pos = setArray(data, pos, [0x00, 0x00, 0x10, 0x00]); // fontRevision
"\x00\x00" + // xMin pos += 4; // checksumAdjustement, skip redundant "\x00\x00\x00\x00"
safeString16(properties.descent) + // yMin pos = setArray(data, pos, [0x5f, 0x0f, 0x3c, 0xf5]); // magicNumber
"\x0F\xFF" + // xMax pos += 2; // Flags, skip redundant "\x00\x00"
safeString16(properties.ascent) + // yMax pos = setSafeInt16(view, pos, unitsPerEm); // unitsPerEM
string16(properties.italicAngle ? 2 : 0) + // macStyle pos = setArray(data, pos, dateArr); // creation date
"\x00\x11" + // lowestRecPPEM pos = setArray(data, pos, dateArr); // modifification date
"\x00\x00" + // fontDirectionHint pos += 2; // xMin, skip redundant "\x00\x00"
"\x00\x00" + // indexToLocFormat pos = setSafeInt16(view, pos, properties.descent); // yMin
"\x00\x00" // glyphDataFormat pos = setArray(data, pos, [0x0f, 0xff]); // xMax
pos = setSafeInt16(view, pos, properties.ascent); // yMax
pos = setInt16(view, pos, properties.italicAngle ? 2 : 0); // macStyle
setArray(data, pos, [0x00, 0x11]); // lowestRecPPEM
// fontDirectionHint, skip redundant "\x00\x00"
// indexToLocFormat, skip redundant "\x00\x00"
// glyphDataFormat, skip redundant "\x00\x00"
return data;
})()
); );
// Horizontal header // Horizontal header
builder.addTable( builder.addTable(
"hhea", "hhea",
"\x00\x01\x00\x00" + // Version number (function fontTableHhea() {
safeString16(properties.ascent) + // Typographic Ascent const data = new Uint8Array(36),
safeString16(properties.descent) + // Typographic Descent view = new DataView(data.buffer);
"\x00\x00" + // Line Gap let pos = 0;
"\xFF\xFF" + // advanceWidthMax
"\x00\x00" + // minLeftSidebearing pos = setArray(data, pos, [0x00, 0x01, 0x00, 0x00]); // Version number
"\x00\x00" + // minRightSidebearing pos = setSafeInt16(view, pos, properties.ascent); // Typographic Ascent
"\x00\x00" + // xMaxExtent pos = setSafeInt16(view, pos, properties.descent); // Typographic Descent
safeString16(properties.capHeight) + // caretSlopeRise pos += 2; // Line Gap, skip redundant "\x00\x00"
safeString16(Math.tan(properties.italicAngle) * properties.xHeight) + // caretSlopeRun pos = setArray(data, pos, [0xff, 0xff]); // advanceWidthMax
"\x00\x00" + // caretOffset pos += 2; // minLeftSidebearing, skip redundant "\x00\x00"
"\x00\x00" + // -reserved- pos += 2; // minRightSidebearing, skip redundant "\x00\x00"
"\x00\x00" + // -reserved- pos += 2; // xMaxExtent, skip redundant "\x00\x00"
"\x00\x00" + // -reserved- pos = setSafeInt16(view, pos, properties.capHeight); // caretSlopeRise
"\x00\x00" + // -reserved- pos = setSafeInt16(
"\x00\x00" + // metricDataFormat view,
string16(numGlyphs) // Number of HMetrics pos,
Math.tan(properties.italicAngle) * properties.xHeight
); // caretSlopeRun
pos += 2; // caretOffset, skip redundant "\x00\x00"
pos += 2; // -reserved-, skip redundant "\x00\x00"
pos += 2; // -reserved-, skip redundant "\x00\x00"
pos += 2; // -reserved-, skip redundant "\x00\x00"
pos += 2; // -reserved-, skip redundant "\x00\x00"
pos += 2; // metricDataFormat, skip redundant "\x00\x00"
setInt16(view, pos, numGlyphs); // Number of HMetrics
return data;
})()
); );
// Horizontal metrics // Horizontal metrics
builder.addTable( builder.addTable(
"hmtx", "hmtx",
(function fontFieldsHmtx() { (function fontTableHmtx() {
const charstrings = font.charstrings; const charstrings = font.charstrings;
const cffWidths = font.cff?.widths ?? null; const cffWidths = font.cff?.widths ?? null;
const data = new Uint8Array(numGlyphs * 4); const data = new Uint8Array(numGlyphs * 4),
view = new DataView(data.buffer);
// Fake .notdef (width=0 and lsb=0) first, skip redundant assignment. // Fake .notdef (width=0 and lsb=0) first, skip redundant assignment.
let pos = 4; let pos = 4;
@ -3325,11 +3351,8 @@ class Font {
} else if (cffWidths) { } else if (cffWidths) {
width = Math.ceil(cffWidths[i] || 0); width = Math.ceil(cffWidths[i] || 0);
} }
ensureInt16(width); pos = setInt16(view, pos, width);
data[pos++] = (width >> 8) & 0xff; pos += 2; // Use lsb=0, skip redundant assignment.
data[pos++] = width & 0xff;
// Use lsb=0, skip redundant assignment.
pos += 2;
} }
return data; return data;
})() })()
@ -3338,8 +3361,15 @@ class Font {
// Maximum profile // Maximum profile
builder.addTable( builder.addTable(
"maxp", "maxp",
"\x00\x00\x50\x00" + // Version number (function fontTableMaxp() {
string16(numGlyphs) // Num of glyphs const data = new Uint8Array(6),
view = new DataView(data.buffer);
setArray(data, 0, [0x00, 0x00, 0x50, 0x00]); // Version number
setInt16(view, 4, numGlyphs); // Num of glyphs
return data;
})()
); );
// Naming tables // Naming tables