mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 16:25:49 +08:00
340 lines
11 KiB
Go
340 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 (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"slices"
|
|
)
|
|
|
|
// compactCFFGlyphs 重排已裁剪字体的字形,同步字符集、编码和字体字典归属
|
|
// 入参: data 已裁剪的CFF数据, keep 保留的原字形编号
|
|
// 返回: []byte 紧凑CFF数据, []uint16 新编号对应的原编号, error 结构或编码错误
|
|
func compactCFFGlyphs(data []byte, keep map[uint16]bool) ([]byte, []uint16, error) {
|
|
ids := []uint16{0}
|
|
for gid, used := range keep {
|
|
if used && gid != 0 {
|
|
ids = append(ids, gid)
|
|
}
|
|
}
|
|
slices.Sort(ids)
|
|
font, err := readType2Font(data)
|
|
if err == nil {
|
|
sids, _, _, _, ok := getCFFCharsetInfo(data, len(font.chars))
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("invalid CFF charset")
|
|
}
|
|
_, end, _ := readType2Index(data, int(data[2]))
|
|
tops, _, _ := readType2Index(data, end)
|
|
dict, _ := readCFFDict(tops[0])
|
|
encoding, err := cffSubsetEncoding(data, dict)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if encoding != nil {
|
|
codes, err := readCFFSubsetEncoding(encoding, sids)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
ordered := []uint16{0}
|
|
seen := map[uint16]bool{0: true}
|
|
for _, gid := range codes {
|
|
if keep[gid] && !seen[gid] {
|
|
ordered = append(ordered, gid)
|
|
seen[gid] = true
|
|
}
|
|
}
|
|
for _, gid := range ids {
|
|
if !seen[gid] {
|
|
ordered = append(ordered, gid)
|
|
}
|
|
}
|
|
ids = ordered
|
|
}
|
|
chars := make([][]byte, len(ids))
|
|
for index, gid := range ids {
|
|
if int(gid) >= len(font.chars) {
|
|
return nil, nil, fmt.Errorf("CFF glyph index out of range")
|
|
}
|
|
chars[index] = font.chars[gid]
|
|
}
|
|
result, err := rebuildNamedCFF(data, font, chars, ids)
|
|
return result, ids, err
|
|
}
|
|
cid, err := readCIDSubsetFont(data)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
cids, _, _, _, ok := getCFFCharsetInfo(data, len(cid.selects))
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("invalid CID CFF charset")
|
|
}
|
|
chars := make([][]byte, len(ids))
|
|
selected := make([]int, len(ids))
|
|
selects := make([]int, len(ids))
|
|
dicts, private, fonts := []cffDict{}, []cffDict{}, []*type2Font{}
|
|
remap := make(map[int]int)
|
|
for index, gid := range ids {
|
|
if int(gid) >= len(cid.selects) {
|
|
return nil, nil, fmt.Errorf("CFF glyph index out of range")
|
|
}
|
|
fd := cid.selects[gid]
|
|
mapped, exists := remap[fd]
|
|
if !exists {
|
|
mapped = len(fonts)
|
|
remap[fd] = mapped
|
|
dicts = append(dicts, cid.dicts[fd])
|
|
private = append(private, cid.private[fd])
|
|
fonts = append(fonts, cid.fonts[fd])
|
|
}
|
|
chars[index], selected[index], selects[index] = cid.fonts[fd].chars[gid], cids[gid], mapped
|
|
}
|
|
cid.dicts, cid.private, cid.fonts = dicts, private, fonts
|
|
cid.charset, cid.selects = encodeCFFSubsetCharset(selected), selects
|
|
result, err := cid.write(chars)
|
|
return result, ids, err
|
|
}
|
|
|
|
// compactCFFTables 重建按字形编号索引的度量和字符映射,不保留旧编号的布局表
|
|
// 入参: font 原字体, cff 紧凑CFF数据, ids 新编号对应的原编号
|
|
// 返回: []byte 独立OpenType字体, error 封装错误
|
|
func compactCFFTables(font *SFNT, cff []byte, ids []uint16) ([]byte, error) {
|
|
tables := make(map[string][]byte)
|
|
for _, tag := range []string{"head", "hhea", "maxp", "OS/2", "post", "name", "vhea", "gasp"} {
|
|
if data := font.Tables[tag]; data != nil {
|
|
tables[tag] = bytes.Clone(data)
|
|
}
|
|
}
|
|
var hmtx, vmtx []byte
|
|
mapping := make(map[rune]uint16)
|
|
for index, gid := range ids {
|
|
hmtx = binary.BigEndian.AppendUint16(hmtx, font.GlyphAdvance(gid))
|
|
hmtx = binary.BigEndian.AppendUint16(hmtx, uint16(font.sideBearing(gid, false)))
|
|
if font.vertical != 0 {
|
|
vmtx = binary.BigEndian.AppendUint16(vmtx, font.GlyphVerticalAdvance(gid))
|
|
vmtx = binary.BigEndian.AppendUint16(vmtx, uint16(font.sideBearing(gid, true)))
|
|
}
|
|
for _, char := range font.glyphCharacters(gid) {
|
|
mapping[char] = uint16(index)
|
|
}
|
|
}
|
|
tables["CFF "], tables["hmtx"] = cff, hmtx
|
|
tables["cmap"] = BuildCmapTable(uint16(len(ids)), mapping)
|
|
binary.BigEndian.PutUint16(tables["hhea"][34:], uint16(len(ids)))
|
|
binary.BigEndian.PutUint16(tables["maxp"][4:], uint16(len(ids)))
|
|
if len(tables["post"]) >= 32 {
|
|
tables["post"] = tables["post"][:32]
|
|
binary.BigEndian.PutUint32(tables["post"], 0x30000)
|
|
}
|
|
if font.vertical != 0 {
|
|
tables["vmtx"] = vmtx
|
|
binary.BigEndian.PutUint16(tables["vhea"][34:], uint16(len(ids)))
|
|
}
|
|
if data := font.Tables["VORG"]; data != nil {
|
|
var err error
|
|
tables["VORG"], err = compactCFFVerticalOrigins(data, font.NumGlyphs(), ids)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return SerializeOTF(tables)
|
|
}
|
|
|
|
// compactCFFVerticalOrigins 重排CFF字体的逐字形纵向原点,保留默认原点
|
|
// 入参: data VORG表, count 原字形数, ids 新编号对应的原编号
|
|
// 返回: []byte 重建的VORG表, error 表结构或记录顺序错误
|
|
func compactCFFVerticalOrigins(data []byte, count uint16, ids []uint16) ([]byte, error) {
|
|
if len(data) < 8 || binary.BigEndian.Uint32(data) != 0x10000 {
|
|
return nil, fmt.Errorf("invalid CFF vertical origin header")
|
|
}
|
|
length := int(binary.BigEndian.Uint16(data[6:]))
|
|
if length > (len(data)-8)/4 {
|
|
return nil, fmt.Errorf("truncated CFF vertical origins")
|
|
}
|
|
origins := make(map[uint16]uint16, length)
|
|
previous := -1
|
|
for index := range length {
|
|
pos := 8 + index*4
|
|
gid := binary.BigEndian.Uint16(data[pos:])
|
|
if gid >= count || int(gid) <= previous {
|
|
return nil, fmt.Errorf("invalid CFF vertical origin glyph")
|
|
}
|
|
origins[gid] = binary.BigEndian.Uint16(data[pos+2:])
|
|
previous = int(gid)
|
|
}
|
|
result := bytes.Clone(data[:8])
|
|
for index, gid := range ids {
|
|
if origin, ok := origins[gid]; ok {
|
|
result = binary.BigEndian.AppendUint16(result, uint16(index))
|
|
result = binary.BigEndian.AppendUint16(result, origin)
|
|
}
|
|
}
|
|
binary.BigEndian.PutUint16(result[6:], uint16((len(result)-8)/4))
|
|
return result, nil
|
|
}
|
|
|
|
// encodeCFFSubsetCharset 选择最短的标准字符集格式,保持SID或CID及字形顺序
|
|
// 入参: ids 按字形编号排列的SID或CID,包含.notdef
|
|
// 返回: []byte 字符集数据
|
|
func encodeCFFSubsetCharset(ids []int) []byte {
|
|
sizes := [3]int{1 + 2*(len(ids)-1), 1, 1}
|
|
for first := 1; first < len(ids); {
|
|
end := first + 1
|
|
for end < len(ids) && ids[end] == ids[end-1]+1 {
|
|
end++
|
|
}
|
|
sizes[1] += 3 * ((end - first + 255) / 256)
|
|
sizes[2] += 4
|
|
first = end
|
|
}
|
|
format := 0
|
|
for index, size := range sizes {
|
|
if size < sizes[format] {
|
|
format = index
|
|
}
|
|
}
|
|
result := make([]byte, 1, sizes[format])
|
|
result[0] = byte(format)
|
|
for first := 1; first < len(ids); {
|
|
end := first + 1
|
|
if format != 0 {
|
|
limit := len(ids)
|
|
if format == 1 {
|
|
limit = min(limit, first+256)
|
|
}
|
|
for end < limit && ids[end] == ids[end-1]+1 {
|
|
end++
|
|
}
|
|
}
|
|
result = binary.BigEndian.AppendUint16(result, uint16(ids[first]))
|
|
if format == 1 {
|
|
result = append(result, byte(end-first-1))
|
|
} else if format == 2 {
|
|
result = binary.BigEndian.AppendUint16(result, uint16(end-first-1))
|
|
}
|
|
first = end
|
|
}
|
|
return result
|
|
}
|
|
|
|
// readCFFSubsetEncoding 解析自定义编码及补充映射,校验重复编码和字形归属
|
|
// 入参: data 编码数据, sids 原字符集
|
|
// 返回: [256]uint16 编码对应的字形编号, error 编码结构错误
|
|
func readCFFSubsetEncoding(data []byte, sids []int) ([256]uint16, error) {
|
|
var codes [256]uint16
|
|
if len(data) < 2 {
|
|
return codes, fmt.Errorf("truncated CFF encoding")
|
|
}
|
|
pos, gid := 2, 1
|
|
for index := 0; index < int(data[1]); index++ {
|
|
if pos >= len(data) {
|
|
return codes, fmt.Errorf("truncated CFF encoding")
|
|
}
|
|
first, count := int(data[pos]), 1
|
|
pos++
|
|
switch data[0] & 0x7f {
|
|
case 0:
|
|
case 1:
|
|
if pos >= len(data) {
|
|
return codes, fmt.Errorf("truncated CFF encoding range")
|
|
}
|
|
count += int(data[pos])
|
|
pos++
|
|
default:
|
|
return codes, fmt.Errorf("invalid CFF encoding format")
|
|
}
|
|
if first+count > len(codes) || gid+count > len(sids) {
|
|
return codes, fmt.Errorf("CFF encoding range out of bounds")
|
|
}
|
|
for code := first; code < first+count; code++ {
|
|
if codes[code] != 0 {
|
|
return codes, fmt.Errorf("duplicate CFF encoding")
|
|
}
|
|
codes[code] = uint16(gid)
|
|
gid++
|
|
}
|
|
}
|
|
if data[0]&0x80 != 0 {
|
|
if pos >= len(data) {
|
|
return codes, fmt.Errorf("truncated CFF encoding supplements")
|
|
}
|
|
count := int(data[pos])
|
|
pos++
|
|
if count > (len(data)-pos)/3 {
|
|
return codes, fmt.Errorf("truncated CFF encoding supplements")
|
|
}
|
|
for range count {
|
|
code, sid := data[pos], int(binary.BigEndian.Uint16(data[pos+1:]))
|
|
pos += 3
|
|
mapped := slices.Index(sids[:gid], sid)
|
|
if codes[code] != 0 || mapped <= 0 {
|
|
return codes, fmt.Errorf("invalid CFF supplemental glyph")
|
|
}
|
|
codes[code] = uint16(mapped)
|
|
}
|
|
}
|
|
if data[0]&0x7f > 1 || pos != len(data) {
|
|
return codes, fmt.Errorf("invalid CFF encoding length or format")
|
|
}
|
|
return codes, nil
|
|
}
|
|
|
|
// encodeCFFSubsetEncoding 按新字形顺序重建主编码及同字形的补充编码
|
|
// 入参: codes 原编码映射, sids 原字符集, ids 新编号对应的原编号
|
|
// 返回: []byte 编码数据, error 字形顺序不满足编码约束
|
|
func encodeCFFSubsetEncoding(codes [256]uint16, sids []int, ids []uint16) ([]byte, error) {
|
|
var base, supplements []byte
|
|
encodings := make(map[uint16][]byte)
|
|
for code, gid := range codes {
|
|
if gid != 0 {
|
|
encodings[gid] = append(encodings[gid], byte(code))
|
|
}
|
|
}
|
|
unencoded := false
|
|
for _, gid := range ids[1:] {
|
|
values := encodings[gid]
|
|
if len(values) == 0 {
|
|
unencoded = true
|
|
} else if unencoded {
|
|
return nil, fmt.Errorf("CFF encoded glyph follows unencoded glyph")
|
|
} else {
|
|
base = append(base, values[0])
|
|
for _, code := range values[1:] {
|
|
supplements = append(supplements, code)
|
|
supplements = binary.BigEndian.AppendUint16(supplements, uint16(sids[gid]))
|
|
}
|
|
}
|
|
}
|
|
var result []byte
|
|
if len(base) == 256 {
|
|
for index, code := range base {
|
|
if int(code) != index {
|
|
return nil, fmt.Errorf("CFF encoding exceeds format zero capacity")
|
|
}
|
|
}
|
|
result = []byte{1, 1, 0, 255}
|
|
} else {
|
|
result = append([]byte{0, byte(len(base))}, base...)
|
|
}
|
|
if len(supplements) != 0 {
|
|
result[0] |= 0x80
|
|
result = append(result, byte(len(supplements)/3))
|
|
result = append(result, supplements...)
|
|
}
|
|
return result, nil
|
|
}
|