Files
ofdgo/ofdgo_font_util.go
T
xiaoqidun 77ffbe6a72
build.yaml / build (push) Successful in 39s
feat(后端架构): 完善后端解耦
2026-09-23 16:12:57 +08:00

738 lines
22 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 ofdgo
import (
"bytes"
"encoding/binary"
"fmt"
"sort"
)
// extractCollectionFont 提取集合中的单个OpenType字体,保留表内容并重新计算校验和
// 入参: data 集合数据, index 字体索引
// 返回: []byte 单字体数据, error 错误信息
func extractCollectionFont(data []byte, index int) ([]byte, error) {
tables, err := fontFileTables(data, index)
if err != nil {
return nil, err
}
if len(tables["head"]) < 12 {
return nil, fmt.Errorf("invalid head font table")
}
tables["head"] = bytes.Clone(tables["head"])
clear(tables["head"][8:12])
return serializeOTF(tables)
}
// fontFileTables 读取单字体或集合指定项的表目录,不复制表数据
// 入参: data 字体数据, index 字体索引
// 返回: map[string][]byte 字体表, error 错误信息
func fontFileTables(data []byte, index int) (map[string][]byte, error) {
count, err := fontFileCount(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
}
// packedGlyphRune 获取包装字体字符
// 入参: gid 字形ID
// 返回: rune 包装字体字符
func packedGlyphRune(gid uint16) rune {
return 0xF0000 + rune(gid)
}
// 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
}
// parseCmapMappings 解析cmap字符映射
// 入参: data cmap表数据
// 返回: map[rune]uint16 字符到字形映射
func parseCmapMappings(data []byte) map[rune]uint16 {
if len(data) < 4 {
return nil
}
numTables := int(binary.BigEndian.Uint16(data[2:4]))
result := make(map[rune]uint16)
for i := 0; i < numTables; i++ {
pos := 4 + i*8
if pos+8 > len(data) {
break
}
platformID := binary.BigEndian.Uint16(data[pos : pos+2])
if platformID != 0 && platformID != 3 {
continue
}
offset := int(binary.BigEndian.Uint32(data[pos+4 : pos+8]))
if offset+2 > len(data) {
continue
}
switch binary.BigEndian.Uint16(data[offset : offset+2]) {
case 0:
parseCmapFormat0(data[offset:], result)
case 4:
parseCmapFormat4(data[offset:], result)
case 6:
parseCmapFormat6(data[offset:], result)
case 12:
parseCmapFormat12(data[offset:], result)
}
}
if len(result) == 0 {
return nil
}
return result
}
// parseCmapFormat0 解析cmap format 0
// 入参: data 子表数据, result 字符映射
func parseCmapFormat0(data []byte, result map[rune]uint16) {
if len(data) < 262 {
return
}
for i := 0; i < 256; i++ {
gid := uint16(data[6+i])
if gid != 0 {
result[rune(i)] = gid
}
}
}
// parseCmapFormat4 解析cmap format 4
// 入参: data 子表数据, result 字符映射
func parseCmapFormat4(data []byte, result map[rune]uint16) {
if len(data) < 16 {
return
}
length := int(binary.BigEndian.Uint16(data[2:4]))
if length > len(data) {
length = len(data)
}
segCount := int(binary.BigEndian.Uint16(data[6:8]) / 2)
endPos := 14
startPos := endPos + segCount*2 + 2
deltaPos := startPos + segCount*2
rangePos := deltaPos + segCount*2
if rangePos+segCount*2 > length {
return
}
for i := 0; i < segCount; i++ {
end := binary.BigEndian.Uint16(data[endPos+i*2:])
start := binary.BigEndian.Uint16(data[startPos+i*2:])
delta := int16(binary.BigEndian.Uint16(data[deltaPos+i*2:]))
rangeOffsetPos := rangePos + i*2
rangeOffset := binary.BigEndian.Uint16(data[rangeOffsetPos:])
if start == 0xFFFF && end == 0xFFFF {
continue
}
for c := start; c <= end; c++ {
var gid uint16
if rangeOffset == 0 {
gid = uint16(int(c) + int(delta))
} else {
gidPos := rangeOffsetPos + int(rangeOffset) + int(c-start)*2
if gidPos+2 > length {
continue
}
gid = binary.BigEndian.Uint16(data[gidPos:])
if gid != 0 {
gid = uint16(int(gid) + int(delta))
}
}
if gid != 0 {
result[rune(c)] = gid
}
if c == 0xFFFF {
break
}
}
}
}
// parseCmapFormat6 解析cmap format 6
// 入参: data 子表数据, result 字符映射
func parseCmapFormat6(data []byte, result map[rune]uint16) {
if len(data) < 10 {
return
}
firstCode := int(binary.BigEndian.Uint16(data[6:8]))
entryCount := int(binary.BigEndian.Uint16(data[8:10]))
for i := 0; i < entryCount && 10+i*2+2 <= len(data); i++ {
gid := binary.BigEndian.Uint16(data[10+i*2:])
if gid != 0 {
result[rune(firstCode+i)] = gid
}
}
}
// parseCmapFormat12 解析cmap format 12
// 入参: data 子表数据, result 字符映射
func parseCmapFormat12(data []byte, result map[rune]uint16) {
if len(data) < 16 {
return
}
length := int(binary.BigEndian.Uint32(data[4:8]))
if length > len(data) {
length = len(data)
}
nGroups := int(binary.BigEndian.Uint32(data[12:16]))
for i := 0; i < nGroups; i++ {
pos := 16 + i*12
if pos+12 > length {
break
}
startChar := binary.BigEndian.Uint32(data[pos:])
endChar := binary.BigEndian.Uint32(data[pos+4:])
startGID := binary.BigEndian.Uint32(data[pos+8:])
for c := startChar; c <= endChar; c++ {
gid := startGID + c - startChar
if gid != 0 && gid <= 0xFFFF {
result[rune(c)] = uint16(gid)
}
if c == endChar {
break
}
}
}
}
// addPackedGlyphMapping 添加字形ID私有映射
// 入参: mapping 字符映射, numGlyphs 字形数量
func addPackedGlyphMapping(mapping map[rune]uint16, numGlyphs uint16) {
for i := uint16(0); i < numGlyphs; i++ {
mapping[packedGlyphRune(i)] = i
}
}
// 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()
}
// cmapSegment cmap表段结构
// 字段: start 开始字符, end 结束字符, delta 增量, offset 偏移
type cmapSegment struct {
start, end uint16
delta int16
offset uint16
}
// buildCmapTable 构建cmap表 (Format 4)
// 入参: numGlyphs 字形数量, mapping 字符映射
// 返回: []byte cmap表数据
func buildCmapTable(numGlyphs uint16, mapping map[rune]uint16) []byte {
if shouldBuildCmapFormat12(mapping) {
return buildCmapTableFormat12(numGlyphs, mapping)
}
var segs []cmapSegment
if mapping == nil {
end := uint16(0xFFFF)
if numGlyphs > 0 {
end = numGlyphs - 1
}
segs = append(segs, cmapSegment{start: 0, end: end, delta: 0, offset: 0})
} else {
var codes []int
for r := range mapping {
if r <= 0xFFFF {
codes = append(codes, int(r))
}
}
sort.Ints(codes)
if len(codes) > 0 {
start := codes[0]
prev := start
for i := 1; i < len(codes); i++ {
curr := codes[i]
if curr != prev+1 {
segs = append(segs, cmapSegment{start: uint16(start), end: uint16(prev), delta: 0, offset: 0})
start = curr
}
prev = curr
}
segs = append(segs, cmapSegment{start: uint16(start), end: uint16(prev), delta: 0, offset: 0})
}
}
segs = append(segs, cmapSegment{start: 0xFFFF, end: 0xFFFF, delta: 1, offset: 0})
segCount := uint16(len(segs))
searchRange := uint16(1)
entrySelector := uint16(0)
for searchRange*2 <= segCount {
searchRange *= 2
entrySelector++
}
searchRange *= 2
rangeShift := segCount*2 - searchRange
buf := new(bytes.Buffer)
binary.Write(buf, binary.BigEndian, uint16(4))
binary.Write(buf, binary.BigEndian, uint16(0))
binary.Write(buf, binary.BigEndian, uint16(0))
binary.Write(buf, binary.BigEndian, uint16(segCount*2))
binary.Write(buf, binary.BigEndian, searchRange)
binary.Write(buf, binary.BigEndian, entrySelector)
binary.Write(buf, binary.BigEndian, rangeShift)
var endCounts, startCounts, idDeltas, idRangeOffsets []uint16
var glyphIds []uint16
for _, s := range segs {
endCounts = append(endCounts, s.end)
startCounts = append(startCounts, s.start)
if mapping == nil {
idDeltas = append(idDeltas, 0)
idRangeOffsets = append(idRangeOffsets, 0)
continue
}
if s.start == 0xFFFF {
idDeltas = append(idDeltas, 1)
idRangeOffsets = append(idRangeOffsets, 0)
continue
}
idDeltas = append(idDeltas, 0)
currentGlyphIdx := len(glyphIds)
for c := int(s.start); c <= int(s.end); c++ {
gid := mapping[rune(c)]
glyphIds = append(glyphIds, gid)
}
offset := (int(segCount)-int(len(idRangeOffsets))-1)*2 + 2 + currentGlyphIdx*2
idRangeOffsets = append(idRangeOffsets, uint16(offset))
}
for _, v := range endCounts {
binary.Write(buf, binary.BigEndian, v)
}
binary.Write(buf, binary.BigEndian, uint16(0))
for _, v := range startCounts {
binary.Write(buf, binary.BigEndian, v)
}
for _, v := range idDeltas {
binary.Write(buf, binary.BigEndian, v)
}
for _, v := range idRangeOffsets {
binary.Write(buf, binary.BigEndian, v)
}
for _, v := range glyphIds {
binary.Write(buf, binary.BigEndian, v)
}
data := buf.Bytes()
binary.BigEndian.PutUint16(data[2:4], uint16(len(data)))
mainBuf := new(bytes.Buffer)
binary.Write(mainBuf, binary.BigEndian, uint16(0))
binary.Write(mainBuf, binary.BigEndian, uint16(1))
binary.Write(mainBuf, binary.BigEndian, uint16(3))
binary.Write(mainBuf, binary.BigEndian, uint16(1))
binary.Write(mainBuf, binary.BigEndian, uint32(12))
mainBuf.Write(data)
return mainBuf.Bytes()
}
// shouldBuildCmapFormat12 判断终止码、补充平面字符或format 4长度溢出时是否需要format 12
// 入参: mapping 字符到字形的映射
// 返回: bool 是否需要构建format 12子表
func shouldBuildCmapFormat12(mapping map[rune]uint16) bool {
if mapping == nil {
return false
}
var codes []int
for r := range mapping {
if r >= 0xFFFF {
return true
}
codes = append(codes, int(r))
}
if len(codes) == 0 {
return false
}
sort.Ints(codes)
segCount := 1
glyphIDCount := 1
prev := codes[0]
for i := 1; i < len(codes); i++ {
curr := codes[i]
if curr == prev {
continue
}
if curr != prev+1 {
segCount++
}
glyphIDCount++
prev = curr
}
segCount++
length := 16 + segCount*8 + glyphIDCount*2
return length > 0xFFFF
}
// buildCmapTableFormat12 构建cmap表 (Format 12)
// 入参: numGlyphs 字形数量, mapping 字符映射
// 返回: []byte cmap表数据
func buildCmapTableFormat12(numGlyphs uint16, mapping map[rune]uint16) []byte {
var codes []int
if mapping == nil {
for i := 0; i < int(numGlyphs); i++ {
codes = append(codes, i)
}
} else {
for r := range mapping {
if r >= 0 {
codes = append(codes, int(r))
}
}
}
sort.Ints(codes)
type cmapGroup struct {
startChar uint32
endChar uint32
startGID uint32
}
var groups []cmapGroup
for i := 0; i < len(codes); {
r := rune(codes[i])
gid := uint16(codes[i])
if mapping != nil {
gid = mapping[r]
}
group := cmapGroup{startChar: uint32(r), endChar: uint32(r), startGID: uint32(gid)}
prevRune := r
prevGID := gid
i++
for i < len(codes) {
nextRune := rune(codes[i])
nextGID := uint16(codes[i])
if mapping != nil {
nextGID = mapping[nextRune]
}
if nextRune != prevRune+1 || nextGID != prevGID+1 {
break
}
group.endChar = uint32(nextRune)
prevRune = nextRune
prevGID = nextGID
i++
}
groups = append(groups, group)
}
sub := new(bytes.Buffer)
binary.Write(sub, binary.BigEndian, uint16(12))
binary.Write(sub, binary.BigEndian, uint16(0))
binary.Write(sub, binary.BigEndian, uint32(16+12*len(groups)))
binary.Write(sub, binary.BigEndian, uint32(0))
binary.Write(sub, binary.BigEndian, uint32(len(groups)))
for _, group := range groups {
binary.Write(sub, binary.BigEndian, group.startChar)
binary.Write(sub, binary.BigEndian, group.endChar)
binary.Write(sub, binary.BigEndian, group.startGID)
}
mainBuf := new(bytes.Buffer)
binary.Write(mainBuf, binary.BigEndian, uint16(0))
binary.Write(mainBuf, binary.BigEndian, uint16(2))
binary.Write(mainBuf, binary.BigEndian, uint16(0))
binary.Write(mainBuf, binary.BigEndian, uint16(4))
binary.Write(mainBuf, binary.BigEndian, uint32(20))
binary.Write(mainBuf, binary.BigEndian, uint16(3))
binary.Write(mainBuf, binary.BigEndian, uint16(10))
binary.Write(mainBuf, binary.BigEndian, uint32(20))
mainBuf.Write(sub.Bytes())
return mainBuf.Bytes()
}
// otfTableRecord OTF表记录结构
// 字段: tag 标签, checksum 校验和, offset 偏移, length 长度, data 数据
type otfTableRecord struct {
tag string
checksum uint32
offset int
length int
data []byte
}
// serializeOTF 序列化OpenType字体结构
// 入参: tables 表数据映射
// 返回: []byte 完整字体数据, error 错误信息
func serializeOTF(tables map[string][]byte) ([]byte, error) {
numTables := uint16(len(tables))
entrySelector := 0
for 1<<(entrySelector+1) <= int(numTables) {
entrySelector++
}
searchRange := 1 << (entrySelector + 4)
rangeShift := int(numTables)*16 - searchRange
buf := new(bytes.Buffer)
if _, ok := tables["CFF "]; ok {
buf.WriteString("OTTO")
} else {
binary.Write(buf, binary.BigEndian, uint32(0x00010000))
}
binary.Write(buf, binary.BigEndian, numTables)
binary.Write(buf, binary.BigEndian, uint16(searchRange))
binary.Write(buf, binary.BigEndian, uint16(entrySelector))
binary.Write(buf, binary.BigEndian, uint16(rangeShift))
var tags []string
for t := range tables {
tags = append(tags, t)
}
sort.Strings(tags)
headerSize := 12 + 16*int(numTables)
offset := headerSize
var records []otfTableRecord
for _, tag := range tags {
data := tables[tag]
pad := (4 - (len(data) % 4)) % 4
padded := make([]byte, len(data)+pad)
copy(padded, data)
if tag == "head" && len(data) >= 12 {
clear(padded[8:12])
}
cs := calcTableChecksum(padded)
records = append(records, otfTableRecord{tag, cs, offset, len(data), padded})
offset += len(padded)
}
for _, r := range records {
buf.WriteString(r.tag)
binary.Write(buf, binary.BigEndian, r.checksum)
binary.Write(buf, binary.BigEndian, uint32(r.offset))
binary.Write(buf, binary.BigEndian, uint32(r.length))
}
for _, r := range records {
buf.Write(r.data)
}
fullData := buf.Bytes()
for _, r := range records {
if r.tag == "head" {
adjOffset := r.offset + 8
if adjOffset+4 <= len(fullData) {
binary.BigEndian.PutUint32(fullData[adjOffset:], 0)
cs := calcTableChecksum(fullData)
checksumAdj := 0xB1B0AFBA - cs
binary.BigEndian.PutUint32(fullData[adjOffset:], checksumAdj)
}
break
}
}
return fullData, nil
}