优化,阅读问题处理

This commit is contained in:
truewhile
2026-10-10 11:26:47 +08:00
parent 374ee28f01
commit 4ed2edab5b
36 changed files with 3921 additions and 140 deletions
+231
View File
@@ -0,0 +1,231 @@
package rule
import (
"bytes"
"encoding/binary"
"strings"
"testing"
)
// 字体混淆还原(ttf.go / queryttf.go)的测试:用合成的 TTF 覆盖 cmap format 0/4、
// loca 短格式、简单/复合字形,验证「错误字体 → 正确字体」的按字形还原。
// buildTestTTF 构造一个最小可解析的 TTF:
// cmap(format 4)把给定码点映射到指定字形下标,glyf 里每个字形一个方框。
func buildTestTTF(t *testing.T, entries map[rune]uint16) []byte {
t.Helper()
const numGlyphs = 8
// 每个字形一个简单方框:轮廓数 1,1 个点。
glyph := func() []byte {
g := make([]byte, 0, 20)
g = append(g, 0x00, 0x01) // numberOfContours = 1
g = append(g, 0, 0, 0, 0, 0, 0, 0, 0) // bbox
g = append(g, 0x00, 0x00) // endPtsOfContours[0] = 0
g = append(g, 0x00, 0x00) // instructionLength = 0
g = append(g, 0x01, 0x01) // flag: on-curve | x-short | y-short
g = append(g, 0x00, 0x00) // x=0, y=0
return g
}
glyfData := make([]byte, 0, 128)
offsets := make([]int, 0, numGlyphs+1)
for i := 0; i < numGlyphs; i++ {
offsets = append(offsets, len(glyfData))
if i == 0 {
continue // .notdef 空字形
}
g := glyph()
// 让不同字形的字节长度不同,轮廓字符串才能区分。
for j := 0; j < i-1; j++ {
g = append(g, 0x01, 0x00, 0x00) // 额外点
}
glyfData = append(glyfData, g...)
}
offsets = append(offsets, len(glyfData))
// loca(短格式,偏移/2)
loca := make([]byte, (numGlyphs+1)*2)
for i, off := range offsets {
binary.BigEndian.PutUint16(loca[i*2:], uint16(off/2))
}
// cmap:format 4 单段 + 结束段
var cmap bytes.Buffer
codes := make([]rune, 0, len(entries))
for cp := range entries {
codes = append(codes, cp)
}
// 按码点排序,构造连续单点段。
for i := 0; i < len(codes); i++ {
for j := i + 1; j < len(codes); j++ {
if codes[j] < codes[i] {
codes[i], codes[j] = codes[j], codes[i]
}
}
}
segCount := len(codes) + 1
endCodes := make([]uint16, 0, segCount)
startCodes := make([]uint16, 0, segCount)
idDeltas := make([]uint16, 0, segCount)
for _, cp := range codes {
endCodes = append(endCodes, uint16(cp))
startCodes = append(startCodes, uint16(cp))
idDeltas = append(idDeltas, uint16(int(entries[cp])-int(cp)))
}
endCodes = append(endCodes, 0xFFFF)
startCodes = append(startCodes, 0xFFFF)
idDeltas = append(idDeltas, 1)
rangeOffsets := make([]uint16, segCount) // 全 0:用 idDelta
sub := new(bytes.Buffer)
writeU16 := func(v uint16) { _ = binary.Write(sub, binary.BigEndian, v) }
length := 16 + segCount*8
writeU16(4) // format
writeU16(uint16(length)) // length
writeU16(0) // language
writeU16(uint16(segCount * 2)) // segCountX2
writeU16(0) // searchRange(解析器不校验)
writeU16(0) // entrySelector
writeU16(0) // rangeShift
for _, v := range endCodes {
writeU16(v)
}
writeU16(0) // reservedPad
for _, v := range startCodes {
writeU16(v)
}
for _, v := range idDeltas {
writeU16(v)
}
for _, v := range rangeOffsets {
writeU16(v)
}
subBytes := sub.Bytes()
// cmap 头 + 一个子表记录
cmap.Write([]byte{0, 0})
_ = binary.Write(&cmap, binary.BigEndian, uint16(1))
_ = binary.Write(&cmap, binary.BigEndian, uint16(3)) // platformID = Windows
_ = binary.Write(&cmap, binary.BigEndian, uint16(1)) // encodingID = Unicode BMP
_ = binary.Write(&cmap, binary.BigEndian, uint32(12))
cmap.Write(subBytes)
head := make([]byte, 54)
binary.BigEndian.PutUint16(head[50:], 0) // indexToLocFormat = 0(短 loca)
maxp := make([]byte, 6)
binary.BigEndian.PutUint16(maxp[4:], numGlyphs)
tables := []struct {
tag string
data []byte
}{
{"cmap", cmap.Bytes()},
{"glyf", glyfData},
{"loca", loca},
{"head", head},
{"maxp", maxp},
}
var out bytes.Buffer
out.Write([]byte{0x00, 0x01, 0x00, 0x00}) // sfntVersion
_ = binary.Write(&out, binary.BigEndian, uint16(len(tables)))
_ = binary.Write(&out, binary.BigEndian, uint16(0))
_ = binary.Write(&out, binary.BigEndian, uint16(0))
_ = binary.Write(&out, binary.BigEndian, uint16(0))
offset := 12 + len(tables)*16
offsetsTable := make([]int, len(tables))
for i, tb := range tables {
padded := tb.data
if len(padded)%4 != 0 {
padded = append(padded, make([]byte, 4-len(padded)%4)...)
}
offsetsTable[i] = offset
offset += len(padded)
}
for i, tb := range tables {
out.WriteString(tb.tag)
_ = binary.Write(&out, binary.BigEndian, uint32(0))
_ = binary.Write(&out, binary.BigEndian, uint32(offsetsTable[i]))
_ = binary.Write(&out, binary.BigEndian, uint32(len(tb.data)))
}
for _, tb := range tables {
out.Write(tb.data)
for out.Len()%4 != 0 {
out.WriteByte(0)
}
}
return out.Bytes()
}
// 解析出的码点 → 字形 → 码点映射与构造时一致。
func TestQueryTTFParseRoundTrip(t *testing.T) {
data := buildTestTTF(t, map[rune]uint16{
'A': 1, 'B': 2, 'C': 3, 'D': 4,
})
font, err := parseQueryTTFFont(data)
if err != nil {
t.Fatalf("解析字体失败: %v", err)
}
for cp, gid := range map[rune]uint16{'A': 1, 'B': 2, 'C': 3, 'D': 4} {
if got := font.unicodeToGlyphID[cp]; got != gid {
t.Fatalf("码点 %q 的字形下标 = %d,期望 %d", cp, got, gid)
}
}
if font.glyphToUnicode[font.unicodeToGlyph['A']] != 'A' {
t.Fatal("字形 → 码点映射不正确")
}
if font.unicodeToGlyph['A'] == font.unicodeToGlyph['B'] {
t.Fatal("不同码点的轮廓不应相同")
}
}
// replaceFont:错误字体把 A 渲染成 B 的字形,正确字体应把 A 还原成 B。
func TestReplaceFontRestoresText(t *testing.T) {
// 错误字体:码点 A 指向字形 2(也就是 B 的形状)。
errorFontData := buildTestTTF(t, map[rune]uint16{'A': 2, 'B': 3, 'C': 4, 'D': 5})
// 正确字体:码点 B 指向字形 2。
correctFontData := buildTestTTF(t, map[rune]uint16{'A': 1, 'B': 2, 'C': 3, 'D': 4})
errorFont, err := parseQueryTTFFont(errorFontData)
if err != nil {
t.Fatal(err)
}
correctFont, err := parseQueryTTFFont(correctFontData)
if err != nil {
t.Fatal(err)
}
// 页面上写的是 'A',实际字形是 B → 应还原为 'B'。
got := replaceFontText("A", errorFont, correctFont, false)
if got != "B" {
t.Fatalf("replaceFont 还原结果 = %q,期望 %q", got, "B")
}
// 空白与未知码点保持原样。
mixed := replaceFontText("A 中", errorFont, correctFont, false)
if !strings.HasPrefix(mixed, "B ") || !strings.HasSuffix(mixed, "中") {
t.Fatalf("混合文本处理异常: %q", mixed)
}
// filter=true 时删掉没有对应字形的字符。
filtered := replaceFontText("A中", errorFont, correctFont, true)
if filtered != "B" {
t.Fatalf("filter 结果 = %q,期望 %q", filtered, "B")
}
}
// 坏字体只应报错,不能 panic。
func TestQueryTTFBadFontNoPanic(t *testing.T) {
cases := [][]byte{
nil,
[]byte("not a font"),
[]byte("ttcf"),
append([]byte{0x00, 0x01, 0x00, 0x00, 0x00, 0x02}, make([]byte, 40)...),
}
for i, data := range cases {
if _, err := parseQueryTTFFont(data); err == nil {
t.Fatalf("坏字体 #%d 应返回错误", i)
}
}
// 截断的合法字体也不能 panic。
full := buildTestTTF(t, map[rune]uint16{'A': 1})
for cut := 1; cut < len(full); cut += 37 {
_, _ = parseQueryTTFFont(full[:cut])
}
}