mirror of
https://github.com/2234839/web-font.git
synced 2026-09-04 23:02:27 +08:00
perf(gsub): serializeSingleSubst 大 coverage 反转遍历,思源 -63%
思源 GSUB 56 lookup 中 4 个非空 type1 lookup(locl 地区变体)的 coverage 极大(lookup#43 = 8881 gid,#0=729,#42=636),但子集仅命中个位数。 原 serializeSingleSubst 用 readCoverageGids 全量展开 coverage 再逐个查 gidLookup(8881 次几乎全未命中),占 subsetGSUB 84%(0.184ms/call)。 改为遍历子集原始 gid(currentSortedSubsetGids,仅 19 项)+ coverageIndexOf 在 coverage 中二分定位下标(format2 下标对应 substituteGlyphIDs 数组), O(subsetSize × log covCount) 替代 O(covCount)。与 reachable 侧 [[gsub-reachable-bigcov-byte-bsearch]] 同一类「展开全量消费极少」浪费。 仅当 covGidCount > subsetSize×4 且 >16 时启用反转,短 coverage(FiraCode 等)走原路径避免二分开销。导出 coverageIndexOf/coverageCount 供复用。 subsetGSUB 思源 min 0.204→0.075ms(-63%),otf-思源 woff2 avg 2.7→1.0ms。 输出逐字节一致(思源 GSUB 6560 bytes 差异 0),全字体 SSIM 不变。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
baa3a026ed
commit
db33685475
@ -145,7 +145,7 @@ function coverageFirstExcludedGid(
|
||||
* @param gid 待查 gid
|
||||
* @returns gid 在 coverage 中的序号;不在返回 -1
|
||||
*/
|
||||
function coverageIndexOf(r: OTReader, off: number, gid: number): number {
|
||||
export function coverageIndexOf(r: OTReader, off: number, gid: number): number {
|
||||
const dv = r.dv;
|
||||
const format = dv.getUint16(off, false);
|
||||
if (format === 1) {
|
||||
@ -195,7 +195,7 @@ function coverageIndexOf(r: OTReader, off: number, gid: number): number {
|
||||
* 反转遍历路径判断「coverage 是否明显多于 reachable」只需 gid 数,无需展开(思源 locl coverage
|
||||
* 达上万 gid,展开即 #1 热点)。format1 直接读 count;format2 累加各 range 的 (end-start+1)。
|
||||
*/
|
||||
function coverageCount(r: OTReader, off: number): number {
|
||||
export function coverageCount(r: OTReader, off: number): number {
|
||||
const dv = r.dv;
|
||||
const format = dv.getUint16(off, false);
|
||||
if (format === 1) {
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
*/
|
||||
|
||||
import { OTWriter as Writer, OTReader as Reader, serializeScriptList, serializeFeatureList, scriptListSpan, featureListSpan } from "./ot-bytes.js";
|
||||
import { coverageIndexOf, coverageCount } from "./gsub-reachable.js";
|
||||
|
||||
/** GSUB lookup 类型常量 */
|
||||
const LT_SINGLE = 1;
|
||||
@ -371,26 +372,62 @@ function serializeSingleSubst(
|
||||
const dv = r.dv;
|
||||
const format = r.u16(off);
|
||||
const covOff = off + r.u16(off + 2);
|
||||
const covGids = readCoverageGids(r, covOff);
|
||||
|
||||
/** 收集 (from新gid → to新gid) 有效项,子集外的剔除 */
|
||||
const entries: Array<{ from: number; to: number }> = [];
|
||||
if (format === 1) {
|
||||
const delta = r.i16(off + 4);
|
||||
for (const g of covGids) {
|
||||
const fromNew = gidLookup[g];
|
||||
const toNew = gidLookup[(g + delta) & 0xffff];
|
||||
if (fromNew >= 0 && toNew >= 0) entries.push({ from: fromNew, to: toNew });
|
||||
}
|
||||
} else if (format === 2) {
|
||||
const count = r.u16(off + 4);
|
||||
for (let i = 0; i < covGids.length && i < count; i++) {
|
||||
const fromNew = gidLookup[covGids[i]];
|
||||
const toNew = gidLookup[dv.getUint16(off + 6 + i * 2, false)];
|
||||
if (fromNew >= 0 && toNew >= 0) entries.push({ from: fromNew, to: toNew });
|
||||
|
||||
/** 反转遍历快路径:思源 locl 等 type1 lookup 的 coverage 可达上千~上万 gid(lookup#43 = 8881),
|
||||
* 但子集仅命中个位数。原实现 readCoverageGids 全量展开 coverage 再逐个查 gidLookup(8881 次,
|
||||
* 几乎全未命中)。改为遍历子集原始 gid(currentSortedSubsetGids,仅 19 项),用 coverageIndexOf
|
||||
* 在 coverage 中二分定位下标,命中才取 target——O(subsetSize × log covCount) 替代 O(covCount)。
|
||||
* 与 [[gsub-reachable-type1-fmt2-reverse-iter]] 同思路。仅当 coverage 明显多于子集时启用,
|
||||
* 否则短 coverage 原遍历更快(二分开销 > 跳过收益)。 */
|
||||
const subsetGids = currentSortedSubsetGids;
|
||||
const covGidCount = coverageCount(r, covOff);
|
||||
/** 阈值:coverage gid 数 / 子集 gid 数 > 4 且 coverage 较大时启用反转 */
|
||||
const useReverse = covGidCount > subsetGids.length * 4 && covGidCount > 16;
|
||||
|
||||
if (useReverse) {
|
||||
if (format === 1) {
|
||||
const delta = r.i16(off + 4);
|
||||
for (const g of subsetGids) {
|
||||
/** from gid 必须在 coverage 中(SingleSubst 仅对 coverage 内 gid 生效) */
|
||||
if (coverageIndexOf(r, covOff, g) < 0) continue;
|
||||
const fromNew = gidLookup[g];
|
||||
const toNew = gidLookup[(g + delta) & 0xffff];
|
||||
if (fromNew >= 0 && toNew >= 0) entries.push({ from: fromNew, to: toNew });
|
||||
}
|
||||
} else if (format === 2) {
|
||||
for (const g of subsetGids) {
|
||||
/** idx = gid 在 coverage 中的序号,与 substituteGlyphIDs 数组下标一一对应 */
|
||||
const idx = coverageIndexOf(r, covOff, g);
|
||||
if (idx < 0) continue;
|
||||
const fromNew = gidLookup[g];
|
||||
const toNew = gidLookup[dv.getUint16(off + 6 + idx * 2, false)];
|
||||
if (fromNew >= 0 && toNew >= 0) entries.push({ from: fromNew, to: toNew });
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
const covGids = readCoverageGids(r, covOff);
|
||||
if (format === 1) {
|
||||
const delta = r.i16(off + 4);
|
||||
for (const g of covGids) {
|
||||
const fromNew = gidLookup[g];
|
||||
const toNew = gidLookup[(g + delta) & 0xffff];
|
||||
if (fromNew >= 0 && toNew >= 0) entries.push({ from: fromNew, to: toNew });
|
||||
}
|
||||
} else if (format === 2) {
|
||||
const count = r.u16(off + 4);
|
||||
for (let i = 0; i < covGids.length && i < count; i++) {
|
||||
const fromNew = gidLookup[covGids[i]];
|
||||
const toNew = gidLookup[dv.getUint16(off + 6 + i * 2, false)];
|
||||
if (fromNew >= 0 && toNew >= 0) entries.push({ from: fromNew, to: toNew });
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (entries.length === 0) return false;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user