Skip to content

Commit 55d20cf

Browse files
authored
update: 2.6.18 fix: patch opentype.js cmap Format 4 table overflow bug
fix: resolve cmap Format 4 table overflow in large font generation opentype.js exhibits a critical overflow bug when processing fonts with 8000+ drawing glyphs. The cmap Format 4 segment generator lacks consecutive segment merging logic, creating isolated segments for each Unicode codepoint. When character count exceeds approximately 8192, the cumulative table length calculation produces values exceeding the uint16 maximum (65535 bytes). The Format 4 length field, defined as uint16, silently truncates on overflow. Windows font parsers detect the length mismatch between the declared header value and actual physical data offset, treating the entire font as corrupted and rejecting it during parsing. The root cause lies in the cmap.make algorithm operating within a closure, making direct external patching unfeasible. Segment sorting occurs prior to binary serialization but provides no opportunity for deduplication. Implement segment merging via Array.prototype.sort interception during the Font.prototype.toTables lifecycle. This technique: - Intercepts sort operations on the segment array post-sort, pre-serialization - Merges consecutive segments where curr.start equals prev.end + 1 and deltas are equal, reducing overall segment count proportionally to run lengths - Restores original Array.sort after completion, avoiding side effects - Operates at the optimal closure boundary without library modification Testing confirms fonts with 8000+ glyphs now load correctly on Windows platforms while maintaining backward compatibility with smaller fonts.
1 parent 796b9f7 commit 55d20cf

2 files changed

Lines changed: 33 additions & 1 deletion

File tree

app/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<link rel="alternate" hreflang="ar" href="https://subs.js.org/ass-subset/?lang=ar">
2222
<link rel="alternate" hreflang="x-default" href="https://subs.js.org/ass-subset/">
2323
<link rel="icon" type="image/jpeg" href="icons/icon-256.png">
24-
<meta name="version" content="2.6.17">
24+
<meta name="version" content="2.6.18">
2525
<meta property="og:type" content="website">
2626
<meta property="og:title" content="ASS Subsetter · Font Embedding for Subtitles">
2727
<meta property="og:description" content="Font embedding tool for ASS/SSA subtitles. Subset fonts to compress file size, or embed them fully. Perfect for complex subtitle typesetting with multiple font files.">

app/worker.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2208,6 +2208,38 @@ self.onmessage = async function (e) {
22082208
try {
22092209
const path = (typeof OPENTYPE_PATH !== 'undefined') ? OPENTYPE_PATH : e.data.opentypePath;
22102210
importScripts(path);
2211+
const applyCmapPatch = () => {
2212+
if (!self.opentype?.Font || self.opentype.Font.prototype._isPatched) return;
2213+
const origToTables = self.opentype.Font.prototype.toTables;
2214+
self.opentype.Font.prototype.toTables = function() {
2215+
const origSort = Array.prototype.sort;
2216+
Array.prototype.sort = function(cmp) {
2217+
const res = origSort.call(this, cmp);
2218+
if (this.length > 0 && 'start' in this[0] && 'end' in this[0] && 'delta' in this[0] && 'glyphIndex' in this[0]) {
2219+
const comp = [];
2220+
for (let i = 0; i < this.length; i++) {
2221+
const curr = this[i];
2222+
const prev = comp[comp.length - 1];
2223+
if (prev && prev.end + 1 === curr.start && prev.delta === curr.delta) {
2224+
prev.end = curr.end;
2225+
} else {
2226+
comp.push(curr);
2227+
}
2228+
}
2229+
this.length = 0;
2230+
for (let i = 0; i < comp.length; i++) this.push(comp[i]);
2231+
}
2232+
return res;
2233+
};
2234+
try {
2235+
return origToTables.call(this);
2236+
} finally {
2237+
Array.prototype.sort = origSort;
2238+
}
2239+
};
2240+
self.opentype.Font.prototype._isPatched = true;
2241+
};
2242+
applyCmapPatch();
22112243
} catch (err) {
22122244
self.postMessage({ type: 'error', id, error: 'Failed to load opentype.js: ' + err.message });
22132245
return;

0 commit comments

Comments
 (0)