feat(字体处理): 优化字体压缩,降低裁剪开销
build.yaml / build (push) Successful in 43s

This commit is contained in:
xiaoqidun committed 2026-10-11 01:36:50 +08:00
1 parent 62c81e7061
commit 226688bbdc
2 files changed
+33 -5

No files matched your search

+25 -4
View File
@@ -51,7 +51,7 @@ func subsetNamedCFFFont(sfnt *sfntFont, glyphs []uint16) ([]byte, error) {
return serializeOTF(tables) return serializeOTF(tables)
} }
// subsetCFFCharstrings 清空未引用的Type2轮廓,保留原字宽、提示和子程序 // subsetCFFCharstrings 清空未引用的Type2轮廓及子程序,保留原字宽、提示和编号
// 入参: data 非CID的CFF数据, keep 保留的字形,调用后包含组合组件 // 入参: data 非CID的CFF数据, keep 保留的字形,调用后包含组合组件
// 返回: []byte 重建的CFF数据, error 结构或依赖错误 // 返回: []byte 重建的CFF数据, error 结构或依赖错误
func subsetCFFCharstrings(data []byte, keep map[uint16]bool) ([]byte, error) { func subsetCFFCharstrings(data []byte, keep map[uint16]bool) ([]byte, error) {
@@ -60,12 +60,15 @@ func subsetCFFCharstrings(data []byte, keep map[uint16]bool) ([]byte, error) {
return nil, err return nil, err
} }
keep[0] = true keep[0] = true
locals := make([]bool, len(font.locals))
globals := make([]bool, len(font.globals))
var stack [48]float64
for gid := range keep { for gid := range keep {
if int(gid) >= len(font.chars) { if int(gid) >= len(font.chars) {
return nil, fmt.Errorf("CFF glyph index out of range") return nil, fmt.Errorf("CFF glyph index out of range")
} }
steps := 0 steps := 0
state := type2State{font: font, width: font.def, seed: font.seed + uint64(gid), steps: &steps, dependencies: keep} state := type2State{font: font, args: stack[:0], width: font.def, seed: font.seed + uint64(gid), steps: &steps, dependencies: keep, localUsage: locals, globalUsage: globals}
if _, err := state.run(font.chars[gid], 0); err != nil { if _, err := state.run(font.chars[gid], 0); err != nil {
return nil, err return nil, err
} }
@@ -78,7 +81,7 @@ func subsetCFFCharstrings(data []byte, keep map[uint16]bool) ([]byte, error) {
continue continue
} }
steps := 0 steps := 0
state := type2State{font: font, width: font.def, widthOnly: true, seed: font.seed + uint64(gid), steps: &steps, dependencies: keep} state := type2State{font: font, args: stack[:0], width: font.def, widthOnly: true, seed: font.seed + uint64(gid), steps: &steps, dependencies: keep}
if _, err := state.run(program, 0); err != nil { if _, err := state.run(program, 0); err != nil {
return nil, err return nil, err
} }
@@ -100,9 +103,27 @@ func subsetCFFCharstrings(data []byte, keep map[uint16]bool) ([]byte, error) {
chars[gid] = encoded.Bytes() chars[gid] = encoded.Bytes()
empty[state.width] = chars[gid] empty[state.width] = chars[gid]
} }
font.locals = subsetCFFSubroutines(font.locals, locals)
font.globals = subsetCFFSubroutines(font.globals, globals)
return rebuildNamedCFF(data, font, chars) return rebuildNamedCFF(data, font, chars)
} }
// subsetCFFSubroutines 将未引用的子程序替换为空返回,保持调用编号及偏移基数
// 入参: programs 子程序索引, used 引用标记
// 返回: [][]byte 裁剪后的子程序索引
func subsetCFFSubroutines(programs [][]byte, used []bool) [][]byte {
result := make([][]byte, len(programs))
empty := []byte{11}
for index, program := range programs {
if used[index] {
result[index] = program
} else {
result[index] = empty
}
}
return result
}
// rebuildNamedCFF 重建非CID的CFF偏移,保留字符集、编码和私有提示参数 // rebuildNamedCFF 重建非CID的CFF偏移,保留字符集、编码和私有提示参数
// 入参: data 原始CFF数据, font 执行资源, chars 替换后的字形程序 // 入参: data 原始CFF数据, font 执行资源, chars 替换后的字形程序
// 返回: []byte 重建的CFF数据, error 不支持的结构 // 返回: []byte 重建的CFF数据, error 不支持的结构
@@ -142,7 +163,7 @@ func rebuildNamedCFF(data []byte, font *type2Font, chars [][]byte) ([]byte, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
private, err := readType2Private(data, dict[18], font) private, err := readType2Private(data, dict[18], &type2Font{})
if err != nil { if err != nil {
return nil, err return nil, err
} }
+8 -1
View File
@@ -55,6 +55,8 @@ type type2State struct {
seed uint64 seed uint64
steps *int steps *int
dependencies map[uint16]bool dependencies map[uint16]bool
localUsage []bool
globalUsage []bool
} }
// readType2Index 读取并校验CFF索引的全部偏移,不复制程序字节 // readType2Index 读取并校验CFF索引的全部偏移,不复制程序字节
@@ -336,13 +338,18 @@ func (s *type2State) run(data []byte, depth int) (bool, error) {
value := s.args[len(s.args)-1] value := s.args[len(s.args)-1]
s.args = s.args[:len(s.args)-1] s.args = s.args[:len(s.args)-1]
subrs := s.font.locals subrs := s.font.locals
usage := s.localUsage
if op == 29 { if op == 29 {
subrs = s.font.globals subrs = s.font.globals
usage = s.globalUsage
} }
value += float64(cffSubrBias(len(subrs))) value += float64(cffSubrBias(len(subrs)))
if value < 0 || value >= float64(len(subrs)) || value != math.Trunc(value) { if value < 0 || value >= float64(len(subrs)) || value != math.Trunc(value) {
return false, fmt.Errorf("invalid Type2 subroutine number") return false, fmt.Errorf("invalid Type2 subroutine number")
} }
if usage != nil {
usage[int(value)] = true
}
ended, err := s.run(subrs[int(value)], depth+1) ended, err := s.run(subrs[int(value)], depth+1)
if ended || err != nil { if ended || err != nil {
return ended, err return ended, err
@@ -503,7 +510,7 @@ func (s *type2State) appendComponent(code, x, y float64) error {
if s.dependencies != nil { if s.dependencies != nil {
s.dependencies[uint16(gid)] = true s.dependencies[uint16(gid)] = true
} }
child := type2State{font: s.font, matrix: s.matrix, width: s.font.def, component: true, output: s.output, originX: x, originY: y, outX: s.outX, outY: s.outY, seed: s.font.seed + uint64(gid), steps: s.steps, stripHints: s.stripHints, dependencies: s.dependencies} child := type2State{font: s.font, matrix: s.matrix, width: s.font.def, component: true, output: s.output, originX: x, originY: y, outX: s.outX, outY: s.outY, seed: s.font.seed + uint64(gid), steps: s.steps, stripHints: s.stripHints, dependencies: s.dependencies, localUsage: s.localUsage, globalUsage: s.globalUsage}
_, err := child.run(s.font.chars[gid], 0) _, err := child.run(s.font.chars[gid], 0)
if err != nil { if err != nil {
return fmt.Errorf("invalid Type2 component %s: %w", name, err) return fmt.Errorf("invalid Type2 component %s: %w", name, err)