mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 15:06:46 +08:00
147 lines
5.5 KiB
Go
147 lines
5.5 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"
|
|
)
|
|
|
|
// variableSFNTSubsettable 核对TrueType可变轴与字形变化表的基本结构
|
|
// 入参: sfnt 已解析字体
|
|
// 返回: bool 是否具备可裁剪的变化数据
|
|
func variableSFNTSubsettable(sfnt *SFNT) bool {
|
|
fvar, gvar := sfnt.Tables["fvar"], sfnt.Tables["gvar"]
|
|
if len(fvar) < 16 || len(gvar) < 20 || binary.BigEndian.Uint32(fvar) != 0x10000 || binary.BigEndian.Uint32(gvar) != 0x10000 {
|
|
return false
|
|
}
|
|
axes := int(binary.BigEndian.Uint16(fvar[8:]))
|
|
offset, size := int(binary.BigEndian.Uint16(fvar[4:])), int(binary.BigEndian.Uint16(fvar[10:]))
|
|
return axes > 0 && size >= 20 && offset >= 16 && offset <= len(fvar) && axes <= (len(fvar)-offset)/size &&
|
|
int(binary.BigEndian.Uint16(gvar[4:])) == axes && binary.BigEndian.Uint16(gvar[12:]) == sfnt.NumGlyphs()
|
|
}
|
|
|
|
// subsetVariableSFNTFont 保留字形编号及可变轴,仅裁剪未使用的轮廓和字形变化数据
|
|
// 入参: sfnt 已解析字体, glyphs 使用的字形
|
|
// 返回: []byte 字体子集, error 字形或变化表错误
|
|
func subsetVariableSFNTFont(sfnt *SFNT, glyphs []uint16) ([]byte, error) {
|
|
if !variableSFNTSubsettable(sfnt) {
|
|
return nil, fmt.Errorf("invalid TrueType variation tables")
|
|
}
|
|
usage := newUsage()
|
|
usage.Glyphs[0] = true
|
|
for _, gid := range glyphs {
|
|
if gid >= sfnt.NumGlyphs() {
|
|
return nil, fmt.Errorf("variable font glyph index out of range")
|
|
}
|
|
usage.Glyphs[gid] = true
|
|
for _, char := range sfnt.glyphCharacters(gid) {
|
|
usage.Characters[char] = true
|
|
}
|
|
}
|
|
dependencies, err := sfnt.glyphDependencies(slices.Collect(maps.Keys(usage.Glyphs))...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, dependency := range dependencies {
|
|
usage.Glyphs[dependency] = true
|
|
}
|
|
mapping := make(map[rune]uint16, len(usage.Characters))
|
|
for char := range usage.Characters {
|
|
mapping[char] = sfnt.GlyphIndex(char)
|
|
}
|
|
table := make(map[string][]byte)
|
|
for _, tag := range []string{"head", "hhea", "hmtx", "maxp", "OS/2", "post", "name", "glyf", "loca", "cvt ", "fpgm", "prep", "gasp", "vhea", "vmtx"} {
|
|
if data := sfnt.Tables[tag]; data != nil {
|
|
table[tag] = data
|
|
}
|
|
}
|
|
table["cmap"] = BuildCmapTable(sfnt.NumGlyphs(), mapping)
|
|
static, err := SerializeOTF(table)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if subset := SubsetSourceSFNT(static, usage); subset != nil {
|
|
table, err = FileTables(subset, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
table["gvar"], err = subsetGlyphVariations(sfnt.Tables["gvar"], usage.Glyphs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, tag := range []string{"fvar", "avar", "cvar", "HVAR", "VVAR", "MVAR", "STAT"} {
|
|
if data := sfnt.Tables[tag]; data != nil {
|
|
table[tag] = data
|
|
}
|
|
}
|
|
return SerializeOTF(table)
|
|
}
|
|
|
|
// subsetGlyphVariations 按原字形编号重建gvar偏移表,保留共享元组及使用字形的完整变化数据
|
|
// 入参: data gvar表, glyphs 保留字形集合
|
|
// 返回: []byte 裁剪后的gvar表, error 表结构错误
|
|
func subsetGlyphVariations(data []byte, glyphs map[uint16]bool) ([]byte, error) {
|
|
if len(data) < 20 || binary.BigEndian.Uint32(data) != 0x10000 {
|
|
return nil, fmt.Errorf("invalid gvar header")
|
|
}
|
|
axes := uint64(binary.BigEndian.Uint16(data[4:]))
|
|
tuples := uint64(binary.BigEndian.Uint16(data[6:]))
|
|
shared := uint64(binary.BigEndian.Uint32(data[8:]))
|
|
count := int(binary.BigEndian.Uint16(data[12:]))
|
|
flags := binary.BigEndian.Uint16(data[14:])
|
|
start := uint64(binary.BigEndian.Uint32(data[16:]))
|
|
width := 2
|
|
if flags&1 != 0 {
|
|
width = 4
|
|
}
|
|
sharedSize := axes * tuples * 2
|
|
headerEnd := uint64(20 + (count+1)*width)
|
|
if flags > 1 || headerEnd > uint64(len(data)) || start < headerEnd || start > uint64(len(data)) || shared > uint64(len(data)) || sharedSize > uint64(len(data))-shared || sharedSize != 0 && shared < headerEnd {
|
|
return nil, fmt.Errorf("invalid gvar offsets")
|
|
}
|
|
readOffset := func(index int) uint64 {
|
|
if width == 2 {
|
|
return uint64(binary.BigEndian.Uint16(data[20+index*2:])) * 2
|
|
}
|
|
return uint64(binary.BigEndian.Uint32(data[20+index*4:]))
|
|
}
|
|
output := make([]byte, 20+(count+1)*4)
|
|
copy(output, data[:20])
|
|
binary.BigEndian.PutUint16(output[14:], 1)
|
|
binary.BigEndian.PutUint32(output[8:], uint32(len(output)))
|
|
output = append(output, data[shared:shared+sharedSize]...)
|
|
base := len(output)
|
|
binary.BigEndian.PutUint32(output[16:], uint32(base))
|
|
for gid := range count {
|
|
first, last := readOffset(gid), readOffset(gid+1)
|
|
if last < first || last > uint64(len(data))-start {
|
|
return nil, fmt.Errorf("invalid gvar glyph offsets")
|
|
}
|
|
if last > first && sharedSize != 0 && start+first < shared+sharedSize && shared < start+last {
|
|
return nil, fmt.Errorf("overlapping gvar glyph and shared tuple data")
|
|
}
|
|
binary.BigEndian.PutUint32(output[20+gid*4:], uint32(len(output)-base))
|
|
if glyphs[uint16(gid)] {
|
|
output = append(output, data[start+first:start+last]...)
|
|
}
|
|
}
|
|
binary.BigEndian.PutUint32(output[20+count*4:], uint32(len(output)-base))
|
|
return output, nil
|
|
}
|