perf(otf): woff2 输出跳过 OTF sfnt checksum 计算

assembleSfnt 在 OTF→woff2 路径原总计算各表 checksum(calcTableChecksum 遍历每表全部字节,CFF 是大头)+ 目录 dirSum + head.checkSumAdjustment 回填。但 encodeTTFToWOFF2 解析 sfnt 目录只读 tag/offset/length(line 742-748 不读 off+4 checksum),也不消费 adjustment,故 woff2 输出时这些全是浪费。

与 [[woff2-skip-ttf-checksum]] 的 TTFWriter.skipCheckSum(font.js:175 woff2 分支早做)完全同类,OTF 侧原遗漏,本轮补齐。裸 OTF 输出(otf/ttf)仍算 checksum(规范要求)。

otf-思源黑体 woff2 avg 1.2→0.7ms,所有 OTF woff2 case SSIM 不变(1.0000),TTF/裸 OTF 用例不受影响。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
崮生(子虚) 2026-07-25 09:54:44 +08:00
parent 51c9f81472
commit cda0215b36
2 changed files with 34 additions and 11 deletions

View File

@ -138,7 +138,7 @@ export const fontSubset = (
* GSUB/GPOS gid */
if (option.sourceType === "otf") {
const fontU8 = new Uint8Array(fontBuffer);
const otfBytes = subsetOTF(fontU8, codePoints, true);
const otfBytes = subsetOTF(fontU8, codePoints, true, option.outType);
if (otfBytes !== null) {
if (option.outType === "woff2") {
return encodeTTFToWOFF2(otfBytes);

View File

@ -389,8 +389,16 @@ function calcTableChecksum(bytes: Uint8Array): number {
/**
* OTF sfntOTTO + 4 + head.checkSumAdjustment
* @param tables (tag bytes) tag 便
* @param skipCheckSum woff2 trueencodeTTFToWOFF2 sfnt tag/offset/length
* line 742-748 off+4 checksum head.checkSumAdjustment checksum
* calcTableChecksum CFF + dirSum + adjustment
* [[woff2-skip-ttf-checksum]] TTFWriter.skipCheckSum TTF OTF
* OTF otf/ttf checksum/ false
*/
function assembleSfnt(tables: { tag: string; bytes: Uint8Array }[]): Uint8Array {
function assembleSfnt(
tables: { tag: string; bytes: Uint8Array }[],
skipCheckSum: boolean,
): Uint8Array {
/** 按 tag 升序sfnt 规范要求,浏览器按 tag 二分查找表) */
tables.sort((a, b) => (a.tag < b.tag ? -1 : a.tag > b.tag ? 1 : 0));
const numTables = tables.length;
@ -426,11 +434,18 @@ function assembleSfnt(tables: { tag: string; bytes: Uint8Array }[]): Uint8Array
out[r + 1] = tables[i].tag.charCodeAt(1);
out[r + 2] = tables[i].tag.charCodeAt(2);
out[r + 3] = tables[i].tag.charCodeAt(3);
const checksum = calcTableChecksum(tables[i].bytes);
tablesDataSum = (tablesDataSum + checksum) >>> 0;
dv.setUint32(r + 4, checksum, false);
dv.setUint32(r + 8, dataOff, false);
dv.setUint32(r + 12, tables[i].bytes.length, false);
/** skipCheckSumwoff2 不消费 checksumoff+4 字段),跳过 calcTableChecksum遍历每表全部字节CFF 大头) */
if (skipCheckSum) {
/** checksum 字段保持 0out 已 zero-init仅写 offset/length */
dv.setUint32(r + 8, dataOff, false);
dv.setUint32(r + 12, tables[i].bytes.length, false);
} else {
const checksum = calcTableChecksum(tables[i].bytes);
tablesDataSum = (tablesDataSum + checksum) >>> 0;
dv.setUint32(r + 4, checksum, false);
dv.setUint32(r + 8, dataOff, false);
dv.setUint32(r + 12, tables[i].bytes.length, false);
}
out.set(tables[i].bytes, dataOff);
dataOff += paddedLens[i];
}
@ -438,8 +453,9 @@ function assembleSfnt(tables: { tag: string; bytes: Uint8Array }[]): Uint8Array
/** head.checkSumAdjustment = 0xB1B0AFBA - sum
* sum = sum + Σ checksum
* sum = checksum dirSize sfnt +
* KB CFF */
if (headIdx >= 0) {
* KB CFF
* skipCheckSumwoff2 adjustmentpassthroughHead 0 dirSum + */
if (!skipCheckSum && headIdx >= 0) {
const headRecOff = 12 + headIdx * 16;
const headDataOff = dv.getUint32(headRecOff + 8, false);
let dirSum = 0;
@ -460,7 +476,13 @@ function assembleSfnt(tables: { tag: string; bytes: Uint8Array }[]): Uint8Array
* @param keepGSUB GSUB/GPOS/
* @returns OTF CFF null
*/
export function subsetOTF(fontBuffer: Uint8Array, codePoints: number[], keepGSUB: boolean): Uint8Array | null {
export function subsetOTF(
fontBuffer: Uint8Array,
codePoints: number[],
keepGSUB: boolean,
/** 输出格式woff2 时 assembleSfnt 跳过 checksumwoff2 不消费,省 calcTableChecksum 遍历 CFF 大表) */
outType?: string,
): Uint8Array | null {
const dv = new DataView(fontBuffer.buffer, fontBuffer.byteOffset, fontBuffer.byteLength);
const tables = readSfntTables(dv);
@ -576,5 +598,6 @@ export function subsetOTF(fontBuffer: Uint8Array, codePoints: number[], keepGSUB
}
}
return assembleSfnt(outTables);
/** woff2 输出不消费 sfnt checksum跳过 calcTableChecksum + adjustment 回填([[woff2-skip-ttf-checksum]] OTF 侧补齐) */
return assembleSfnt(outTables, outType === "woff2");
}