mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-08-30 12:12:40 +08:00
feat(字体渲染): 强化字体清洗能力
This commit is contained in:
+389
-1
@@ -134,7 +134,7 @@ func sanitizeCFF(data []byte) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
fdCount, _ := getCFFIndexCount(data, fdArrOff)
|
fdCount, _ := getCFFIndexCount(data, fdArrOff)
|
||||||
if fdCount != 1 {
|
if fdCount != 1 {
|
||||||
return nil, fmt.Errorf("complex cid (fdarray count=%d) not supported", fdCount)
|
return sanitizeMultiFDCFF(data, hdrSize, nameIndexData, topDict, stringIndexData, globalSubrIndexData, fdArrOff, fdCount)
|
||||||
}
|
}
|
||||||
fontDictData, _ := getCFFIndexData(data, fdArrOff)
|
fontDictData, _ := getCFFIndexData(data, fdArrOff)
|
||||||
fontDict := parseCFFDict(fontDictData)
|
fontDict := parseCFFDict(fontDictData)
|
||||||
@@ -246,6 +246,92 @@ func sanitizeCFF(data []byte) ([]byte, error) {
|
|||||||
return newCFF.Bytes(), nil
|
return newCFF.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sanitizeMultiFDCFF 清洗多 FD 的 CID CFF 数据
|
||||||
|
// 入参: data 原始CFF数据, hdrSize 头部大小, nameIndexData 名称索引, topDict 顶层字典
|
||||||
|
// 入参: stringIndexData 字符串索引, globalSubrIndexData 全局子程序索引, fdArrOff FDArray偏移, fdCount FD数量
|
||||||
|
// 返回: []byte 清洗后的CFF数据, error 错误信息
|
||||||
|
func sanitizeMultiFDCFF(data []byte, hdrSize int, nameIndexData []byte, topDict cffDict, stringIndexData []byte, globalSubrIndexData []byte, fdArrOff int, fdCount int) ([]byte, error) {
|
||||||
|
charStringsOffs, ok := topDict[17]
|
||||||
|
if !ok || len(charStringsOffs) == 0 {
|
||||||
|
return nil, fmt.Errorf("missing charstrings")
|
||||||
|
}
|
||||||
|
charStringsOff := int(charStringsOffs[0])
|
||||||
|
charStrings := readCFFIndexItems(data, charStringsOff)
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
inlined[gid] = removeType2Hints(inlineType2CharString(cs, localSubrs[fd], globalSubrs, 0))
|
||||||
|
}
|
||||||
|
delete(topDict, 1230)
|
||||||
|
delete(topDict, 1236)
|
||||||
|
delete(topDict, 1237)
|
||||||
|
delete(topDict, 1234)
|
||||||
|
delete(topDict, 15)
|
||||||
|
delete(topDict, 16)
|
||||||
|
privateDict := make(cffDict)
|
||||||
|
if len(privateDicts) > 0 {
|
||||||
|
for k, v := range privateDicts[0] {
|
||||||
|
privateDict[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(privateDict, 19)
|
||||||
|
finalPrivData := encodeCFFDict(privateDict)
|
||||||
|
privateLen := len(finalPrivData)
|
||||||
|
charStringsData := encodeCFFIndex(inlined)
|
||||||
|
topDict[18] = []float64{float64(privateLen), 0}
|
||||||
|
var newCFF bytes.Buffer
|
||||||
|
dummyDict := make(cffDict)
|
||||||
|
for k, v := range topDict {
|
||||||
|
dummyDict[k] = v
|
||||||
|
}
|
||||||
|
dummyDict[17] = []float64{0}
|
||||||
|
dummyDict[18] = []float64{float64(privateLen), 0}
|
||||||
|
dummyTopData := encodeCFFDict(dummyDict)
|
||||||
|
topIdxSize := 2 + 1 + 8 + len(dummyTopData)
|
||||||
|
dataStart := hdrSize + len(nameIndexData) + topIdxSize + len(stringIndexData) + len(globalSubrIndexData)
|
||||||
|
charStringsPos := dataStart
|
||||||
|
privatePos := charStringsPos + len(charStringsData)
|
||||||
|
topDict[17] = []float64{float64(charStringsPos)}
|
||||||
|
topDict[18] = []float64{float64(privateLen), float64(privatePos)}
|
||||||
|
finalTopData := encodeCFFDict(topDict)
|
||||||
|
topIndex := encodeCFFIndex([][]byte{finalTopData})
|
||||||
|
newCFF.Write(data[:hdrSize])
|
||||||
|
newCFF.Write(nameIndexData)
|
||||||
|
newCFF.Write(topIndex)
|
||||||
|
newCFF.Write(stringIndexData)
|
||||||
|
newCFF.Write(globalSubrIndexData)
|
||||||
|
if newCFF.Len() != dataStart {
|
||||||
|
diff := newCFF.Len() - dataStart
|
||||||
|
charStringsPos += diff
|
||||||
|
privatePos += diff
|
||||||
|
topDict[17] = []float64{float64(charStringsPos)}
|
||||||
|
topDict[18] = []float64{float64(privateLen), float64(privatePos)}
|
||||||
|
finalTopData = encodeCFFDict(topDict)
|
||||||
|
topIndex = encodeCFFIndex([][]byte{finalTopData})
|
||||||
|
newCFF.Reset()
|
||||||
|
newCFF.Write(data[:hdrSize])
|
||||||
|
newCFF.Write(nameIndexData)
|
||||||
|
newCFF.Write(topIndex)
|
||||||
|
newCFF.Write(stringIndexData)
|
||||||
|
newCFF.Write(globalSubrIndexData)
|
||||||
|
}
|
||||||
|
newCFF.Write(charStringsData)
|
||||||
|
newCFF.Write(finalPrivData)
|
||||||
|
return newCFF.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
// parseCFFAndCountGlyphs 解析 CFF 头部并统计字形数量
|
// parseCFFAndCountGlyphs 解析 CFF 头部并统计字形数量
|
||||||
// 入参: data CFF数据
|
// 入参: data CFF数据
|
||||||
// 返回: int 字形数量, error 错误信息
|
// 返回: int 字形数量, error 错误信息
|
||||||
@@ -579,6 +665,308 @@ func getCFFIndexData(data []byte, offset int) ([]byte, int) {
|
|||||||
return data[start : start+length], size
|
return data[start : start+length], size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readCFFIndexItems 读取 CFF 索引中的所有数据项
|
||||||
|
// 入参: data CFF数据, offset 索引偏移
|
||||||
|
// 返回: [][]byte 数据项列表
|
||||||
|
func readCFFIndexItems(data []byte, offset int) [][]byte {
|
||||||
|
count, _ := getCFFIndexCount(data, offset)
|
||||||
|
if count == 0 || offset+3 > len(data) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
offSize := int(data[offset+2])
|
||||||
|
if offSize < 1 || offSize > 4 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
dataStart := offset + 3 + (count+1)*offSize
|
||||||
|
items := make([][]byte, 0, count)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
p1 := offset + 3 + i*offSize
|
||||||
|
p2 := p1 + offSize
|
||||||
|
if p2+offSize > len(data) {
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
off1 := readCFFOffset(data, p1, offSize)
|
||||||
|
off2 := readCFFOffset(data, p2, offSize)
|
||||||
|
start := dataStart + off1 - 1
|
||||||
|
length := off2 - off1
|
||||||
|
if start < 0 || length < 0 || start+length > len(data) {
|
||||||
|
items = append(items, nil)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
items = append(items, data[start:start+length])
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseCFFFDSelect 解析 CID CFF 的 FDSelect
|
||||||
|
// 入参: data CFF数据, offset FDSelect偏移, numGlyphs 字形数量
|
||||||
|
// 返回: []int 字形对应的FD索引
|
||||||
|
func parseCFFFDSelect(data []byte, offset int, numGlyphs int) []int {
|
||||||
|
result := make([]int, numGlyphs)
|
||||||
|
if offset <= 0 || offset >= len(data) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
format := data[offset]
|
||||||
|
pos := offset + 1
|
||||||
|
switch format {
|
||||||
|
case 0:
|
||||||
|
for i := 0; i < numGlyphs && pos+i < len(data); i++ {
|
||||||
|
result[i] = int(data[pos+i])
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
if pos+2 > len(data) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
nRanges := int(binary.BigEndian.Uint16(data[pos:]))
|
||||||
|
pos += 2
|
||||||
|
ranges := make([]struct {
|
||||||
|
first int
|
||||||
|
fd int
|
||||||
|
}, 0, nRanges)
|
||||||
|
for i := 0; i < nRanges && pos+3 <= len(data); i++ {
|
||||||
|
first := int(binary.BigEndian.Uint16(data[pos:]))
|
||||||
|
fd := int(data[pos+2])
|
||||||
|
ranges = append(ranges, struct {
|
||||||
|
first int
|
||||||
|
fd int
|
||||||
|
}{first: first, fd: fd})
|
||||||
|
pos += 3
|
||||||
|
}
|
||||||
|
if pos+2 > len(data) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
sentinel := int(binary.BigEndian.Uint16(data[pos:]))
|
||||||
|
for i, item := range ranges {
|
||||||
|
end := sentinel
|
||||||
|
if i+1 < len(ranges) {
|
||||||
|
end = ranges[i+1].first
|
||||||
|
}
|
||||||
|
if end > numGlyphs {
|
||||||
|
end = numGlyphs
|
||||||
|
}
|
||||||
|
for gid := item.first; gid < end; gid++ {
|
||||||
|
if gid >= 0 && gid < numGlyphs {
|
||||||
|
result[gid] = item.fd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
type type2Operand struct {
|
||||||
|
value int
|
||||||
|
outStart int
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 偏移量
|
||||||
|
func cffSubrBias(count int) int {
|
||||||
|
if count < 1240 {
|
||||||
|
return 107
|
||||||
|
}
|
||||||
|
if count < 33900 {
|
||||||
|
return 1131
|
||||||
|
}
|
||||||
|
return 32768
|
||||||
|
}
|
||||||
|
|
||||||
// parseCFFWidths 从 CFF 数据中解析 Glyph 宽度
|
// parseCFFWidths 从 CFF 数据中解析 Glyph 宽度
|
||||||
// 入参: data CFF数据, numGlyphs 字形数量
|
// 入参: data CFF数据, numGlyphs 字形数量
|
||||||
// 返回: []uint16 宽度列表, error 错误信息
|
// 返回: []uint16 宽度列表, error 错误信息
|
||||||
|
|||||||
Reference in New Issue
Block a user