Files
ofdgo/internal/base/font/sfnt.go
T
xiaoqidun 0b1dc7e6c8
build.yaml / build (push) Successful in 54s
feat(字体图像): 优化字体图像性能
2026-10-15 21:58:12 +08:00

361 lines
11 KiB
Go

// Copyright 2025-2026 肖其顿 (XIAO QI DUN)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package font
import (
"encoding/binary"
"fmt"
"maps"
"slices"
"sync"
"github.com/go-text/typesetting/font/opentype/tables"
)
// SFNT 保存独立字体表和基础度量,不包含绘图库对象
type SFNT struct {
Tables map[string][]byte
IsTrueType, IsCFF bool
cmaps []sfntCmap
units, count, horizontal, vertical uint16
reverseOnce sync.Once
reverse map[uint16][]rune
}
// sfntCmap 保存编码记录及对应字符映射,标记已解析的共享子表
type sfntCmap struct {
platform, encoding, format uint16
mapping map[rune]uint16
duplicate bool
}
// ParseSFNT 校验独立字体的必需表、度量范围及字符映射
// 入参: data 独立SFNT数据
// 返回: *SFNT 字体, error 格式错误
func ParseSFNT(data []byte) (*SFNT, error) {
table, err := FileTables(data, 0)
if err != nil {
return nil, err
}
return ParseSFNTTables(table)
}
// ParseSFNTTables 校验字体表并解析度量和字符映射,借用表数据且不修改其内容
// 入参: table 字体表
// 返回: *SFNT 字体, error 格式错误
func ParseSFNTTables(table map[string][]byte) (*SFNT, error) {
head, maxp, hhea := table["head"], table["maxp"], table["hhea"]
if len(head) != 54 || len(maxp) < 6 || len(hhea) < 36 {
return nil, fmt.Errorf("missing or truncated font metrics")
}
f := &SFNT{Tables: maps.Clone(table), IsTrueType: table["glyf"] != nil, IsCFF: table["CFF "] != nil}
f.units, f.count = binary.BigEndian.Uint16(head[18:]), binary.BigEndian.Uint16(maxp[4:])
f.horizontal = binary.BigEndian.Uint16(hhea[34:])
if f.units < 16 || f.units > 16384 || f.count == 0 || f.IsTrueType == f.IsCFF || !validSFNTMetrics(table["hmtx"], f.count, f.horizontal) {
return nil, fmt.Errorf("invalid font outlines or horizontal metrics")
}
if vhea := table["vhea"]; vhea != nil {
if len(vhea) < 36 {
return nil, fmt.Errorf("truncated vertical metrics header")
}
f.vertical = binary.BigEndian.Uint16(vhea[34:])
if !validSFNTMetrics(table["vmtx"], f.count, f.vertical) {
return nil, fmt.Errorf("invalid vertical metrics")
}
}
if f.IsTrueType {
format := binary.BigEndian.Uint16(head[50:])
if format > 1 || len(table["loca"]) < (int(f.count)+1)*(2+2*int(format)) {
return nil, fmt.Errorf("invalid glyph location table")
}
}
cmaps, err := readSFNTCmaps(table["cmap"])
if err != nil {
return nil, err
}
f.cmaps = cmaps
for _, cmap := range f.cmaps {
if cmap.duplicate {
continue
}
for _, glyph := range cmap.mapping {
if glyph >= f.count {
return nil, fmt.Errorf("cmap glyph index out of range")
}
}
}
return f, nil
}
// validSFNTMetrics 校验完整度量记录和末尾边距数组的范围
// 入参: data 度量表, count 字形数, metrics 完整度量记录数
// 返回: bool 是否有效
func validSFNTMetrics(data []byte, count, metrics uint16) bool {
return metrics != 0 && metrics <= count && len(data) >= int(metrics)*4+int(count-metrics)*2
}
// readSFNTCmaps 校验各编码子表并保留原顺序,共享子表只解析一次
// 入参: data cmap表
// 返回: []sfntCmap 编码记录, error 表结构错误
func readSFNTCmaps(data []byte) ([]sfntCmap, error) {
data, err := NormalizeCmap(data)
if err != nil {
return nil, err
}
if _, _, err := tables.ParseCmap(data); err != nil {
return nil, err
}
count := int(binary.BigEndian.Uint16(data[2:]))
result := make([]sfntCmap, 0, count)
parsed := make(map[uint32]map[rune]uint16)
for i := range count {
record := data[4+8*i:]
offset := binary.BigEndian.Uint32(record[4:])
if uint64(offset)+2 > uint64(len(data)) {
return nil, fmt.Errorf("invalid cmap offset")
}
sub := data[offset:]
format := binary.BigEndian.Uint16(sub)
mapping, found := parsed[offset]
if !found {
mapping = make(map[rune]uint16)
var err error
switch format {
case 0:
parseCmapFormat0(sub, mapping)
case 4:
parseCmapFormat4(sub, mapping)
case 6:
parseCmapFormat6(sub, mapping)
case 12:
parseCmapFormat12(sub, mapping)
case 2:
mapping, err = parseCmapFormat2(sub)
case 8, 10, 13:
mapping, err = parseCmapExtended(sub)
case 14:
default:
return nil, fmt.Errorf("unsupported cmap format %d", format)
}
if err != nil {
return nil, err
}
parsed[offset] = mapping
}
result = append(result, sfntCmap{platform: binary.BigEndian.Uint16(record), encoding: binary.BigEndian.Uint16(record[2:]), format: format, mapping: mapping, duplicate: found})
}
return result, nil
}
// UnitsPerEm 返回字体设计单位
// 返回: uint16 每em单位数
func (f *SFNT) UnitsPerEm() uint16 { return f.units }
// NumGlyphs 返回字形总数
// 返回: uint16 字形数
func (f *SFNT) NumGlyphs() uint16 { return f.count }
// CmapEntries 返回各编码记录的映射项总数,共享子表按记录分别计数
// 返回: uint64 用于缓存预算的映射项数量
func (f *SFNT) CmapEntries() uint64 {
var count uint64
for _, cmap := range f.cmaps {
count += uint64(len(cmap.mapping))
}
return count
}
// GlyphIndex 按编码表顺序查找字符,不替换缺失字形
// 入参: char 字符
// 返回: uint16 字形编号
func (f *SFNT) GlyphIndex(char rune) uint16 {
for _, cmap := range f.cmaps {
if cmap.duplicate {
continue
}
if gid := cmap.mapping[char]; gid != 0 {
return gid
}
}
return 0
}
// glyphCharacters 返回指向指定字形的全部字符
// 入参: gid 字形编号
// 返回: []rune 排序后的字符
func (f *SFNT) glyphCharacters(gid uint16) []rune {
f.reverseOnce.Do(func() {
f.reverse = make(map[uint16][]rune)
seen := make(map[rune]bool)
for _, cmap := range f.cmaps {
if cmap.duplicate {
continue
}
for char, glyph := range cmap.mapping {
if glyph != 0 && !seen[char] {
f.reverse[glyph] = append(f.reverse[glyph], char)
seen[char] = true
}
}
}
for _, chars := range f.reverse {
slices.Sort(chars)
}
})
return f.reverse[gid]
}
// GlyphAdvance 返回水平字宽
// 入参: gid 字形编号
// 返回: uint16 字宽
func (f *SFNT) GlyphAdvance(gid uint16) uint16 {
if gid >= f.count {
return 0
}
return binary.BigEndian.Uint16(f.Tables["hmtx"][int(min(gid, f.horizontal-1))*4:])
}
// GlyphVerticalAdvance 返回纵向字距,无垂直度量时使用em高度
// 入参: gid 字形编号
// 返回: uint16 纵向字距
func (f *SFNT) GlyphVerticalAdvance(gid uint16) uint16 {
if f.vertical == 0 {
return f.units
}
if gid >= f.count {
return 0
}
return binary.BigEndian.Uint16(f.Tables["vmtx"][int(min(gid, f.vertical-1))*4:])
}
// sideBearing 返回指定方向的字形边距
// 入参: gid 字形编号, vertical 是否读取顶部边距
// 返回: int16 边距
func (f *SFNT) sideBearing(gid uint16, vertical bool) int16 {
data, count := f.Tables["hmtx"], f.horizontal
if vertical {
data, count = f.Tables["vmtx"], f.vertical
}
if count == 0 || gid >= f.count {
return 0
}
offset := int(gid)*4 + 2
if gid >= count {
offset = int(count)*4 + int(gid-count)*2
}
return int16(binary.BigEndian.Uint16(data[offset:]))
}
// VerticalMetrics 依据OS/2排版标志选择行度量,缺失时使用hhea
// 返回: uint16 上升高度, uint16 下降高度, uint16 行间隙
func (f *SFNT) VerticalMetrics() (uint16, uint16, uint16) {
hhea, os2 := f.Tables["hhea"], f.Tables["OS/2"]
a, d, gap := int(int16(binary.BigEndian.Uint16(hhea[4:]))), int(int16(binary.BigEndian.Uint16(hhea[6:]))), int(int16(binary.BigEndian.Uint16(hhea[8:])))
if len(os2) >= 78 {
if binary.BigEndian.Uint16(os2[62:])&0x80 != 0 {
ta, td := int(int16(binary.BigEndian.Uint16(os2[68:]))), int(int16(binary.BigEndian.Uint16(os2[70:])))
if ta > 0 && td < 0 {
a, d, gap = ta, td, int(int16(binary.BigEndian.Uint16(os2[72:])))
}
} else {
wa, wd := int(binary.BigEndian.Uint16(os2[74:])), int(binary.BigEndian.Uint16(os2[76:]))
if wa != 0 && wd != 0 {
gap, a, d = a-d+gap-wa-wd, wa, -wd
}
}
}
return uint16(max(0, a)), uint16(max(0, -d)), uint16(min(65535, max(0, gap)))
}
// Write 保留原始表数据并重新计算字体校验和
// 返回: []byte 独立字体
func (f *SFNT) Write() []byte { data, _ := SerializeOTF(f.Tables); return data }
// glyphDependencies 批量收集复合字形依赖,校验引用、记录长度和嵌套深度
// 入参: glyphs 字形编号
// 返回: []uint16 依赖字形, error 字形错误
func (f *SFNT) glyphDependencies(glyphs ...uint16) ([]uint16, error) {
heights, active := make(map[uint16]int), make(map[uint16]bool)
var result []uint16
var visit func(uint16) error
visit = func(id uint16) error {
if id >= f.count || active[id] || len(active) > 64 {
return fmt.Errorf("invalid composite glyph reference")
}
if height := heights[id]; height != 0 {
if len(active)+height > 65 {
return fmt.Errorf("invalid composite glyph reference")
}
return nil
}
heights[id], active[id] = 1, true
defer delete(active, id)
data, err := trueTypeGlyphData(f.Tables, id)
if err != nil {
return err
}
if len(data) == 0 {
return nil
}
if len(data) < 10 {
return fmt.Errorf("truncated glyph header")
}
if int16(binary.BigEndian.Uint16(data)) >= 0 {
return nil
}
for pos := 10; ; {
if len(data)-pos < 4 {
return fmt.Errorf("truncated composite glyph")
}
flags, child := binary.BigEndian.Uint16(data[pos:]), binary.BigEndian.Uint16(data[pos+2:])
pos += 6
if flags&1 != 0 {
pos += 2
}
switch flags & 0xc8 {
case 8:
pos += 2
case 0x40:
pos += 4
case 0x80:
pos += 8
case 0:
default:
return fmt.Errorf("invalid composite transform")
}
if pos > len(data) {
return fmt.Errorf("truncated composite transform")
}
if heights[child] == 0 {
result = append(result, child)
}
if err := visit(child); err != nil {
return err
}
heights[id] = max(heights[id], heights[child]+1)
if flags&0x20 == 0 {
break
}
}
return nil
}
for _, id := range glyphs {
if err := visit(id); err != nil {
return nil, err
}
}
return result, nil
}