mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 08:46:55 +08:00
346 lines
12 KiB
Go
346 lines
12 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 (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"math"
|
|
"sort"
|
|
)
|
|
|
|
// finite 判断数值是否有限
|
|
// 入参: value 数值
|
|
// 返回: bool 是否有限
|
|
func finite(value float64) bool {
|
|
return !math.IsNaN(value) && !math.IsInf(value, 0)
|
|
}
|
|
|
|
// ExtractCollectionFont 提取集合中的单个OpenType字体,保留表内容并重新计算校验和
|
|
// 入参: data 集合数据, index 字体索引
|
|
// 返回: []byte 单字体数据, error 错误信息
|
|
func ExtractCollectionFont(data []byte, index int) ([]byte, error) {
|
|
tables, err := FileTables(data, index)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(tables["head"]) < 12 {
|
|
return nil, fmt.Errorf("invalid head font table")
|
|
}
|
|
return SerializeOTF(tables)
|
|
}
|
|
|
|
// FileTables 读取单字体或集合指定项的表目录,不复制表数据
|
|
// 入参: data 字体数据, index 字体索引
|
|
// 返回: map[string][]byte 字体表, error 错误信息
|
|
func FileTables(data []byte, index int) (map[string][]byte, error) {
|
|
count, err := FileCount(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if index < 0 || index >= count {
|
|
return nil, fmt.Errorf("invalid font index %d", index)
|
|
}
|
|
var offset uint64
|
|
if string(data[:4]) == "ttcf" {
|
|
offset = uint64(binary.BigEndian.Uint32(data[12+index*4:]))
|
|
}
|
|
if offset > uint64(len(data)-12) {
|
|
return nil, fmt.Errorf("invalid font offset")
|
|
}
|
|
header := data[offset : offset+12]
|
|
if string(header[:4]) != "\x00\x01\x00\x00" && string(header[:4]) != "OTTO" && string(header[:4]) != "true" {
|
|
return nil, fmt.Errorf("invalid OpenType header")
|
|
}
|
|
tableCount := uint64(binary.BigEndian.Uint16(header[4:]))
|
|
offset += 12
|
|
if tableCount == 0 || tableCount > (uint64(len(data))-offset)/16 {
|
|
return nil, fmt.Errorf("invalid font table directory")
|
|
}
|
|
tables := make(map[string][]byte, tableCount)
|
|
for i := uint64(0); i < tableCount; i++ {
|
|
entry := data[offset+i*16 : offset+(i+1)*16]
|
|
tag := string(entry[:4])
|
|
start := uint64(binary.BigEndian.Uint32(entry[8:]))
|
|
length := uint64(binary.BigEndian.Uint32(entry[12:]))
|
|
if start > uint64(len(data)) || length > uint64(len(data))-start {
|
|
return nil, fmt.Errorf("invalid %s font table range", tag)
|
|
}
|
|
if _, exists := tables[tag]; exists {
|
|
return nil, fmt.Errorf("duplicate %s font table", tag)
|
|
}
|
|
tables[tag] = data[start : start+length]
|
|
}
|
|
return tables, nil
|
|
}
|
|
|
|
// calcTableChecksum 计算字体表校验和
|
|
// 入参: data 表数据
|
|
// 返回: uint32 校验和
|
|
func calcTableChecksum(data []byte) uint32 {
|
|
var sum uint32
|
|
length := len(data)
|
|
for i := 0; i < length; i += 4 {
|
|
if i+4 <= length {
|
|
sum += binary.BigEndian.Uint32(data[i : i+4])
|
|
} else {
|
|
var val uint32
|
|
rem := data[i:]
|
|
for j, b := range rem {
|
|
val |= uint32(b) << (24 - 8*j)
|
|
}
|
|
sum += val
|
|
}
|
|
}
|
|
return sum
|
|
}
|
|
|
|
// buildHeadTable 构建head表
|
|
// 入参: unitsPerEm 每em单位数
|
|
// 返回: []byte head表数据
|
|
func buildHeadTable(unitsPerEm uint16) []byte {
|
|
buf := new(bytes.Buffer)
|
|
binary.Write(buf, binary.BigEndian, uint16(1))
|
|
binary.Write(buf, binary.BigEndian, uint16(0))
|
|
binary.Write(buf, binary.BigEndian, uint32(0))
|
|
binary.Write(buf, binary.BigEndian, uint32(0))
|
|
binary.Write(buf, binary.BigEndian, uint32(0x5F0F3CF5))
|
|
binary.Write(buf, binary.BigEndian, uint16(0))
|
|
binary.Write(buf, binary.BigEndian, unitsPerEm)
|
|
binary.Write(buf, binary.BigEndian, int64(0))
|
|
binary.Write(buf, binary.BigEndian, int64(0))
|
|
binary.Write(buf, binary.BigEndian, int16(0))
|
|
binary.Write(buf, binary.BigEndian, int16(-500))
|
|
binary.Write(buf, binary.BigEndian, int16(1000))
|
|
binary.Write(buf, binary.BigEndian, int16(1000))
|
|
binary.Write(buf, binary.BigEndian, uint16(0))
|
|
binary.Write(buf, binary.BigEndian, uint16(0))
|
|
binary.Write(buf, binary.BigEndian, int16(2))
|
|
binary.Write(buf, binary.BigEndian, int16(0))
|
|
binary.Write(buf, binary.BigEndian, int16(0))
|
|
return buf.Bytes()
|
|
}
|
|
|
|
// buildHheaTable 构建hhea表
|
|
// 入参: numGlyphs 字形数量
|
|
// 返回: []byte hhea表数据
|
|
func buildHheaTable(numGlyphs uint16) []byte {
|
|
buf := new(bytes.Buffer)
|
|
binary.Write(buf, binary.BigEndian, uint16(1))
|
|
binary.Write(buf, binary.BigEndian, uint16(0))
|
|
binary.Write(buf, binary.BigEndian, int16(800))
|
|
binary.Write(buf, binary.BigEndian, int16(-200))
|
|
binary.Write(buf, binary.BigEndian, int16(0))
|
|
binary.Write(buf, binary.BigEndian, uint16(1000))
|
|
binary.Write(buf, binary.BigEndian, int16(0))
|
|
binary.Write(buf, binary.BigEndian, int16(0))
|
|
binary.Write(buf, binary.BigEndian, int16(1000))
|
|
binary.Write(buf, binary.BigEndian, int16(0))
|
|
binary.Write(buf, binary.BigEndian, int16(0))
|
|
binary.Write(buf, binary.BigEndian, int16(0))
|
|
binary.Write(buf, binary.BigEndian, int16(0))
|
|
binary.Write(buf, binary.BigEndian, int16(0))
|
|
binary.Write(buf, binary.BigEndian, int16(0))
|
|
binary.Write(buf, binary.BigEndian, int16(0))
|
|
binary.Write(buf, binary.BigEndian, int16(0))
|
|
binary.Write(buf, binary.BigEndian, uint16(numGlyphs))
|
|
return buf.Bytes()
|
|
}
|
|
|
|
// buildCFFMaxpTable 构建CFF轮廓使用的maxp 0.5表
|
|
// 入参: numGlyphs 字形数量
|
|
// 返回: []byte maxp表数据
|
|
func buildCFFMaxpTable(numGlyphs uint16) []byte {
|
|
buf := new(bytes.Buffer)
|
|
binary.Write(buf, binary.BigEndian, uint32(0x00005000))
|
|
binary.Write(buf, binary.BigEndian, uint16(numGlyphs))
|
|
return buf.Bytes()
|
|
}
|
|
|
|
// buildTrueTypeMaxpTable 构建TrueType轮廓使用的maxp 1.0表
|
|
// 入参: numGlyphs 字形数量
|
|
// 返回: []byte maxp表数据
|
|
func buildTrueTypeMaxpTable(numGlyphs uint16) []byte {
|
|
buf := new(bytes.Buffer)
|
|
binary.Write(buf, binary.BigEndian, uint32(0x00010000))
|
|
binary.Write(buf, binary.BigEndian, uint16(numGlyphs))
|
|
for i := 0; i < 13; i++ {
|
|
binary.Write(buf, binary.BigEndian, uint16(0))
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
// buildOS2Table 构建OS/2表 (使用默认Metrics)
|
|
// 返回: []byte OS/2表数据
|
|
func buildOS2Table() []byte {
|
|
return BuildOS2TableWithMetrics(800, -200)
|
|
}
|
|
|
|
// BuildOS2TableWithMetrics 构建OS/2表
|
|
// 入参: ascender 上升部, descender 下降部
|
|
// 返回: []byte OS/2表数据
|
|
func BuildOS2TableWithMetrics(ascender, descender int16) []byte {
|
|
os2 := new(bytes.Buffer)
|
|
binary.Write(os2, binary.BigEndian, uint16(3))
|
|
binary.Write(os2, binary.BigEndian, int16(500))
|
|
binary.Write(os2, binary.BigEndian, uint16(400))
|
|
binary.Write(os2, binary.BigEndian, uint16(5))
|
|
binary.Write(os2, binary.BigEndian, uint16(0))
|
|
binary.Write(os2, binary.BigEndian, int16(250))
|
|
binary.Write(os2, binary.BigEndian, int16(250))
|
|
binary.Write(os2, binary.BigEndian, int16(0))
|
|
binary.Write(os2, binary.BigEndian, int16(0))
|
|
binary.Write(os2, binary.BigEndian, int16(250))
|
|
binary.Write(os2, binary.BigEndian, int16(250))
|
|
binary.Write(os2, binary.BigEndian, int16(0))
|
|
binary.Write(os2, binary.BigEndian, int16(0))
|
|
binary.Write(os2, binary.BigEndian, int16(50))
|
|
binary.Write(os2, binary.BigEndian, int16(250))
|
|
binary.Write(os2, binary.BigEndian, int16(0))
|
|
os2.Write(make([]byte, 10))
|
|
binary.Write(os2, binary.BigEndian, uint32(0))
|
|
binary.Write(os2, binary.BigEndian, uint32(0))
|
|
binary.Write(os2, binary.BigEndian, uint32(0))
|
|
binary.Write(os2, binary.BigEndian, uint32(0))
|
|
os2.WriteString("PfEd")
|
|
binary.Write(os2, binary.BigEndian, uint16(0x0040))
|
|
binary.Write(os2, binary.BigEndian, uint16(0))
|
|
binary.Write(os2, binary.BigEndian, uint16(255))
|
|
binary.Write(os2, binary.BigEndian, ascender)
|
|
binary.Write(os2, binary.BigEndian, descender)
|
|
binary.Write(os2, binary.BigEndian, int16(0))
|
|
binary.Write(os2, binary.BigEndian, uint16(ascender))
|
|
if descender < 0 {
|
|
binary.Write(os2, binary.BigEndian, uint16(-descender))
|
|
} else {
|
|
binary.Write(os2, binary.BigEndian, uint16(descender))
|
|
}
|
|
binary.Write(os2, binary.BigEndian, uint32(0))
|
|
binary.Write(os2, binary.BigEndian, uint32(0))
|
|
binary.Write(os2, binary.BigEndian, int16(0))
|
|
binary.Write(os2, binary.BigEndian, int16(0))
|
|
binary.Write(os2, binary.BigEndian, uint16(0))
|
|
binary.Write(os2, binary.BigEndian, uint16(0))
|
|
binary.Write(os2, binary.BigEndian, uint16(0))
|
|
return os2.Bytes()
|
|
}
|
|
|
|
// buildNameTable 构建name表 (最小化)
|
|
// 返回: []byte name表数据
|
|
func buildNameTable() []byte {
|
|
buf := new(bytes.Buffer)
|
|
binary.Write(buf, binary.BigEndian, uint16(0))
|
|
binary.Write(buf, binary.BigEndian, uint16(0))
|
|
binary.Write(buf, binary.BigEndian, uint16(6))
|
|
return buf.Bytes()
|
|
}
|
|
|
|
// BuildPostTable 构建post表 (版本3.0, 无字形名称)
|
|
// 返回: []byte post表数据
|
|
func BuildPostTable() []byte {
|
|
buf := new(bytes.Buffer)
|
|
binary.Write(buf, binary.BigEndian, uint32(0x00030000))
|
|
binary.Write(buf, binary.BigEndian, uint32(0))
|
|
binary.Write(buf, binary.BigEndian, int16(0))
|
|
binary.Write(buf, binary.BigEndian, int16(0))
|
|
binary.Write(buf, binary.BigEndian, uint32(0))
|
|
binary.Write(buf, binary.BigEndian, uint32(0))
|
|
binary.Write(buf, binary.BigEndian, uint32(0))
|
|
binary.Write(buf, binary.BigEndian, uint32(0))
|
|
binary.Write(buf, binary.BigEndian, uint32(0))
|
|
return buf.Bytes()
|
|
}
|
|
|
|
// buildHmtxTable 构建hmtx表
|
|
// 入参: widths 宽度列表
|
|
// 返回: []byte hmtx表数据
|
|
func buildHmtxTable(widths []uint16) []byte {
|
|
buf := new(bytes.Buffer)
|
|
for _, w := range widths {
|
|
binary.Write(buf, binary.BigEndian, uint16(w))
|
|
binary.Write(buf, binary.BigEndian, int16(0))
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
// SerializeOTF 序列化OpenType字体结构,一次分配输出,不修改输入表数据
|
|
// 复用各表校验和计算整份字体的调整值,不重复扫描表数据
|
|
// 入参: tables 表数据映射
|
|
// 返回: []byte 完整字体数据, error 错误信息
|
|
func SerializeOTF(tables map[string][]byte) ([]byte, error) {
|
|
if len(tables) > math.MaxUint16/16 {
|
|
return nil, fmt.Errorf("too many font tables")
|
|
}
|
|
headerSize := 12 + 16*len(tables)
|
|
size := uint64(headerSize)
|
|
tags := make([]string, 0, len(tables))
|
|
for tag, data := range tables {
|
|
if len(tag) != 4 {
|
|
return nil, fmt.Errorf("invalid font table tag %q", tag)
|
|
}
|
|
if tag == "head" && len(data) < 12 {
|
|
return nil, fmt.Errorf("invalid head font table")
|
|
}
|
|
size += (uint64(len(data)) + 3) &^ 3
|
|
if size > math.MaxUint32 || size > uint64(math.MaxInt) {
|
|
return nil, fmt.Errorf("font table data too large")
|
|
}
|
|
tags = append(tags, tag)
|
|
}
|
|
sort.Strings(tags)
|
|
data := make([]byte, int(size))
|
|
numTables := len(tables)
|
|
entrySelector := 0
|
|
for 1<<(entrySelector+1) <= numTables {
|
|
entrySelector++
|
|
}
|
|
searchRange := 1 << (entrySelector + 4)
|
|
rangeShift := numTables*16 - searchRange
|
|
if tables["CFF "] != nil || tables["CFF2"] != nil {
|
|
copy(data, "OTTO")
|
|
} else {
|
|
binary.BigEndian.PutUint32(data, 0x00010000)
|
|
}
|
|
binary.BigEndian.PutUint16(data[4:], uint16(numTables))
|
|
binary.BigEndian.PutUint16(data[6:], uint16(searchRange))
|
|
binary.BigEndian.PutUint16(data[8:], uint16(entrySelector))
|
|
binary.BigEndian.PutUint16(data[10:], uint16(rangeShift))
|
|
offset, adjustment := headerSize, 0
|
|
var checksum uint32
|
|
for i, tag := range tags {
|
|
table := tables[tag]
|
|
length := (len(table) + 3) &^ 3
|
|
padded := data[offset : offset+length]
|
|
copy(padded, table)
|
|
if tag == "head" {
|
|
clear(padded[8:12])
|
|
adjustment = offset + 8
|
|
}
|
|
record := data[12+i*16 : 28+i*16]
|
|
copy(record, tag)
|
|
tableChecksum := calcTableChecksum(padded)
|
|
checksum += tableChecksum
|
|
binary.BigEndian.PutUint32(record[4:], tableChecksum)
|
|
binary.BigEndian.PutUint32(record[8:], uint32(offset))
|
|
binary.BigEndian.PutUint32(record[12:], uint32(len(table)))
|
|
offset += length
|
|
}
|
|
if adjustment != 0 {
|
|
binary.BigEndian.PutUint32(data[adjustment:], 0xB1B0AFBA-checksum-calcTableChecksum(data[:headerSize]))
|
|
}
|
|
return data, nil
|
|
}
|