mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
1 parent
be5901100e
commit
c8a163a077
35 files changed
+3045
-373
No files matched your search
@@ -0,0 +1,299 @@
|
||||
// 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 ofdgo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// errTTInvalidLocations 表示字形索引不满足递增偏移要求
|
||||
var errTTInvalidLocations = errors.New("invalid TrueType glyph locations")
|
||||
|
||||
// normalizeTrueTypeLocations 验证物理字形边界后按原编号重排,不改变空字形和分量引用
|
||||
// 入参: tables 独占字体表目录,表内容只读
|
||||
// 返回: bool 是否重建索引, error 无法可靠恢复的索引或轮廓错误
|
||||
func normalizeTrueTypeLocations(tables map[string][]byte) (bool, error) {
|
||||
if _, ok := tables["glyf"]; !ok {
|
||||
return false, nil
|
||||
}
|
||||
head, maxp, loca, glyf := tables["head"], tables["maxp"], tables["loca"], tables["glyf"]
|
||||
if len(head) < 54 || len(maxp) < 6 {
|
||||
return false, fmt.Errorf("%w: missing header", errTTInvalidLocations)
|
||||
}
|
||||
count := int(binary.BigEndian.Uint16(maxp[4:]))
|
||||
width := 2
|
||||
switch binary.BigEndian.Uint16(head[50:]) {
|
||||
case 0:
|
||||
case 1:
|
||||
width = 4
|
||||
default:
|
||||
return false, fmt.Errorf("%w: format", errTTInvalidLocations)
|
||||
}
|
||||
if count == 0 || len(loca) < (count+1)*width {
|
||||
return false, fmt.Errorf("%w: truncated index", errTTInvalidLocations)
|
||||
}
|
||||
location := func(i int) uint64 {
|
||||
if width == 2 {
|
||||
return uint64(binary.BigEndian.Uint16(loca[i*2:])) * 2
|
||||
}
|
||||
return uint64(binary.BigEndian.Uint32(loca[i*4:]))
|
||||
}
|
||||
ordered, previous := true, uint64(0)
|
||||
for i := range count + 1 {
|
||||
offset := location(i)
|
||||
if offset > uint64(len(glyf)) {
|
||||
return false, fmt.Errorf("%w: offset %d outside table", errTTInvalidLocations, i)
|
||||
}
|
||||
ordered = ordered && previous <= offset
|
||||
previous = offset
|
||||
}
|
||||
if ordered {
|
||||
return false, nil
|
||||
}
|
||||
offsets := make([]int, count+1)
|
||||
for i := range offsets {
|
||||
offsets[i] = int(location(i))
|
||||
}
|
||||
physical := slices.Clone(offsets)
|
||||
physical = append(physical, len(glyf))
|
||||
slices.Sort(physical)
|
||||
physical = slices.Compact(physical)
|
||||
bodies := make(map[int][]byte, count)
|
||||
components := make([][]uint16, count)
|
||||
var total uint64
|
||||
for id := range count {
|
||||
start := offsets[id]
|
||||
if start == offsets[id+1] {
|
||||
continue
|
||||
}
|
||||
if _, duplicate := bodies[start]; duplicate {
|
||||
return false, fmt.Errorf("%w: ambiguous shared offset %d", errTTInvalidLocations, start)
|
||||
}
|
||||
index, _ := slices.BinarySearch(physical, start)
|
||||
if index+1 >= len(physical) {
|
||||
return false, fmt.Errorf("%w: missing glyph %d body", errTTInvalidLocations, id)
|
||||
}
|
||||
data := glyf[start:physical[index+1]]
|
||||
size, refs, err := trueTypeGlyphSize(data, count)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("%w: glyph %d: %w", errTTInvalidLocations, id, err)
|
||||
}
|
||||
if len(data)-size > 3 || len(bytes.Trim(data[size:], "\x00")) != 0 {
|
||||
return false, fmt.Errorf("%w: ambiguous glyph %d padding", errTTInvalidLocations, id)
|
||||
}
|
||||
bodies[start], components[id] = data[:size], refs
|
||||
total += (uint64(size) + 3) &^ 3
|
||||
if total > uint64(^uint(0)>>1) || total > uint64(^uint32(0)) {
|
||||
return false, fmt.Errorf("%w: rebuilt table exceeds capacity", errTTInvalidLocations)
|
||||
}
|
||||
}
|
||||
state := make([]uint8, count)
|
||||
var visit func(int) error
|
||||
visit = func(id int) error {
|
||||
if state[id] == 1 {
|
||||
return fmt.Errorf("%w: cyclic component %d", errTTInvalidLocations, id)
|
||||
}
|
||||
if state[id] == 2 {
|
||||
return nil
|
||||
}
|
||||
state[id] = 1
|
||||
for _, ref := range components[id] {
|
||||
if err := visit(int(ref)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
state[id] = 2
|
||||
return nil
|
||||
}
|
||||
for id := range count {
|
||||
if err := visit(id); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
result, locations := make([]byte, 0, total), make([]byte, (count+1)*4)
|
||||
for id := range count {
|
||||
binary.BigEndian.PutUint32(locations[id*4:], uint32(len(result)))
|
||||
if offsets[id] != offsets[id+1] {
|
||||
result = append(result, bodies[offsets[id]]...)
|
||||
result = append(result, make([]byte, (-len(result))&3)...)
|
||||
}
|
||||
}
|
||||
binary.BigEndian.PutUint32(locations[count*4:], uint32(len(result)))
|
||||
header := bytes.Clone(head)
|
||||
binary.BigEndian.PutUint16(header[50:], 1)
|
||||
tables["head"], tables["loca"], tables["glyf"] = header, locations, result
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// trueTypeGlyphSize 检查单个字形的完整记录长度及复合引用,不执行指令或分配坐标
|
||||
// 入参: data 字形及尾部对齐数据, count 字形总数
|
||||
// 返回: int 记录长度, []uint16 分量编号, error 结构错误
|
||||
func trueTypeGlyphSize(data []byte, count int) (int, []uint16, error) {
|
||||
if len(data) < 10 {
|
||||
return 0, nil, fmt.Errorf("truncated glyph header")
|
||||
}
|
||||
contours := int(int16(binary.BigEndian.Uint16(data)))
|
||||
pos := 10
|
||||
if contours >= 0 {
|
||||
if contours == 0 && len(data) == 10 {
|
||||
return 10, nil, nil
|
||||
}
|
||||
if contours > (len(data)-12)/2 || len(data) < 12 {
|
||||
return 0, nil, fmt.Errorf("truncated contour endpoints")
|
||||
}
|
||||
points := 0
|
||||
for range contours {
|
||||
end := int(binary.BigEndian.Uint16(data[pos:])) + 1
|
||||
if end <= points {
|
||||
return 0, nil, fmt.Errorf("unordered contour endpoints")
|
||||
}
|
||||
points, pos = end, pos+2
|
||||
}
|
||||
instructions := int(binary.BigEndian.Uint16(data[pos:]))
|
||||
pos += 2
|
||||
if instructions > len(data)-pos {
|
||||
return 0, nil, fmt.Errorf("truncated glyph instructions")
|
||||
}
|
||||
pos += instructions
|
||||
coordinates := 0
|
||||
for seen := 0; seen < points; {
|
||||
if pos >= len(data) {
|
||||
return 0, nil, fmt.Errorf("truncated glyph flags")
|
||||
}
|
||||
flag, repeat := data[pos], 1
|
||||
pos++
|
||||
if flag&0x80 != 0 {
|
||||
return 0, nil, fmt.Errorf("reserved glyph flag")
|
||||
}
|
||||
if flag&8 != 0 {
|
||||
if pos >= len(data) {
|
||||
return 0, nil, fmt.Errorf("truncated glyph repeat")
|
||||
}
|
||||
repeat += int(data[pos])
|
||||
pos++
|
||||
}
|
||||
if repeat > points-seen {
|
||||
return 0, nil, fmt.Errorf("excessive glyph repeat")
|
||||
}
|
||||
for axis := range 2 {
|
||||
if flag&byte(2<<axis) != 0 {
|
||||
coordinates += repeat
|
||||
} else if flag&byte(16<<axis) == 0 {
|
||||
coordinates += 2 * repeat
|
||||
}
|
||||
}
|
||||
seen += repeat
|
||||
}
|
||||
if coordinates > len(data)-pos {
|
||||
return 0, nil, fmt.Errorf("truncated glyph coordinates")
|
||||
}
|
||||
return pos + coordinates, nil, nil
|
||||
}
|
||||
var refs []uint16
|
||||
instructions := false
|
||||
for {
|
||||
if len(data)-pos < 6 {
|
||||
return 0, nil, fmt.Errorf("truncated glyph component")
|
||||
}
|
||||
flags, id := binary.BigEndian.Uint16(data[pos:]), binary.BigEndian.Uint16(data[pos+2:])
|
||||
if int(id) >= count || flags&0xe010 != 0 {
|
||||
return 0, nil, fmt.Errorf("invalid glyph component")
|
||||
}
|
||||
refs = append(refs, id)
|
||||
pos += 6
|
||||
if flags&1 != 0 {
|
||||
pos += 2
|
||||
}
|
||||
switch flags & 0xc8 {
|
||||
case 0:
|
||||
case 8:
|
||||
pos += 2
|
||||
case 0x40:
|
||||
pos += 4
|
||||
case 0x80:
|
||||
pos += 8
|
||||
default:
|
||||
return 0, nil, fmt.Errorf("conflicting component transforms")
|
||||
}
|
||||
if pos > len(data) {
|
||||
return 0, nil, fmt.Errorf("truncated component arguments")
|
||||
}
|
||||
instructions = instructions || flags&0x100 != 0
|
||||
if flags&0x20 == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if instructions {
|
||||
if len(data)-pos < 2 {
|
||||
return 0, nil, fmt.Errorf("truncated component instructions")
|
||||
}
|
||||
length := int(binary.BigEndian.Uint16(data[pos:]))
|
||||
pos += 2
|
||||
if length > len(data)-pos {
|
||||
return 0, nil, fmt.Errorf("truncated component instructions")
|
||||
}
|
||||
pos += length
|
||||
}
|
||||
return pos, refs, nil
|
||||
}
|
||||
|
||||
// trueTypePointCount 按完整轮廓计算虚拟点编号,局部复用分量计数,不执行字形指令
|
||||
// 入参: tables 字体表, id 字形编号, counts 本次计数缓存, active 递归访问集合
|
||||
// 返回: int 不含虚拟点的轮廓点数, error 结构或循环引用错误
|
||||
func trueTypePointCount(tables map[string][]byte, id uint16, counts map[uint16]int, active map[uint16]bool) (int, error) {
|
||||
if count, ok := counts[id]; ok {
|
||||
return count, nil
|
||||
}
|
||||
maxp := tables["maxp"]
|
||||
if len(maxp) < 6 || id >= binary.BigEndian.Uint16(maxp[4:]) || active[id] {
|
||||
return 0, fmt.Errorf("invalid TrueType component reference %d", id)
|
||||
}
|
||||
active[id] = true
|
||||
defer delete(active, id)
|
||||
data, err := trueTypeGlyphData(tables, id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
counts[id] = 0
|
||||
return 0, nil
|
||||
}
|
||||
_, refs, err := trueTypeGlyphSize(data, int(binary.BigEndian.Uint16(maxp[4:])))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
contours := int(int16(binary.BigEndian.Uint16(data)))
|
||||
count := 0
|
||||
if contours > 0 {
|
||||
count = int(binary.BigEndian.Uint16(data[10+2*(contours-1):])) + 1
|
||||
} else {
|
||||
for _, ref := range refs {
|
||||
points, err := trueTypePointCount(tables, ref, counts, active)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
count += points
|
||||
if count > 65536 {
|
||||
return 0, fmt.Errorf("excessive TrueType component points")
|
||||
}
|
||||
}
|
||||
}
|
||||
counts[id] = count
|
||||
return count, nil
|
||||
}
|
||||
Reference in new issue
Block a user