Files
2026-10-14 00:12:00 +08:00

235 lines
7.9 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"
"maps"
"slices"
"unicode/utf8"
"github.com/go-text/typesetting/font/opentype/tables"
)
// subsetCmapVariations 裁剪Unicode变体映射,将非默认变体的字形加入保留集合
// 入参: data 字符映射表, chars 使用字符, glyphs 保留字形, count 字形总数
// 返回: []byte 格式14子表,无变体时为空, bool 映射是否有效
func subsetCmapVariations(data []byte, chars map[rune]bool, glyphs map[uint16]bool, count uint16) ([]byte, bool) {
if len(data) < 4 {
return nil, false
}
n := int(binary.BigEndian.Uint16(data[2:]))
if n > (len(data)-4)/8 {
return nil, false
}
var source []byte
for i := 0; i < n; i++ {
record := data[4+i*8:]
offset := uint64(binary.BigEndian.Uint32(record[4:]))
if offset < uint64(4+n*8) || offset+2 > uint64(len(data)) {
return nil, false
}
if binary.BigEndian.Uint16(data[offset:]) != 14 {
continue
}
if source != nil || binary.BigEndian.Uint16(record) != 0 || binary.BigEndian.Uint16(record[2:]) != 5 || offset+10 > uint64(len(data)) {
return nil, false
}
length := uint64(binary.BigEndian.Uint32(data[offset+2:]))
if length < 10 || offset+length > uint64(len(data)) {
return nil, false
}
source = data[offset : offset+length]
}
if source == nil {
return nil, true
}
return subsetCmapVariationTable(source, chars, glyphs, count)
}
// subsetCmapVariationTable 按使用字符筛选默认及非默认变体,保持选择符和映射有序
// 入参: data 格式14子表, chars 使用字符, glyphs 保留字形, count 字形总数
// 返回: []byte 裁剪后的子表, bool 子表是否有效
func subsetCmapVariationTable(data []byte, chars map[rune]bool, glyphs map[uint16]bool, count uint16) ([]byte, bool) {
n := uint64(binary.BigEndian.Uint32(data[6:]))
header := 10 + n*11
if header > uint64(len(data)) {
return nil, false
}
used := slices.Sorted(maps.Keys(chars))
var records, payload []byte
previous := rune(-1)
for i := uint64(0); i < n; i++ {
raw := data[10+i*11:]
for j, size := range []uint64{4, 5} {
offset := uint64(binary.BigEndian.Uint32(raw[3+j*4:]))
if offset == 0 {
continue
}
if offset < header || offset+4 > uint64(len(data)) {
return nil, false
}
entries := uint64(binary.BigEndian.Uint32(data[offset:]))
if offset+4+entries*size > uint64(len(data)) {
return nil, false
}
}
record, _, err := tables.ParseVariationSelector(raw, data)
selector := cmapVariationRune(record.VarSelector)
if err != nil || !utf8.ValidRune(selector) || selector <= previous || !validCmapVariation(record, count) {
return nil, false
}
previous = selector
if !chars[selector] {
continue
}
defaults := subsetCmapDefaultVariations(record.DefaultUVS.Ranges, used)
var alternatives []byte
for _, entry := range record.NonDefaultUVS.Ranges {
if chars[cmapVariationRune(entry.UnicodeValue)] {
alternatives = append(alternatives, entry.UnicodeValue[:]...)
alternatives = binary.BigEndian.AppendUint16(alternatives, uint16(entry.GlyphID))
glyphs[uint16(entry.GlyphID)] = true
}
}
if len(defaults)+len(alternatives) == 0 {
continue
}
start := len(records)
records = append(records, record.VarSelector[:]...)
records = append(records, make([]byte, 8)...)
for j, entries := range [][]byte{defaults, alternatives} {
if len(entries) == 0 {
continue
}
binary.BigEndian.PutUint32(records[start+3+j*4:], uint32(len(payload)+1))
payload = binary.BigEndian.AppendUint32(payload, uint32(len(entries)/(4+j)))
payload = append(payload, entries...)
}
}
if len(records) == 0 {
return nil, true
}
if uint64(len(records))+uint64(len(payload))+10 > 1<<32-1 {
return nil, false
}
for i := 0; i < len(records); i += 11 {
for _, j := range []int{3, 7} {
offset := binary.BigEndian.Uint32(records[i+j:])
if offset != 0 {
binary.BigEndian.PutUint32(records[i+j:], offset-1+uint32(10+len(records)))
}
}
}
out := make([]byte, 10, 10+len(records)+len(payload))
binary.BigEndian.PutUint16(out, 14)
binary.BigEndian.PutUint32(out[2:], uint32(cap(out)))
binary.BigEndian.PutUint32(out[6:], uint32(len(records)/11))
out = append(out, records...)
return append(out, payload...), true
}
// validCmapVariation 检查变体映射的字符范围、排序、互斥关系及字形编号
// 入参: record 选择符记录, count 字形总数
// 返回: bool 映射是否有效
func validCmapVariation(record tables.VariationSelector, count uint16) bool {
defaults := record.DefaultUVS.Ranges
previous := rune(-1)
for _, entry := range defaults {
first := cmapVariationRune(entry.StartUnicodeValue)
last := first + rune(entry.AdditionalCount)
if first <= previous || !utf8.ValidRune(first) || !utf8.ValidRune(last) || first < 0xd800 && last >= 0xd800 {
return false
}
previous = last
}
previous = -1
index := 0
for _, entry := range record.NonDefaultUVS.Ranges {
char := cmapVariationRune(entry.UnicodeValue)
if char <= previous || !utf8.ValidRune(char) || uint16(entry.GlyphID) >= count {
return false
}
previous = char
for index < len(defaults) && cmapVariationRune(defaults[index].StartUnicodeValue)+rune(defaults[index].AdditionalCount) < char {
index++
}
if index < len(defaults) && cmapVariationRune(defaults[index].StartUnicodeValue) <= char {
return false
}
}
return true
}
// subsetCmapDefaultVariations 筛选默认变体的使用字符,将连续字符合并为格式14范围
// 入参: ranges 默认变体范围, chars 已排序的使用字符
// 返回: []byte 范围记录,不含数量字段
func subsetCmapDefaultVariations(ranges []tables.UnicodeRange, chars []rune) []byte {
var out []byte
index := 0
last := rune(-2)
for _, char := range chars {
for index < len(ranges) && cmapVariationRune(ranges[index].StartUnicodeValue)+rune(ranges[index].AdditionalCount) < char {
index++
}
if index == len(ranges) {
break
}
if cmapVariationRune(ranges[index].StartUnicodeValue) > char {
continue
}
if char == last+1 && out[len(out)-1] < 255 {
out[len(out)-1]++
} else {
out = append(out, byte(char>>16), byte(char>>8), byte(char), 0)
}
last = char
}
return out
}
// cmapVariationRune 读取变体映射中的24位Unicode码点
// 入参: value 三字节码点
// 返回: rune Unicode码点
func cmapVariationRune(value [3]byte) rune {
return rune(value[0])<<16 | rune(value[1])<<8 | rune(value[2])
}
// buildCmapWithVariations 在生成的Unicode映射中插入格式14记录,保持编码记录有序
// 入参: count 字形总数, mapping 字符映射, variations 格式14子表
// 返回: []byte 字符映射表
func buildCmapWithVariations(count uint16, mapping map[rune]uint16, variations []byte) []byte {
base := BuildCmapTable(count, mapping)
if len(variations) == 0 {
return base
}
payload := base[binary.BigEndian.Uint32(base[8:]):]
out := make([]byte, 28, 28+len(payload)+len(variations))
binary.BigEndian.PutUint16(out[2:], 3)
encoding := uint16(3)
if binary.BigEndian.Uint16(payload) == 12 {
encoding = 4
}
binary.BigEndian.PutUint16(out[6:], encoding)
binary.BigEndian.PutUint32(out[8:], 28)
binary.BigEndian.PutUint16(out[14:], 5)
binary.BigEndian.PutUint32(out[16:], uint32(28+len(payload)))
last := 4 + 8*(int(binary.BigEndian.Uint16(base[2:]))-1)
copy(out[20:28], base[last:last+8])
binary.BigEndian.PutUint32(out[24:], 28)
out = append(out, payload...)
return append(out, variations...)
}