mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 14:53:44 +08:00
1 parent
90901fb9e9
commit
1104f0fdd1
6 files changed
+978
-470
No files matched your search
+81
-461
@@ -30,12 +30,6 @@ import (
|
||||
// cffDict 使用float64存储所有数值,以统一处理整数和实数
|
||||
type cffDict map[int][]float64
|
||||
|
||||
// type2Operand Type2操作数
|
||||
type type2Operand struct {
|
||||
value int
|
||||
outStart int
|
||||
}
|
||||
|
||||
// wrapCFFToOTF 将CFF裸数据包装为OpenType字体格式
|
||||
// 入参: cffData CFF字体数据
|
||||
// 返回: []byte OTF字体数据, map[rune]uint16 字符映射, error 错误信息
|
||||
@@ -46,9 +40,10 @@ func wrapCFFToOTF(cffData []byte) ([]byte, map[rune]uint16, error) {
|
||||
}
|
||||
mapping := getCmapFromCFF(cffData, int(numGlyphs))
|
||||
sanitized, err := sanitizeCFF(cffData)
|
||||
if err == nil {
|
||||
cffData = sanitized
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cffData = sanitized
|
||||
cffData, err = normalizeCFFCharstrings(cffData)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -114,8 +109,10 @@ func normalizeCFFCharstrings(data []byte) ([]byte, error) {
|
||||
if charStart < topStart+topSize || charSize == 0 || charStart+charSize > len(data) {
|
||||
return nil, fmt.Errorf("invalid CFF charstrings")
|
||||
}
|
||||
chars := readCFFIndexItems(data, charStart)
|
||||
changed := false
|
||||
chars, changed, err := normalizeType2Programs(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var explicitCharset []byte
|
||||
if charset := dict[15]; len(charset) == 1 && (charset[0] == 1 || charset[0] == 2) {
|
||||
sids, _, _, _, ok := getCFFCharsetInfo(data, len(chars))
|
||||
@@ -128,22 +125,6 @@ func normalizeCFFCharstrings(data []byte) ([]byte, error) {
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
for index, charstring := range chars {
|
||||
normalized, inserted, err := explicitType2CounterStems(charstring)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
changed = changed || inserted
|
||||
if bytes.Contains(normalized, []byte{12, 0}) {
|
||||
var removed bool
|
||||
normalized, removed, err = stripType2Dotsection(normalized)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
changed = changed || removed
|
||||
}
|
||||
chars[index] = normalized
|
||||
}
|
||||
if !changed {
|
||||
return data, nil
|
||||
}
|
||||
@@ -197,110 +178,6 @@ func normalizeCFFCharstrings(data []byte) ([]byte, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// explicitType2CounterStems 将计数掩码前省略的vstemhm补为显式指令,不改变笔画提示及掩码
|
||||
// 入参: data Type2字形程序
|
||||
// 返回: []byte 等价程序, bool 是否发生变更, error 字节码错误
|
||||
func explicitType2CounterStems(data []byte) ([]byte, bool, error) {
|
||||
operands, hints := 0, 0
|
||||
for index := 0; index < len(data); {
|
||||
start, op := index, data[index]
|
||||
index++
|
||||
switch {
|
||||
case op == 28:
|
||||
index += 2
|
||||
operands++
|
||||
case op == 255:
|
||||
index += 4
|
||||
operands++
|
||||
case op >= 247:
|
||||
index++
|
||||
operands++
|
||||
case op >= 32:
|
||||
operands++
|
||||
case op == 1 || op == 3 || op == 18 || op == 23:
|
||||
hints += operands / 2
|
||||
operands = 0
|
||||
case op == 19 || op == 20:
|
||||
hints += operands / 2
|
||||
index += (hints + 7) / 8
|
||||
if index > len(data) {
|
||||
return nil, false, fmt.Errorf("incomplete Type2 hint mask")
|
||||
}
|
||||
if op == 20 && operands >= 2 {
|
||||
result := make([]byte, 0, len(data)+1)
|
||||
result = append(result, data[:start]...)
|
||||
result = append(result, 23)
|
||||
return append(result, data[start:]...), true, nil
|
||||
}
|
||||
operands = 0
|
||||
case op == 12 && index < len(data) && data[index] == 0:
|
||||
index++
|
||||
default:
|
||||
return data, false, nil
|
||||
}
|
||||
if index > len(data) {
|
||||
return nil, false, fmt.Errorf("incomplete Type2 operand")
|
||||
}
|
||||
}
|
||||
return data, false, nil
|
||||
}
|
||||
|
||||
// stripType2Dotsection 保留字形指令与掩码并移除无效果的旧指令
|
||||
// 入参: data Type2字形程序
|
||||
// 返回: []byte 标准化后的程序, bool 是否发生变更, error 错误信息
|
||||
func stripType2Dotsection(data []byte) ([]byte, bool, error) {
|
||||
var result []byte
|
||||
stack, hints := 0, 0
|
||||
for index := 0; index < len(data); {
|
||||
start := index
|
||||
operator := int(data[index])
|
||||
index++
|
||||
switch {
|
||||
case operator == 28:
|
||||
index += 2
|
||||
stack++
|
||||
case operator == 255:
|
||||
index += 4
|
||||
stack++
|
||||
case operator >= 247 && operator <= 254:
|
||||
index++
|
||||
stack++
|
||||
case operator >= 32:
|
||||
stack++
|
||||
case operator == 12:
|
||||
if index >= len(data) {
|
||||
return nil, false, fmt.Errorf("incomplete Type2 operator")
|
||||
}
|
||||
operator = 1200 + int(data[index])
|
||||
index++
|
||||
if operator == 1200 {
|
||||
if result == nil {
|
||||
result = append(make([]byte, 0, len(data)-2), data[:start]...)
|
||||
}
|
||||
continue
|
||||
}
|
||||
default:
|
||||
if operator == 1 || operator == 3 || operator == 18 || operator == 23 || operator == 19 || operator == 20 {
|
||||
hints += stack / 2
|
||||
}
|
||||
if operator == 19 || operator == 20 {
|
||||
index += (hints + 7) / 8
|
||||
}
|
||||
stack = 0
|
||||
}
|
||||
if index > len(data) {
|
||||
return nil, false, fmt.Errorf("incomplete Type2 charstring")
|
||||
}
|
||||
if result != nil {
|
||||
result = append(result, data[start:index]...)
|
||||
}
|
||||
}
|
||||
if result == nil {
|
||||
return data, false, nil
|
||||
}
|
||||
return result, true, nil
|
||||
}
|
||||
|
||||
// sanitizeCFF 尝试清洗CFF数据,转换CID字体并合并FontMatrix
|
||||
// 入参: data 原始CFF数据
|
||||
// 返回: []byte 清洗后的CFF数据, error 错误信息
|
||||
@@ -451,23 +328,55 @@ func sanitizeMultiFDCFF(data []byte, hdrSize int, nameIndexData []byte, topDict
|
||||
return nil, fmt.Errorf("missing charstrings")
|
||||
}
|
||||
charStringsOff := int(charStringsOffs[0])
|
||||
charStrings := readCFFIndexItems(data, charStringsOff)
|
||||
charStrings, _, err := readType2Index(data, charStringsOff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(charStrings) == 0 {
|
||||
return nil, fmt.Errorf("missing charstrings")
|
||||
}
|
||||
fdSelect := make([]int, len(charStrings))
|
||||
if fdSelectVals, ok := topDict[1237]; ok && len(fdSelectVals) > 0 {
|
||||
fdSelect = parseCFFFDSelect(data, int(fdSelectVals[0]), len(charStrings))
|
||||
fdSelectVals := topDict[1237]
|
||||
if len(fdSelectVals) != 1 || !finite(fdSelectVals[0]) || fdSelectVals[0] < 0 || fdSelectVals[0] >= float64(len(data)) || fdSelectVals[0] != math.Trunc(fdSelectVals[0]) {
|
||||
return nil, fmt.Errorf("invalid CFF FDSelect offset")
|
||||
}
|
||||
globalSubrs := readCFFIndexItems(globalSubrIndexData, 0)
|
||||
localSubrs, privateDicts := readCFFLocalSubrs(data, fdArrOff, fdCount)
|
||||
inlined := make([][]byte, len(charStrings))
|
||||
for gid, cs := range charStrings {
|
||||
fd := 0
|
||||
if gid < len(fdSelect) && fdSelect[gid] >= 0 && fdSelect[gid] < len(localSubrs) {
|
||||
fd = fdSelect[gid]
|
||||
fdSelect, err := readType2FDSelect(data, int(fdSelectVals[0]), len(charStrings), fdCount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
globalSubrs, _, err := readType2Index(globalSubrIndexData, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fdItems, _, err := readType2Index(data, fdArrOff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(fdItems) != fdCount {
|
||||
return nil, fmt.Errorf("invalid CFF FDArray count")
|
||||
}
|
||||
fonts := make([]type2Font, fdCount)
|
||||
privateDicts := make([]cffDict, fdCount)
|
||||
for fd, item := range fdItems {
|
||||
fonts[fd] = type2Font{data: data, chars: charStrings, globals: globalSubrs, seed: 1}
|
||||
privateDicts[fd], err = readType2Private(data, parseCFFDict(item)[18], &fonts[fd])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CFF font dictionary %d: %w", fd, err)
|
||||
}
|
||||
inlined[gid] = removeType2Hints(inlineType2CharString(cs, localSubrs[fd], globalSubrs, 0))
|
||||
}
|
||||
inlined := make([][]byte, len(charStrings))
|
||||
var stack [48]float64
|
||||
for gid, cs := range charStrings {
|
||||
font := &fonts[fdSelect[gid]]
|
||||
var output bytes.Buffer
|
||||
steps := 0
|
||||
state := type2State{font: font, args: stack[:0], widthTarget: &fonts[0], width: font.def, seed: font.seed + uint64(gid), steps: &steps, output: &output, stripHints: true}
|
||||
if len(cs) == 0 {
|
||||
cs = []byte{14}
|
||||
}
|
||||
if _, err := state.run(cs, 0); err != nil {
|
||||
return nil, fmt.Errorf("CFF glyph %d: %w", gid, err)
|
||||
}
|
||||
inlined[gid] = output.Bytes()
|
||||
}
|
||||
delete(topDict, 1230)
|
||||
delete(topDict, 1236)
|
||||
@@ -487,7 +396,7 @@ func sanitizeMultiFDCFF(data []byte, hdrSize int, nameIndexData []byte, topDict
|
||||
}
|
||||
finalPrivData := encodeCFFDict(privateDict)
|
||||
charStringsData := encodeCFFIndex(inlined)
|
||||
return encodeSanitizedCFF(data[:hdrSize], nameIndexData, topDict, stringIndexData, globalSubrIndexData, charStringsData, finalPrivData, nil), nil
|
||||
return encodeSanitizedCFF(data[:hdrSize], nameIndexData, topDict, stringIndexData, encodeCFFIndex(nil), charStringsData, finalPrivData, nil), nil
|
||||
}
|
||||
|
||||
// encodeSanitizedCFF 按字典编码长度收敛偏移并组装规范化字体
|
||||
@@ -939,201 +848,6 @@ func parseCFFFDSelect(data []byte, offset int, numGlyphs int) []int {
|
||||
return result
|
||||
}
|
||||
|
||||
// readCFFLocalSubrs 读取CID CFF的本地子程序
|
||||
// 入参: data CFF数据, fdArrOff FDArray偏移, fdCount FD数量
|
||||
// 返回: [][][]byte 本地子程序列表, []cffDict Private字典列表
|
||||
func readCFFLocalSubrs(data []byte, fdArrOff int, fdCount int) ([][][]byte, []cffDict) {
|
||||
fdItems := readCFFIndexItems(data, fdArrOff)
|
||||
localSubrs := make([][][]byte, fdCount)
|
||||
privateDicts := make([]cffDict, fdCount)
|
||||
for i := 0; i < fdCount && i < len(fdItems); i++ {
|
||||
fdDict := parseCFFDict(fdItems[i])
|
||||
privVals, ok := fdDict[18]
|
||||
if !ok || len(privVals) != 2 {
|
||||
privateDicts[i] = make(cffDict)
|
||||
continue
|
||||
}
|
||||
privSize := int(privVals[0])
|
||||
privOff := int(privVals[1])
|
||||
if privSize <= 0 || privOff < 0 || privOff+privSize > len(data) {
|
||||
privateDicts[i] = make(cffDict)
|
||||
continue
|
||||
}
|
||||
privData := data[privOff : privOff+privSize]
|
||||
privDict := parseCFFDict(privData)
|
||||
privateDicts[i] = privDict
|
||||
if subrVals, ok := privDict[19]; ok && len(subrVals) > 0 {
|
||||
subrOff := privOff + int(subrVals[0])
|
||||
if subrOff >= 0 && subrOff < len(data) {
|
||||
localSubrs[i] = readCFFIndexItems(data, subrOff)
|
||||
}
|
||||
}
|
||||
}
|
||||
return localSubrs, privateDicts
|
||||
}
|
||||
|
||||
// inlineType2CharString 内联Type2 CharString子程序调用
|
||||
// 入参: data CharString数据, localSubrs 本地子程序, globalSubrs 全局子程序, depth 递归深度
|
||||
// 返回: []byte 内联后的CharString数据
|
||||
func inlineType2CharString(data []byte, localSubrs, globalSubrs [][]byte, depth int) []byte {
|
||||
if depth > 8 {
|
||||
return data
|
||||
}
|
||||
out := make([]byte, 0, len(data))
|
||||
var stack []type2Operand
|
||||
hintCount := 0
|
||||
for i := 0; i < len(data); {
|
||||
b := data[i]
|
||||
if b == 28 && i+2 < len(data) {
|
||||
start := len(out)
|
||||
out = append(out, data[i:i+3]...)
|
||||
stack = append(stack, type2Operand{value: int(parseShortInt(data, i)), outStart: start})
|
||||
i += 3
|
||||
continue
|
||||
}
|
||||
if b >= 32 && b <= 246 {
|
||||
start := len(out)
|
||||
out = append(out, b)
|
||||
stack = append(stack, type2Operand{value: int(parseNumberType2(data, i)), outStart: start})
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if b >= 247 && b <= 254 && i+1 < len(data) {
|
||||
start := len(out)
|
||||
out = append(out, data[i:i+2]...)
|
||||
stack = append(stack, type2Operand{value: int(parseNumberType2(data, i)), outStart: start})
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
if b == 255 && i+4 < len(data) {
|
||||
start := len(out)
|
||||
out = append(out, data[i:i+5]...)
|
||||
stack = append(stack, type2Operand{value: int(parseNumberType2(data, i)), outStart: start})
|
||||
i += 5
|
||||
continue
|
||||
}
|
||||
if b == 10 || b == 29 {
|
||||
if len(stack) > 0 {
|
||||
operand := stack[len(stack)-1]
|
||||
out = out[:operand.outStart]
|
||||
subrs := localSubrs
|
||||
if b == 29 {
|
||||
subrs = globalSubrs
|
||||
}
|
||||
idx := operand.value + cffSubrBias(len(subrs))
|
||||
if idx >= 0 && idx < len(subrs) {
|
||||
subr := inlineType2CharString(subrs[idx], localSubrs, globalSubrs, depth+1)
|
||||
if len(subr) > 0 && subr[len(subr)-1] == 11 {
|
||||
subr = subr[:len(subr)-1]
|
||||
}
|
||||
out = append(out, subr...)
|
||||
}
|
||||
stack = nil
|
||||
}
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if b == 11 && depth > 0 {
|
||||
return out
|
||||
}
|
||||
out = append(out, b)
|
||||
i++
|
||||
op := int(b)
|
||||
if b == 12 && i < len(data) {
|
||||
out = append(out, data[i])
|
||||
op = 1200 + int(data[i])
|
||||
i++
|
||||
}
|
||||
switch op {
|
||||
case 1, 3, 18, 23:
|
||||
hintCount += len(stack) / 2
|
||||
case 19, 20:
|
||||
hintCount += len(stack) / 2
|
||||
maskBytes := (hintCount + 7) / 8
|
||||
if i+maskBytes > len(data) {
|
||||
maskBytes = len(data) - i
|
||||
}
|
||||
out = append(out, data[i:i+maskBytes]...)
|
||||
i += maskBytes
|
||||
}
|
||||
stack = nil
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// removeType2Hints 移除Type2 CharString的hint指令
|
||||
// 入参: data CharString数据
|
||||
// 返回: []byte 移除hint后的CharString数据
|
||||
func removeType2Hints(data []byte) []byte {
|
||||
out := make([]byte, 0, len(data))
|
||||
var stack []type2Operand
|
||||
hintCount := 0
|
||||
for i := 0; i < len(data); {
|
||||
b := data[i]
|
||||
if b == 28 && i+2 < len(data) {
|
||||
start := len(out)
|
||||
out = append(out, data[i:i+3]...)
|
||||
stack = append(stack, type2Operand{value: int(parseShortInt(data, i)), outStart: start})
|
||||
i += 3
|
||||
continue
|
||||
}
|
||||
if b >= 32 && b <= 246 {
|
||||
start := len(out)
|
||||
out = append(out, b)
|
||||
stack = append(stack, type2Operand{value: int(parseNumberType2(data, i)), outStart: start})
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if b >= 247 && b <= 254 && i+1 < len(data) {
|
||||
start := len(out)
|
||||
out = append(out, data[i:i+2]...)
|
||||
stack = append(stack, type2Operand{value: int(parseNumberType2(data, i)), outStart: start})
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
if b == 255 && i+4 < len(data) {
|
||||
start := len(out)
|
||||
out = append(out, data[i:i+5]...)
|
||||
stack = append(stack, type2Operand{value: int(parseNumberType2(data, i)), outStart: start})
|
||||
i += 5
|
||||
continue
|
||||
}
|
||||
op := int(b)
|
||||
opLen := 1
|
||||
if b == 12 && i+1 < len(data) {
|
||||
op = 1200 + int(data[i+1])
|
||||
opLen = 2
|
||||
}
|
||||
switch op {
|
||||
case 1, 3, 18, 23:
|
||||
if len(stack) > 0 {
|
||||
out = out[:stack[0].outStart]
|
||||
}
|
||||
hintCount += len(stack) / 2
|
||||
stack = nil
|
||||
i += opLen
|
||||
continue
|
||||
case 19, 20:
|
||||
if len(stack) > 0 {
|
||||
out = out[:stack[0].outStart]
|
||||
}
|
||||
hintCount += len(stack) / 2
|
||||
i += opLen
|
||||
maskBytes := (hintCount + 7) / 8
|
||||
if i+maskBytes > len(data) {
|
||||
maskBytes = len(data) - i
|
||||
}
|
||||
i += maskBytes
|
||||
stack = nil
|
||||
continue
|
||||
}
|
||||
out = append(out, data[i:i+opLen]...)
|
||||
i += opLen
|
||||
stack = nil
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// cffSubrBias 获取Type2子程序偏移
|
||||
// 入参: count 子程序数量
|
||||
// 返回: int 偏移量
|
||||
@@ -1147,136 +861,42 @@ func cffSubrBias(count int) int {
|
||||
return 32768
|
||||
}
|
||||
|
||||
// parseCFFWidths 从CFF数据中解析Glyph宽度
|
||||
// parseCFFWidths 从Type2执行状态获取字宽,保留首个清栈指令的宽度语义
|
||||
// 入参: data CFF数据, numGlyphs 字形数量
|
||||
// 返回: []uint16 宽度列表, error 错误信息
|
||||
// 返回: []uint16 宽度列表, error 字体或执行错误
|
||||
func parseCFFWidths(data []byte, numGlyphs int) ([]uint16, error) {
|
||||
if len(data) < 4 {
|
||||
return nil, fmt.Errorf("data too short")
|
||||
font, err := readType2Font(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hdrSize := int(data[2])
|
||||
offset := hdrSize
|
||||
_, sz := getCFFIndexCount(data, offset)
|
||||
offset += sz
|
||||
topDictData, sz := getCFFIndexData(data, offset)
|
||||
offset += sz
|
||||
if topDictData == nil {
|
||||
return nil, fmt.Errorf("failed to read top dict")
|
||||
if len(font.chars) != numGlyphs {
|
||||
return nil, fmt.Errorf("CFF glyph count differs from metrics")
|
||||
}
|
||||
topDict := parseCFFDict(topDictData)
|
||||
_, sz = getCFFIndexCount(data, offset)
|
||||
offset += sz
|
||||
_, sz = getCFFIndexCount(data, offset)
|
||||
offset += sz
|
||||
var nominalWidthX float64 = 0
|
||||
var defaultWidthX float64 = 0
|
||||
if vals, ok := topDict[18]; ok && len(vals) == 2 {
|
||||
privSize := int(vals[0])
|
||||
privOff := int(vals[1])
|
||||
if privSize > 0 && privOff+privSize <= len(data) {
|
||||
privData := data[privOff : privOff+privSize]
|
||||
privDict := parseCFFDict(privData)
|
||||
if v, ok := privDict[20]; ok && len(v) > 0 {
|
||||
defaultWidthX = v[0]
|
||||
}
|
||||
if v, ok := privDict[21]; ok && len(v) > 0 {
|
||||
nominalWidthX = v[0]
|
||||
widths := make([]uint16, numGlyphs)
|
||||
var stack [48]float64
|
||||
for gid, program := range font.chars {
|
||||
if len(program) == 0 {
|
||||
program = []byte{14}
|
||||
}
|
||||
steps := 0
|
||||
state := type2State{font: font, args: stack[:0], width: font.def, widthOnly: true, seed: font.seed + uint64(gid), steps: &steps}
|
||||
if _, err := state.run(program, 0); err != nil && !state.widthSet {
|
||||
state.width = font.def
|
||||
}
|
||||
if gid == 0 && state.width < 0 {
|
||||
steps = 0
|
||||
empty := type2State{font: font, args: stack[:0], width: font.def, seed: font.seed, steps: &steps}
|
||||
_, err := empty.run(program, 0)
|
||||
if err == nil && !empty.pathStarted && !empty.changed {
|
||||
state.width = 0
|
||||
}
|
||||
}
|
||||
if !finite(state.width) || state.width < 0 || state.width > math.MaxUint16 {
|
||||
return nil, fmt.Errorf("CFF glyph %d width cannot be represented", gid)
|
||||
}
|
||||
widths[gid] = uint16(math.Round(state.width))
|
||||
}
|
||||
if vals, ok := topDict[17]; ok && len(vals) > 0 {
|
||||
charStrOff := int(vals[0])
|
||||
count, _ := getCFFIndexCount(data, charStrOff)
|
||||
limit := count
|
||||
if numGlyphs < limit {
|
||||
limit = numGlyphs
|
||||
}
|
||||
widths := make([]uint16, numGlyphs)
|
||||
for i := range widths {
|
||||
widths[i] = uint16(defaultWidthX)
|
||||
}
|
||||
for i := 0; i < limit; i++ {
|
||||
offSize := int(data[charStrOff+2])
|
||||
p1 := charStrOff + 3 + i*offSize
|
||||
p2 := p1 + offSize
|
||||
off1 := readCFFOffset(data, p1, offSize)
|
||||
off2 := readCFFOffset(data, p2, offSize)
|
||||
dataStartBase := charStrOff + 3 + (count+1)*offSize
|
||||
start := dataStartBase + (off1 - 1)
|
||||
length := off2 - off1
|
||||
if start < 0 || start+length > len(data) {
|
||||
widths[i] = uint16(defaultWidthX)
|
||||
continue
|
||||
}
|
||||
csData := data[start : start+length]
|
||||
w := scanCharStringWidth(csData, nominalWidthX, defaultWidthX)
|
||||
widths[i] = uint16(w)
|
||||
}
|
||||
return widths, nil
|
||||
}
|
||||
return nil, fmt.Errorf("no charstrings")
|
||||
}
|
||||
|
||||
// scanCharStringWidth 扫描CharString获取宽度
|
||||
// 入参: data CharString数据, nominal 名义宽度, def 默认宽度
|
||||
// 返回: float64 宽度值
|
||||
func scanCharStringWidth(data []byte, nominal, def float64) float64 {
|
||||
stackDepth := 0
|
||||
i := 0
|
||||
firstVal := 0.0
|
||||
for i < len(data) {
|
||||
b := data[i]
|
||||
if b <= 31 {
|
||||
if b == 28 {
|
||||
i += 3
|
||||
stackDepth++
|
||||
if stackDepth == 1 {
|
||||
firstVal = parseShortInt(data, i-3)
|
||||
}
|
||||
} else if b == 29 {
|
||||
i += 5
|
||||
stackDepth++
|
||||
} else if b == 12 {
|
||||
i += 2
|
||||
if stackDepth%2 != 0 {
|
||||
return nominal + firstVal
|
||||
}
|
||||
return def
|
||||
} else if b == 19 || b == 20 {
|
||||
if stackDepth%2 != 0 {
|
||||
return nominal + firstVal
|
||||
}
|
||||
return def
|
||||
} else {
|
||||
if stackDepth%2 != 0 {
|
||||
return nominal + firstVal
|
||||
}
|
||||
return def
|
||||
}
|
||||
} else {
|
||||
stackDepth++
|
||||
if stackDepth == 1 {
|
||||
firstVal = parseNumberType2(data, i)
|
||||
}
|
||||
if b >= 32 && b <= 246 {
|
||||
i++
|
||||
} else if b >= 247 && b <= 250 {
|
||||
i += 2
|
||||
} else if b >= 251 && b <= 254 {
|
||||
i += 2
|
||||
} else if b == 255 {
|
||||
i += 5
|
||||
}
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// parseShortInt 解析短整数 (Type 2 CharString)
|
||||
// 入参: data 数据, idx 索引
|
||||
// 返回: float64 浮点值
|
||||
func parseShortInt(data []byte, idx int) float64 {
|
||||
return float64(int16(binary.BigEndian.Uint16(data[idx+1:])))
|
||||
return widths, nil
|
||||
}
|
||||
|
||||
// parseNumberType2 解析Number (Type 2)
|
||||
|
||||
+15
-3
@@ -40,10 +40,10 @@ func fixTrueType(data []byte, fixCmap, fixName bool) (bool, []byte, map[rune]uin
|
||||
tag := string(data[pos : pos+4])
|
||||
offset := binary.BigEndian.Uint32(data[pos+8 : pos+12])
|
||||
length := binary.BigEndian.Uint32(data[pos+12 : pos+16])
|
||||
if uint32(len(data)) >= offset+length {
|
||||
existingTables[tag] = data[offset : offset+length]
|
||||
if uint64(offset)+uint64(length) <= uint64(len(data)) {
|
||||
existingTables[tag] = data[int(offset) : int(offset)+int(length)]
|
||||
padding := (4 - (length & 3)) & 3
|
||||
if offset%4 != 0 || uint32(len(data))-offset-length < padding {
|
||||
if offset%4 != 0 || len(data)-int(offset)-int(length) < int(padding) {
|
||||
malformedDirectory = true
|
||||
}
|
||||
} else {
|
||||
@@ -53,6 +53,18 @@ func fixTrueType(data []byte, fixCmap, fixName bool) (bool, []byte, map[rune]uin
|
||||
}
|
||||
if existingTables["CFF "] != nil {
|
||||
isCFFSfnt = true
|
||||
sanitized, err := sanitizeCFF(existingTables["CFF "])
|
||||
if err != nil {
|
||||
return false, data, nil, false, err
|
||||
}
|
||||
normalized, err := normalizeCFFCharstrings(sanitized)
|
||||
if err != nil {
|
||||
return false, data, nil, false, err
|
||||
}
|
||||
if !bytes.Equal(normalized, existingTables["CFF "]) {
|
||||
existingTables["CFF "] = normalized
|
||||
malformedDirectory = true
|
||||
}
|
||||
}
|
||||
cmap, err := normalizeCmap(existingTables["cmap"])
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,502 @@
|
||||
// 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"
|
||||
"math"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// type2Font 保存Type2字形程序、子程序及私有字宽
|
||||
type type2Font struct {
|
||||
data []byte
|
||||
chars [][]byte
|
||||
locals [][]byte
|
||||
globals [][]byte
|
||||
names map[string]int
|
||||
nominal, def float64
|
||||
seed uint64
|
||||
}
|
||||
|
||||
// type2State 按Type2规范保存跨子程序共享的执行状态
|
||||
type type2State struct {
|
||||
font *type2Font
|
||||
widthTarget *type2Font
|
||||
args []float64
|
||||
transient [32]float64
|
||||
stored uint32
|
||||
width float64
|
||||
widthSet bool
|
||||
widthOnly bool
|
||||
hints int
|
||||
x, y float64
|
||||
outX, outY float64
|
||||
originX, originY float64
|
||||
component bool
|
||||
stripHints bool
|
||||
pathStarted bool
|
||||
changed bool
|
||||
output *bytes.Buffer
|
||||
seed uint64
|
||||
steps *int
|
||||
}
|
||||
|
||||
// readType2Index 读取并校验CFF索引的全部偏移,不复制程序字节
|
||||
// 入参: data 字体数据, offset 索引偏移
|
||||
// 返回: [][]byte 索引项, int 索引结束位置, error 错误信息
|
||||
func readType2Index(data []byte, offset int) ([][]byte, int, error) {
|
||||
if offset < 0 || offset > len(data)-2 {
|
||||
return nil, 0, fmt.Errorf("truncated CFF index")
|
||||
}
|
||||
count := int(binary.BigEndian.Uint16(data[offset:]))
|
||||
if count == 0 {
|
||||
return nil, offset + 2, nil
|
||||
}
|
||||
if offset > len(data)-3 {
|
||||
return nil, 0, fmt.Errorf("truncated CFF index header")
|
||||
}
|
||||
width := int(data[offset+2])
|
||||
if width < 1 || width > 4 || count+1 > (len(data)-offset-3)/width {
|
||||
return nil, 0, fmt.Errorf("invalid CFF index offsets")
|
||||
}
|
||||
base := offset + 3 + (count+1)*width
|
||||
items := make([][]byte, count)
|
||||
previous := uint64(0)
|
||||
for index := 0; index <= count; index++ {
|
||||
value := uint64(0)
|
||||
for _, b := range data[offset+3+index*width : offset+3+(index+1)*width] {
|
||||
value = value<<8 | uint64(b)
|
||||
}
|
||||
if value < 1 || value-1 > uint64(len(data)-base) || value < previous || index == 0 && value != 1 {
|
||||
return nil, 0, fmt.Errorf("invalid CFF index range")
|
||||
}
|
||||
if index != 0 {
|
||||
items[index-1] = data[base+int(previous)-1 : base+int(value)-1]
|
||||
}
|
||||
previous = value
|
||||
}
|
||||
return items, base + int(previous) - 1, nil
|
||||
}
|
||||
|
||||
// readType2Font 读取单FD字体的Type2执行资源,CID字体先经字体规范化
|
||||
// 入参: data CFF字体数据
|
||||
// 返回: *type2Font 执行资源, error 错误信息
|
||||
func readType2Font(data []byte) (*type2Font, error) {
|
||||
if !isBareCFFData(data) {
|
||||
return nil, fmt.Errorf("invalid CFF header")
|
||||
}
|
||||
names, end, err := readType2Index(data, int(data[2]))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid CFF name index: %w", err)
|
||||
}
|
||||
if len(names) != 1 {
|
||||
return nil, fmt.Errorf("invalid CFF font count")
|
||||
}
|
||||
tops, end, err := readType2Index(data, end)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid CFF top index: %w", err)
|
||||
}
|
||||
if len(tops) != 1 {
|
||||
return nil, fmt.Errorf("invalid CFF top dictionary count")
|
||||
}
|
||||
_, end, err = readType2Index(data, end)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
globals, _, err := readType2Index(data, end)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dict := parseCFFDict(tops[0])
|
||||
if value, ok := dict[1206]; ok && (len(value) != 1 || value[0] != 2) {
|
||||
return nil, fmt.Errorf("unsupported CFF charstring type")
|
||||
}
|
||||
if _, cid := dict[1230]; cid {
|
||||
return nil, fmt.Errorf("CID CFF requires FD normalization")
|
||||
}
|
||||
offset := dict[17]
|
||||
if len(offset) != 1 || !finite(offset[0]) || offset[0] < 0 || offset[0] > float64(len(data)) || offset[0] != math.Trunc(offset[0]) {
|
||||
return nil, fmt.Errorf("invalid CFF charstrings offset")
|
||||
}
|
||||
chars, _, err := readType2Index(data, int(offset[0]))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid CFF charstrings: %w", err)
|
||||
}
|
||||
if len(chars) == 0 {
|
||||
return nil, fmt.Errorf("empty CFF charstrings")
|
||||
}
|
||||
font := &type2Font{data: data, chars: chars, globals: globals, seed: 1}
|
||||
if _, err := readType2Private(data, dict[18], font); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return font, nil
|
||||
}
|
||||
|
||||
// readType2Private 读取FD私有字宽、随机种子和局部子程序
|
||||
// 入参: data 字体数据, values Private范围, font 执行资源
|
||||
// 返回: cffDict 私有字典, error 范围或字段错误
|
||||
func readType2Private(data []byte, values []float64, font *type2Font) (cffDict, error) {
|
||||
private := cffDict{}
|
||||
var err error
|
||||
if values != nil {
|
||||
if len(values) != 2 || !finite(values[0]) || !finite(values[1]) || values[0] < 0 || values[1] < 0 || values[0] > float64(len(data)) || values[1] > float64(len(data))-values[0] || values[0] != math.Trunc(values[0]) || values[1] != math.Trunc(values[1]) {
|
||||
return nil, fmt.Errorf("invalid CFF private range")
|
||||
}
|
||||
start := int(values[1])
|
||||
private = parseCFFDict(data[start : start+int(values[0])])
|
||||
for op, target := range map[int]*float64{20: &font.def, 21: &font.nominal} {
|
||||
if value, ok := private[op]; ok {
|
||||
if len(value) != 1 || !finite(value[0]) {
|
||||
return nil, fmt.Errorf("invalid CFF private width")
|
||||
}
|
||||
*target = value[0]
|
||||
}
|
||||
}
|
||||
if value, ok := private[1219]; ok {
|
||||
if len(value) != 1 || !finite(value[0]) || value[0] < math.MinInt32 || value[0] > math.MaxInt32 || value[0] != math.Trunc(value[0]) {
|
||||
return nil, fmt.Errorf("invalid CFF random seed")
|
||||
}
|
||||
font.seed = uint64(int64(value[0])) | 1
|
||||
}
|
||||
if value, ok := private[19]; ok {
|
||||
if len(value) != 1 || !finite(value[0]) || value[0] < 0 || value[0] > float64(len(data)-start) || value[0] != math.Trunc(value[0]) {
|
||||
return nil, fmt.Errorf("invalid CFF local subroutine offset")
|
||||
}
|
||||
font.locals, _, err = readType2Index(data, start+int(value[0]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return private, nil
|
||||
}
|
||||
|
||||
// normalizeType2Programs 展开组合字形和求值指令,普通字形保留原程序及提示
|
||||
// 无法规范化的字形保留原程序,由实际使用时的轮廓解析校验
|
||||
// 入参: data CFF字体数据
|
||||
// 返回: [][]byte 字形程序, bool 是否变更, error 错误信息
|
||||
func normalizeType2Programs(data []byte) ([][]byte, bool, error) {
|
||||
font, err := readType2Font(data)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
result := slices.Clone(font.chars)
|
||||
changed := false
|
||||
var stack [48]float64
|
||||
for gid, program := range font.chars {
|
||||
if len(program) == 0 {
|
||||
continue
|
||||
}
|
||||
steps := 0
|
||||
state := type2State{font: font, args: stack[:0], width: font.def, seed: font.seed + uint64(gid), steps: &steps}
|
||||
_, err := state.run(program, 0)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !state.changed {
|
||||
continue
|
||||
}
|
||||
var output bytes.Buffer
|
||||
steps = 0
|
||||
state = type2State{font: font, args: stack[:0], width: font.def, seed: font.seed + uint64(gid), steps: &steps, output: &output}
|
||||
if _, err := state.run(program, 0); err != nil {
|
||||
return nil, false, fmt.Errorf("CFF glyph %d: %w", gid, err)
|
||||
}
|
||||
result[gid] = output.Bytes()
|
||||
changed = true
|
||||
}
|
||||
return result, changed, nil
|
||||
}
|
||||
|
||||
// push 按Type2规范附录B校验并追加操作数
|
||||
// 入参: value 操作数
|
||||
// 返回: error 栈或数值错误
|
||||
func (s *type2State) push(value float64) error {
|
||||
if !finite(value) || len(s.args) >= 48 {
|
||||
return fmt.Errorf("invalid Type2 operand stack")
|
||||
}
|
||||
s.args = append(s.args, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// emit 写入已经求值的操作数及指令,分析阶段不分配输出
|
||||
// 入参: op 操作符, values 操作数
|
||||
// 返回: error 数值编码或程序长度错误
|
||||
func (s *type2State) emit(op int, values []float64) error {
|
||||
if s.output == nil {
|
||||
return nil
|
||||
}
|
||||
for _, value := range values {
|
||||
if err := encodeType2Number(s.output, value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if op >= 1200 {
|
||||
s.output.WriteByte(12)
|
||||
s.output.WriteByte(byte(op - 1200))
|
||||
} else {
|
||||
s.output.WriteByte(byte(op))
|
||||
}
|
||||
if s.output.Len() > 65535 {
|
||||
return fmt.Errorf("expanded Type2 charstring exceeds 65535 bytes")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// takeWidth 在首次清栈指令前提取可选字宽,组件不写入自身字宽
|
||||
// 入参: explicit 是否含字宽
|
||||
// 返回: error 字宽编码错误
|
||||
func (s *type2State) takeWidth(explicit bool) error {
|
||||
if s.widthSet {
|
||||
return nil
|
||||
}
|
||||
s.widthSet = true
|
||||
if explicit {
|
||||
s.width = s.font.nominal + s.args[0]
|
||||
if !finite(s.width) {
|
||||
return fmt.Errorf("invalid Type2 width")
|
||||
}
|
||||
if s.output != nil && !s.component && s.widthTarget == nil {
|
||||
if err := encodeType2Number(s.output, s.args[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
copy(s.args, s.args[1:])
|
||||
s.args = s.args[:len(s.args)-1]
|
||||
}
|
||||
if s.output != nil && !s.component && s.widthTarget != nil && s.width != s.widthTarget.def {
|
||||
return encodeType2Number(s.output, s.width-s.widthTarget.nominal)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// run 执行Type2程序,操作数和暂存数组在子程序间共享
|
||||
// 入参: data 字形或子程序, depth 调用深度
|
||||
// 返回: bool 是否结束字形, error 指令错误
|
||||
func (s *type2State) run(data []byte, depth int) (bool, error) {
|
||||
if depth > 10 || len(data) > 65535 {
|
||||
return false, fmt.Errorf("Type2 implementation limit exceeded")
|
||||
}
|
||||
for index := 0; index < len(data); {
|
||||
(*s.steps)++
|
||||
if *s.steps > 1<<20 {
|
||||
return false, fmt.Errorf("Type2 execution limit exceeded")
|
||||
}
|
||||
op := int(data[index])
|
||||
start := index
|
||||
index++
|
||||
if op == 28 || op >= 32 {
|
||||
length := 1
|
||||
if op == 28 {
|
||||
length = 3
|
||||
} else if op == 255 {
|
||||
length = 5
|
||||
} else if op >= 247 {
|
||||
length = 2
|
||||
}
|
||||
if length > len(data)-start {
|
||||
return false, fmt.Errorf("truncated Type2 number")
|
||||
}
|
||||
index = start + length
|
||||
if err := s.push(parseNumberType2(data, start)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if op == 12 {
|
||||
if index == len(data) {
|
||||
return false, fmt.Errorf("truncated Type2 operator")
|
||||
}
|
||||
op = 1200 + int(data[index])
|
||||
index++
|
||||
}
|
||||
switch op {
|
||||
case 10, 29:
|
||||
if len(s.args) == 0 {
|
||||
return false, fmt.Errorf("missing Type2 subroutine number")
|
||||
}
|
||||
value := s.args[len(s.args)-1]
|
||||
s.args = s.args[:len(s.args)-1]
|
||||
subrs := s.font.locals
|
||||
if op == 29 {
|
||||
subrs = s.font.globals
|
||||
}
|
||||
value += float64(cffSubrBias(len(subrs)))
|
||||
if value < 0 || value >= float64(len(subrs)) || value != math.Trunc(value) {
|
||||
return false, fmt.Errorf("invalid Type2 subroutine number")
|
||||
}
|
||||
ended, err := s.run(subrs[int(value)], depth+1)
|
||||
if ended || err != nil {
|
||||
return ended, err
|
||||
}
|
||||
case 11:
|
||||
if depth == 0 {
|
||||
return false, fmt.Errorf("Type2 return outside subroutine")
|
||||
}
|
||||
return false, nil
|
||||
case 1, 3, 18, 23, 19, 20:
|
||||
if err := s.takeWidth(len(s.args)%2 == 1); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if s.widthOnly {
|
||||
return true, nil
|
||||
}
|
||||
if len(s.args)%2 != 0 || s.hints+len(s.args)/2 > 96 {
|
||||
return false, fmt.Errorf("invalid Type2 stem hints")
|
||||
}
|
||||
s.hints += len(s.args) / 2
|
||||
if op == 19 || op == 20 {
|
||||
if s.hints == 0 {
|
||||
return false, fmt.Errorf("Type2 mask has no stem hints")
|
||||
}
|
||||
length := (s.hints + 7) / 8
|
||||
if length > len(data)-index {
|
||||
return false, fmt.Errorf("truncated Type2 hint mask")
|
||||
}
|
||||
if !s.component && !s.stripHints && len(s.args) > 0 {
|
||||
if err := s.emit(23, s.args); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if op == 20 {
|
||||
s.changed = true
|
||||
}
|
||||
}
|
||||
if !s.component && !s.stripHints {
|
||||
if err := s.emit(op, nil); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if s.output != nil {
|
||||
s.output.Write(data[index : index+length])
|
||||
}
|
||||
}
|
||||
index += length
|
||||
} else if !s.component && !s.stripHints {
|
||||
if err := s.emit(op, s.args); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
s.args = s.args[:0]
|
||||
case 4, 21, 22:
|
||||
count := 1
|
||||
if op == 21 {
|
||||
count = 2
|
||||
}
|
||||
if err := s.takeWidth(len(s.args) == count+1); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(s.args) != count {
|
||||
return false, fmt.Errorf("invalid Type2 moveto operands")
|
||||
}
|
||||
if s.widthOnly {
|
||||
return true, nil
|
||||
}
|
||||
if op != 4 {
|
||||
s.x += s.args[0]
|
||||
}
|
||||
if op != 22 {
|
||||
s.y += s.args[count-1]
|
||||
}
|
||||
if !finite(s.x) || !finite(s.y) {
|
||||
return false, fmt.Errorf("invalid Type2 current point")
|
||||
}
|
||||
if s.component {
|
||||
if err := s.emit(21, []float64{s.x + s.originX - s.outX, s.y + s.originY - s.outY}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
} else if err := s.emit(op, s.args); err != nil {
|
||||
return false, err
|
||||
}
|
||||
s.outX, s.outY = s.x+s.originX, s.y+s.originY
|
||||
s.pathStarted = true
|
||||
s.args = s.args[:0]
|
||||
case 14:
|
||||
if err := s.takeWidth(len(s.args) == 1 || len(s.args) == 5); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if s.widthOnly {
|
||||
return true, nil
|
||||
}
|
||||
if len(s.args) == 4 {
|
||||
if s.component {
|
||||
return false, fmt.Errorf("nested Type2 composite glyph")
|
||||
}
|
||||
if err := s.appendComponent(s.args[2], 0, 0); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := s.appendComponent(s.args[3], s.args[0], s.args[1]); err != nil {
|
||||
return false, err
|
||||
}
|
||||
s.changed = true
|
||||
} else if len(s.args) != 0 {
|
||||
return false, fmt.Errorf("invalid Type2 endchar operands")
|
||||
}
|
||||
if !s.component {
|
||||
if err := s.emit(14, nil); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
case 1200:
|
||||
s.changed = true
|
||||
case 5, 6, 7, 8, 24, 25, 26, 27, 30, 31, 1234, 1235, 1236, 1237:
|
||||
if !s.pathStarted {
|
||||
return false, fmt.Errorf("Type2 path has no moveto")
|
||||
}
|
||||
if err := s.draw(op); err != nil {
|
||||
return false, err
|
||||
}
|
||||
default:
|
||||
if err := s.calculate(op); err != nil {
|
||||
return false, err
|
||||
}
|
||||
s.changed = true
|
||||
}
|
||||
}
|
||||
return false, fmt.Errorf("Type2 program has no terminator")
|
||||
}
|
||||
|
||||
// appendComponent 按StandardEncoding查找组合组件,保持源字宽与独立暂存状态
|
||||
// 入参: code 标准字符码, x 横向偏移, y 纵向偏移
|
||||
// 返回: error 组件或指令错误
|
||||
func (s *type2State) appendComponent(code, x, y float64) error {
|
||||
if code < 0 || code > 255 || code != math.Trunc(code) || type1StandardNames[int(code)] == "" {
|
||||
return fmt.Errorf("invalid Type2 component code")
|
||||
}
|
||||
if s.font.names == nil {
|
||||
sids, _, _, index, ok := getCFFCharsetInfo(s.font.data, len(s.font.chars))
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid Type2 composite charset")
|
||||
}
|
||||
s.font.names = make(map[string]int, len(sids))
|
||||
for gid, sid := range sids {
|
||||
s.font.names[getCFFSIDString(s.font.data, index, sid)] = gid
|
||||
}
|
||||
}
|
||||
name := type1StandardNames[int(code)]
|
||||
gid, ok := s.font.names[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("missing Type2 component %s", name)
|
||||
}
|
||||
child := type2State{font: s.font, width: s.font.def, component: true, output: s.output, originX: x, originY: y, outX: s.outX, outY: s.outY, seed: s.font.seed + uint64(gid), steps: s.steps}
|
||||
_, err := child.run(s.font.chars[gid], 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid Type2 component %s: %w", name, err)
|
||||
}
|
||||
s.outX, s.outY = child.outX, child.outY
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// 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"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// readType2FDSelect 校验CID字体的逐字形FD归属和范围终点
|
||||
// 入参: data 字体数据, offset FDSelect偏移, count 字形数, fonts FD数量
|
||||
// 返回: []int FD索引, error 格式或范围错误
|
||||
func readType2FDSelect(data []byte, offset, count, fonts int) ([]int, error) {
|
||||
if offset < 0 || offset >= len(data) || count <= 0 || fonts <= 0 {
|
||||
return nil, fmt.Errorf("invalid CFF FDSelect range")
|
||||
}
|
||||
result := make([]int, count)
|
||||
switch data[offset] {
|
||||
case 0:
|
||||
if count > len(data)-offset-1 {
|
||||
return nil, fmt.Errorf("truncated CFF FDSelect")
|
||||
}
|
||||
for gid, fd := range data[offset+1 : offset+1+count] {
|
||||
if int(fd) >= fonts {
|
||||
return nil, fmt.Errorf("invalid CFF font dictionary index")
|
||||
}
|
||||
result[gid] = int(fd)
|
||||
}
|
||||
case 3:
|
||||
if offset > len(data)-3 {
|
||||
return nil, fmt.Errorf("truncated CFF FDSelect header")
|
||||
}
|
||||
ranges := int(binary.BigEndian.Uint16(data[offset+1:]))
|
||||
start := offset + 3
|
||||
if ranges == 0 || ranges > (len(data)-start-2)/3 {
|
||||
return nil, fmt.Errorf("invalid CFF FDSelect ranges")
|
||||
}
|
||||
if binary.BigEndian.Uint16(data[start:]) != 0 || int(binary.BigEndian.Uint16(data[start+3*ranges:])) != count {
|
||||
return nil, fmt.Errorf("invalid CFF FDSelect endpoints")
|
||||
}
|
||||
for index := 0; index < ranges; index++ {
|
||||
pos := start + 3*index
|
||||
first := int(binary.BigEndian.Uint16(data[pos:]))
|
||||
last := int(binary.BigEndian.Uint16(data[pos+3:]))
|
||||
fd := int(data[pos+2])
|
||||
if last <= first || last > count || fd >= fonts {
|
||||
return nil, fmt.Errorf("invalid CFF FDSelect interval")
|
||||
}
|
||||
for gid := first; gid < last; gid++ {
|
||||
result[gid] = fd
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported CFF FDSelect format %d", data[offset])
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"math"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// calculate 求值Type2算术、条件、栈及暂存指令,不写入输出程序
|
||||
// 入参: op 操作符
|
||||
// 返回: error 操作数或运算错误
|
||||
func (s *type2State) calculate(op int) error {
|
||||
count := 0
|
||||
switch op {
|
||||
case 1223:
|
||||
s.seed = uint64(uint32(s.seed*1664525 + 1013904223))
|
||||
return s.push(float64(s.seed+1) / 4294967296)
|
||||
case 1205, 1209, 1214, 1218, 1221, 1226, 1227, 1229:
|
||||
count = 1
|
||||
case 1203, 1204, 1210, 1211, 1212, 1215, 1220, 1224, 1228, 1230:
|
||||
count = 2
|
||||
case 1222:
|
||||
count = 4
|
||||
default:
|
||||
return fmt.Errorf("unsupported Type2 operator %d", op)
|
||||
}
|
||||
if len(s.args) < count {
|
||||
return fmt.Errorf("missing Type2 operands for operator %d", op)
|
||||
}
|
||||
values := s.args[len(s.args)-count:]
|
||||
value := values[0]
|
||||
switch op {
|
||||
case 1203:
|
||||
value = 0
|
||||
if values[0] != 0 && values[1] != 0 {
|
||||
value = 1
|
||||
}
|
||||
case 1204:
|
||||
value = 0
|
||||
if values[0] != 0 || values[1] != 0 {
|
||||
value = 1
|
||||
}
|
||||
case 1205:
|
||||
value = 0
|
||||
if values[0] == 0 {
|
||||
value = 1
|
||||
}
|
||||
case 1209:
|
||||
value = math.Abs(value)
|
||||
case 1210:
|
||||
value += values[1]
|
||||
case 1211:
|
||||
value -= values[1]
|
||||
case 1212:
|
||||
value /= values[1]
|
||||
case 1214:
|
||||
value = -value
|
||||
case 1215:
|
||||
value = 0
|
||||
if values[0] == values[1] {
|
||||
value = 1
|
||||
}
|
||||
case 1218:
|
||||
s.args = s.args[:len(s.args)-1]
|
||||
return nil
|
||||
case 1220, 1221:
|
||||
index := values[count-1]
|
||||
if index < 0 || index >= float64(len(s.transient)) || index != math.Trunc(index) {
|
||||
return fmt.Errorf("invalid Type2 transient index")
|
||||
}
|
||||
if op == 1220 {
|
||||
s.transient[int(index)] = value
|
||||
s.stored |= 1 << uint(index)
|
||||
s.args = s.args[:len(s.args)-2]
|
||||
return nil
|
||||
}
|
||||
if s.stored&(1<<uint(index)) == 0 {
|
||||
return fmt.Errorf("uninitialized Type2 transient value")
|
||||
}
|
||||
value = s.transient[int(index)]
|
||||
case 1222:
|
||||
if values[2] > values[3] {
|
||||
value = values[1]
|
||||
}
|
||||
case 1224:
|
||||
value *= values[1]
|
||||
case 1226:
|
||||
value = math.Sqrt(value)
|
||||
case 1227:
|
||||
return s.push(value)
|
||||
case 1228:
|
||||
values[0], values[1] = values[1], values[0]
|
||||
return nil
|
||||
case 1229:
|
||||
if value != math.Trunc(value) || value >= float64(len(s.args)-1) {
|
||||
return fmt.Errorf("invalid Type2 stack index")
|
||||
}
|
||||
index := max(0, value)
|
||||
if len(s.args) < 2 {
|
||||
return fmt.Errorf("empty Type2 indexed stack")
|
||||
}
|
||||
value = s.args[len(s.args)-2-int(index)]
|
||||
case 1230:
|
||||
n, j := values[0], values[1]
|
||||
if n < 0 || n > float64(len(s.args)-2) || n != math.Trunc(n) || j != math.Trunc(j) {
|
||||
return fmt.Errorf("invalid Type2 stack roll")
|
||||
}
|
||||
s.args = s.args[:len(s.args)-2]
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
shift := int(math.Mod(j, n))
|
||||
if shift < 0 {
|
||||
shift += int(n)
|
||||
}
|
||||
tail := s.args[len(s.args)-int(n):]
|
||||
slices.Reverse(tail)
|
||||
slices.Reverse(tail[:shift])
|
||||
slices.Reverse(tail[shift:])
|
||||
return nil
|
||||
}
|
||||
s.args = s.args[:len(s.args)-count]
|
||||
return s.push(value)
|
||||
}
|
||||
|
||||
// draw 校验Type2路径指令并推进当前点,保留曲线及Flex编码
|
||||
// 入参: op 操作符
|
||||
// 返回: error 路径操作数或输出错误
|
||||
func (s *type2State) draw(op int) error {
|
||||
a := s.args
|
||||
valid := true
|
||||
dx, dy := 0.0, 0.0
|
||||
switch op {
|
||||
case 5:
|
||||
valid = len(a) >= 2 && len(a)%2 == 0
|
||||
if valid {
|
||||
for i := 0; i < len(a); i += 2 {
|
||||
dx, dy = dx+a[i], dy+a[i+1]
|
||||
}
|
||||
}
|
||||
case 6, 7:
|
||||
valid = len(a) > 0
|
||||
for i, value := range a {
|
||||
if (i%2 == 0) == (op == 6) {
|
||||
dx += value
|
||||
} else {
|
||||
dy += value
|
||||
}
|
||||
}
|
||||
case 8, 24, 25:
|
||||
start, end := 0, len(a)
|
||||
if op == 24 {
|
||||
end -= 2
|
||||
valid = len(a) >= 8 && end%6 == 0
|
||||
} else if op == 25 {
|
||||
start = len(a) - 6
|
||||
valid = len(a) >= 8 && start%2 == 0
|
||||
} else {
|
||||
valid = len(a) >= 6 && len(a)%6 == 0
|
||||
}
|
||||
if valid {
|
||||
for i := start; i < end; i += 2 {
|
||||
dx, dy = dx+a[i], dy+a[i+1]
|
||||
}
|
||||
for i := 0; i < start; i += 2 {
|
||||
dx, dy = dx+a[i], dy+a[i+1]
|
||||
}
|
||||
if end < len(a) {
|
||||
dx, dy = dx+a[end], dy+a[end+1]
|
||||
}
|
||||
}
|
||||
case 26, 27:
|
||||
valid = len(a) >= 4 && (len(a)%4 == 0 || len(a)%4 == 1)
|
||||
if valid {
|
||||
i := 0
|
||||
if len(a)%4 == 1 {
|
||||
if op == 26 {
|
||||
dx = a[0]
|
||||
} else {
|
||||
dy = a[0]
|
||||
}
|
||||
i++
|
||||
}
|
||||
for ; i < len(a); i += 4 {
|
||||
if op == 26 {
|
||||
dx, dy = dx+a[i+1], dy+a[i]+a[i+2]+a[i+3]
|
||||
} else {
|
||||
dx, dy = dx+a[i]+a[i+1]+a[i+3], dy+a[i+2]
|
||||
}
|
||||
}
|
||||
}
|
||||
case 30, 31:
|
||||
valid = len(a) >= 4 && (len(a)%4 == 0 || len(a)%4 == 1)
|
||||
if valid {
|
||||
horizontal := op == 31
|
||||
for i := 0; i+4 <= len(a); i += 4 {
|
||||
if horizontal {
|
||||
dx, dy = dx+a[i]+a[i+1], dy+a[i+2]+a[i+3]
|
||||
} else {
|
||||
dx, dy = dx+a[i+1]+a[i+3], dy+a[i]+a[i+2]
|
||||
}
|
||||
if i+5 == len(a) {
|
||||
if horizontal {
|
||||
dx += a[i+4]
|
||||
} else {
|
||||
dy += a[i+4]
|
||||
}
|
||||
}
|
||||
horizontal = !horizontal
|
||||
}
|
||||
}
|
||||
case 1234:
|
||||
valid = len(a) == 7
|
||||
if valid {
|
||||
dx = a[0] + a[1] + a[3] + a[4] + a[5] + a[6]
|
||||
}
|
||||
case 1235:
|
||||
valid = len(a) == 13
|
||||
if valid {
|
||||
for i := 0; i < 12; i += 2 {
|
||||
dx, dy = dx+a[i], dy+a[i+1]
|
||||
}
|
||||
}
|
||||
case 1236:
|
||||
valid = len(a) == 9
|
||||
if valid {
|
||||
dx = a[0] + a[2] + a[4] + a[5] + a[6] + a[8]
|
||||
}
|
||||
case 1237:
|
||||
valid = len(a) == 11
|
||||
if valid {
|
||||
for i := 0; i < 10; i += 2 {
|
||||
dx, dy = dx+a[i], dy+a[i+1]
|
||||
}
|
||||
if math.Abs(dx) > math.Abs(dy) {
|
||||
dx, dy = dx+a[10], 0
|
||||
} else {
|
||||
dx, dy = 0, dy+a[10]
|
||||
}
|
||||
}
|
||||
default:
|
||||
valid = false
|
||||
}
|
||||
if !valid || !finite(s.x+dx) || !finite(s.y+dy) {
|
||||
return fmt.Errorf("invalid Type2 path operands for operator %d", op)
|
||||
}
|
||||
if err := s.emit(op, a); err != nil {
|
||||
return err
|
||||
}
|
||||
s.x, s.y = s.x+dx, s.y+dy
|
||||
s.outX, s.outY = s.x+s.originX, s.y+s.originY
|
||||
s.args = s.args[:0]
|
||||
return nil
|
||||
}
|
||||
+43
-6
@@ -91,21 +91,21 @@ func (s *transparentEdgeImage64) Bounds() image.Rectangle { return s.rect }
|
||||
// 返回: color.Model 颜色模型
|
||||
func (s *transparentEdgeImage64) ColorModel() color.Model { return color.NRGBA64Model }
|
||||
|
||||
// At 读取十六位原始颜色,透明邻域使用最小非零透明度保留边缘颜色
|
||||
// NRGBA64At 读取十六位原始颜色,透明邻域使用最小非零透明度保留边缘颜色
|
||||
// 入参: x 横向坐标, y 纵向坐标
|
||||
// 返回: color.Color 非预乘颜色
|
||||
func (s *transparentEdgeImage64) At(x, y int) color.Color {
|
||||
// 返回: color.NRGBA64 非预乘颜色
|
||||
func (s *transparentEdgeImage64) NRGBA64At(x, y int) color.NRGBA64 {
|
||||
if !image.Pt(x, y).In(s.rect) {
|
||||
return color.NRGBA64{}
|
||||
}
|
||||
b := s.source.Bounds()
|
||||
sx, sy := b.Min.X+min(max(x-1, 0), b.Dx()-1), b.Min.Y+min(max(y-1, 0), b.Dy()-1)
|
||||
c := color.NRGBA64Model.Convert(s.source.At(sx, sy)).(color.NRGBA64)
|
||||
c := imageNRGBA64At(s.source, sx, sy)
|
||||
if c.A == 0 {
|
||||
var best color.NRGBA64
|
||||
for ny := max(sy-1, b.Min.Y); ny < min(sy+2, b.Max.Y); ny++ {
|
||||
for nx := max(sx-1, b.Min.X); nx < min(sx+2, b.Max.X); nx++ {
|
||||
v := color.NRGBA64Model.Convert(s.source.At(nx, ny)).(color.NRGBA64)
|
||||
v := imageNRGBA64At(s.source, nx, ny)
|
||||
if v.A > best.A {
|
||||
best = v
|
||||
}
|
||||
@@ -122,11 +122,16 @@ func (s *transparentEdgeImage64) At(x, y int) color.Color {
|
||||
return c
|
||||
}
|
||||
|
||||
// At 返回非预乘十六位像素颜色
|
||||
// 入参: x 横向坐标, y 纵向坐标
|
||||
// 返回: color.Color 像素颜色
|
||||
func (s *transparentEdgeImage64) At(x, y int) color.Color { return s.NRGBA64At(x, y) }
|
||||
|
||||
// RGBA64At 返回保留原采样精度的预乘颜色
|
||||
// 入参: x 横向坐标, y 纵向坐标
|
||||
// 返回: color.RGBA64 预乘颜色
|
||||
func (s *transparentEdgeImage64) RGBA64At(x, y int) color.RGBA64 {
|
||||
r, g, b, a := s.At(x, y).RGBA()
|
||||
r, g, b, a := s.NRGBA64At(x, y).RGBA()
|
||||
return color.RGBA64{R: uint16(r), G: uint16(g), B: uint16(b), A: uint16(a)}
|
||||
}
|
||||
|
||||
@@ -426,6 +431,38 @@ func imageNRGBAAt(img image.Image, x, y int) color.NRGBA {
|
||||
return color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA)
|
||||
}
|
||||
|
||||
// imageNRGBA64At 读取十六位非预乘像素,原生图像避免颜色装箱
|
||||
// 入参: img 图片对象, x X坐标, y Y坐标
|
||||
// 返回: color.NRGBA64 NRGBA64像素
|
||||
func imageNRGBA64At(img image.Image, x, y int) color.NRGBA64 {
|
||||
switch src := img.(type) {
|
||||
case *image.NRGBA64:
|
||||
return src.NRGBA64At(x, y)
|
||||
case *image.Gray16:
|
||||
c := src.Gray16At(x, y)
|
||||
return color.NRGBA64{R: c.Y, G: c.Y, B: c.Y, A: 0xffff}
|
||||
case *image.Alpha16:
|
||||
c := src.Alpha16At(x, y)
|
||||
if c.A == 0 {
|
||||
return color.NRGBA64{}
|
||||
}
|
||||
return color.NRGBA64{R: 0xffff, G: 0xffff, B: 0xffff, A: c.A}
|
||||
case *image.RGBA, *image.RGBA64:
|
||||
c := src.(image.RGBA64Image).RGBA64At(x, y)
|
||||
if c.A == 0 {
|
||||
return color.NRGBA64{}
|
||||
}
|
||||
r, g, b, a := uint32(c.R), uint32(c.G), uint32(c.B), uint32(c.A)
|
||||
if a != 0xffff {
|
||||
r = r * 0xffff / a
|
||||
g = g * 0xffff / a
|
||||
b = b * 0xffff / a
|
||||
}
|
||||
return color.NRGBA64{R: uint16(r), G: uint16(g), B: uint16(b), A: c.A}
|
||||
}
|
||||
return color.NRGBA64Model.Convert(img.At(x, y)).(color.NRGBA64)
|
||||
}
|
||||
|
||||
// transparentEdgeColor 获取透明像素相邻的可见颜色
|
||||
// 入参: img 图片对象, x X坐标, y Y坐标
|
||||
// 返回: color.NRGBA 颜色, bool 是否存在
|
||||
|
||||
Reference in new issue
Block a user