mirror of
https://github.com/2234839/web-font.git
synced 2026-09-04 23:02:27 +08:00
perf(gpos): ScriptList/FeatureList 整块拷贝跳过逐字段序列化
两表不含 glyphId(仅引用 lookup index),绝大多数字体其子表紧凑排列在 [listOff, listOff+span) 内、与下一表无物理交错,字节块本身即合法表。 新增 scriptListSpan/featureListSpan 计算跨度并校验自洽,满足则 subarray 整块拷贝,否则降级 serializeScriptList/serializeFeatureList。 收益:初夏 subsetGPOS 0.270→0.226ms,纯标点 0.240→0.165(-31%), 思源 0.071→0.036(-49%)。顺带保留 fontTools 的 FeatureTable 去重 (serialize 重排丢失致初夏 FeatureList 1840→3728B),输出 GPOS 字节 初夏 12064→10176(-16%)、思源 1194→978(-18%)。全基准 SSIM 不变。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8c311768f6
commit
ccc2e995c8
@ -14,7 +14,7 @@
|
||||
* @reference https://learn.microsoft.com/en-us/typography/opentype/spec/gpos
|
||||
*/
|
||||
|
||||
import { OTWriter as Writer, OTReader as Reader, serializeScriptList, serializeFeatureList } from "./ot-bytes.js";
|
||||
import { OTWriter as Writer, OTReader as Reader, serializeScriptList, serializeFeatureList, scriptListSpan, featureListSpan } from "./ot-bytes.js";
|
||||
|
||||
/** ValueFormat 位掩码 */
|
||||
const VF_X_PLACEMENT = 0x0001;
|
||||
@ -180,8 +180,24 @@ export function subsetGPOS(
|
||||
* - 不支持的(MarkBase/Context 等):原样拷贝原始字节(gid 不重映射,
|
||||
* 浏览器用新 gid 查 coverage 查不到会跳过,不会破坏,保留可能的非 gid 相关规则)
|
||||
*/
|
||||
const scriptListBytes = serializeScriptList(r, scriptListOff);
|
||||
const featureListBytes = serializeFeatureList(r, featureListOff);
|
||||
/** ScriptList / FeatureList 整块拷贝快路径:
|
||||
* 两表均不含 glyphId(仅引用 lookup index),若其子表紧凑排列在 [listOff, listOff+span)
|
||||
* 内、不与下一表物理交错,则该字节块本身即合法 ScriptList/FeatureList,直接 subarray 拷贝,
|
||||
* 跳过 serializeScriptList/serializeFeatureList 的逐 u16 读写(初夏 GPOS 0.087ms 主热点),
|
||||
* 并保留 fontTools 的 LangSys/FeatureTable 去重(serialize 重排会丢失,初夏 FL 1840→3728B)。
|
||||
* span 越界(越过下一表起始,说明物理交错)或解析异常时降级 serialize。 */
|
||||
const slSpan = scriptListSpan(r, scriptListOff);
|
||||
const slContiguous = slSpan >= 0 && scriptListOff + slSpan <= featureListOff;
|
||||
const scriptListBytes = slContiguous
|
||||
? gposBytes.subarray(scriptListOff, scriptListOff + slSpan)
|
||||
: serializeScriptList(r, scriptListOff);
|
||||
const flSpan = featureListSpan(r, featureListOff);
|
||||
const flContiguous = flSpan >= 0 && featureListOff + flSpan <= lookupListOff;
|
||||
const featureListBytes = flContiguous
|
||||
? gposBytes.subarray(featureListOff, featureListOff + flSpan)
|
||||
: serializeFeatureList(r, featureListOff);
|
||||
/** span 函数可能因越界读取 set errorFlag,clear 后后续 serializeSubtable 的越界检测仍有效 */
|
||||
r.clearError();
|
||||
/** ScriptList/FeatureList 解析失败(异常表)则整体降级返回 null(调用方保留原始 GPOS 字节) */
|
||||
if (!scriptListBytes || !featureListBytes) return null;
|
||||
|
||||
|
||||
@ -159,6 +159,80 @@ export class OTReader {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算 ScriptList 的连续字节跨度(相对 listAbs 的字节数),用于判断能否整块原样拷贝。
|
||||
*
|
||||
* ScriptList 不含 glyphId,子表(ScriptTable/LangSys)偏移相对 listAbs 起始。
|
||||
* 绝大多数字体(含思源/初夏/令东的 GPOS 与 GSUB)的 ScriptList 子表紧凑排列在
|
||||
* [listAbs, listAbs+span) 内、与 FeatureList/LookupList 无物理交错——此时该字节块本身
|
||||
* 就是合法 ScriptList,可整块拷贝跳过逐字段重序列化,并保留 fontTools 的 LangSys 去重
|
||||
* (serializeScriptList 紧凑重排会丢失去重、输出反而更大,如思源 OTF 538→1066B)。
|
||||
*
|
||||
* 本函数扫描全部 ScriptTable/LangSys,返回其最大结束偏移作为 span。返回 -1 表示解析异常
|
||||
* (errorFlag,调用方降级 serializeScriptList)。注意:span 仅保证「子表结束位置」,
|
||||
* 调用方还需校验 span 不越过下一表起始以排除物理交错。
|
||||
*
|
||||
* @param r 原始字节读取器
|
||||
* @param listAbs ScriptList 在原始字节中的绝对偏移
|
||||
* @returns ScriptList 字节跨度(≥0),解析异常返回 -1
|
||||
*/
|
||||
export function scriptListSpan(r: OTReader, listAbs: number): number {
|
||||
r.clearError();
|
||||
const scriptCount = r.u16(listAbs);
|
||||
/** span = listAbs 起,覆盖 ScriptRecord 数组 + 所有 ScriptTable 及其 LangSys 的结束位置 */
|
||||
let span = 2 + scriptCount * 6;
|
||||
for (let i = 0; i < scriptCount; i++) {
|
||||
/** ScriptRecord 偏移相对 listAbs */
|
||||
const scriptRel = r.u16(listAbs + 2 + i * 6 + 4);
|
||||
const scriptAbs = listAbs + scriptRel;
|
||||
const defaultLangSysOff = r.u16(scriptAbs);
|
||||
const langSysCount = r.u16(scriptAbs + 2);
|
||||
/** ScriptTable 头:defaultLangSysOff(2) + langSysCount(2) + LangSysRecord[langSysCount](6) */
|
||||
span = Math.max(span, scriptRel + 4 + langSysCount * 6);
|
||||
/** defaultLangSys 表:lookupOrder(2)+reqFeatureIdx(2)+featureIdxCount(2)+indices */
|
||||
if (defaultLangSysOff !== 0) {
|
||||
const dlAbs = scriptAbs + defaultLangSysOff;
|
||||
const dlRel = scriptRel + defaultLangSysOff;
|
||||
const fic = r.u16(dlAbs + 4);
|
||||
span = Math.max(span, dlRel + 6 + fic * 2);
|
||||
}
|
||||
/** 各 LangSys 表 */
|
||||
for (let li = 0; li < langSysCount; li++) {
|
||||
const lsRel = r.u16(scriptAbs + 4 + li * 6 + 4);
|
||||
const lsAbs = scriptAbs + lsRel;
|
||||
const fic = r.u16(lsAbs + 4);
|
||||
span = Math.max(span, scriptRel + lsRel + 6 + fic * 2);
|
||||
}
|
||||
}
|
||||
return r.errorFlag ? -1 : span;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算 FeatureList 的连续字节跨度(相对 listAbs 的字节数),用于判断能否整块原样拷贝。
|
||||
*
|
||||
* FeatureList 不含 glyphId,FeatureTable 偏移相对 listAbs。fontTools 常对内容相同的
|
||||
* FeatureTable 去重(多个 FeatureRecord 指向同一 FeatureTable,如初夏 GPOS 296 个 feature
|
||||
* 共享 1 个 FeatureTable)。serializeFeatureList 逐 feature 重写会丢失去重(296 份独立拷贝,
|
||||
* 1840→3728B)。若 FeatureTable 紧凑排列在 [listAbs, listAbs+span) 内无交错,整块拷贝
|
||||
* 既跳过逐字段序列化、又保留去重。
|
||||
*
|
||||
* @returns FeatureList 字节跨度(≥0),解析异常返回 -1
|
||||
*/
|
||||
export function featureListSpan(r: OTReader, listAbs: number): number {
|
||||
r.clearError();
|
||||
const featureCount = r.u16(listAbs);
|
||||
let span = 2 + featureCount * 6;
|
||||
for (let i = 0; i < featureCount; i++) {
|
||||
/** FeatureRecord 偏移相对 listAbs */
|
||||
const ftRel = r.u16(listAbs + 2 + i * 6 + 4);
|
||||
const ftAbs = listAbs + ftRel;
|
||||
const lookupIndexCount = r.u16(ftAbs + 2);
|
||||
/** FeatureTable: featureParamsOff(2) + lookupIndexCount(2) + indices */
|
||||
span = Math.max(span, ftRel + 4 + lookupIndexCount * 2);
|
||||
}
|
||||
return r.errorFlag ? -1 : span;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新序列化 ScriptList(GPOS/GSUB 通用,结构完全相同)
|
||||
*
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user