mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 16:59:27 +08:00
235 lines
7.9 KiB
Go
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 ofdgo
|
|
|
|
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...)
|
|
}
|