feat(转换能力): 修复字体转换与复杂图形处理
build.yaml / build (push) Successful in 41s

This commit is contained in:
xiaoqidun committed 2026-09-28 11:06:00 +08:00
1 parent b5a52dafb8
commit 6d9f1fbe41
4 files changed
+31 -117

No files matched your search

+27 -89
View File
@@ -378,22 +378,6 @@ func sanitizeCFF(data []byte) ([]byte, error) {
delete(topDict, 1234)
delete(topDict, 15)
delete(topDict, 16)
topDict[18] = []float64{float64(privSize), 0}
var newCFF bytes.Buffer
newCFF.Write(data[:hdrSize])
newCFF.Write(nameIndexData)
dummyDict := make(map[int][]float64)
for k, v := range topDict {
dummyDict[k] = v
}
dummyDict[17] = []float64{0}
dummyDict[18] = []float64{float64(privSize), 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)
privateLen := privSize
var finalPrivData []byte
if len(privDictData) > 0 {
pDict := parseCFFDict(privDictData)
@@ -411,41 +395,8 @@ func sanitizeCFF(data []byte) ([]byte, error) {
}
}
finalPrivData = encodeCFFDict(pDict)
privateLen = len(finalPrivData)
}
localSubrsPos := privatePos + privateLen
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)
if newCFF.Len() != dataStart {
diff := newCFF.Len() - dataStart
charStringsPos += diff
privatePos += diff
localSubrsPos += 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)
if len(localSubrData) > 0 {
newCFF.Write(localSubrData)
}
return newCFF.Bytes(), nil
return encodeSanitizedCFF(data[:hdrSize], nameIndexData, topDict, stringIndexData, globalSubrIndexData, charStringsData, finalPrivData, localSubrData), nil
}
// normalizeCFFPrivate 移除旧版CFF中不生效的缺省字段,拒绝需要额外解释的取值
@@ -508,48 +459,35 @@ func sanitizeMultiFDCFF(data []byte, hdrSize int, nameIndexData []byte, topDict
return nil, err
}
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
return encodeSanitizedCFF(data[:hdrSize], nameIndexData, topDict, stringIndexData, globalSubrIndexData, charStringsData, finalPrivData, nil), nil
}
// encodeSanitizedCFF 按字典编码长度收敛偏移并组装规范化字体
// 入参: header 头部, names 名称索引, dict 顶层字典, strings 字符串索引, globals 全局子程序索引
// 入参: chars 字形索引, private 私有字典, locals 局部子程序索引
// 返回: []byte CFF字体数据
func encodeSanitizedCFF(header, names []byte, dict cffDict, strings, globals, chars, private, locals []byte) []byte {
dict[17] = []float64{0}
dict[18] = []float64{float64(len(private)), 0}
prefix := len(header) + len(names) + len(strings) + len(globals)
var top []byte
for {
top = encodeCFFIndex([][]byte{encodeCFFDict(dict)})
charOffset := prefix + len(top)
privateOffset := charOffset + len(chars)
if dict[17][0] == float64(charOffset) && dict[18][1] == float64(privateOffset) {
break
}
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)
dict[17][0] = float64(charOffset)
dict[18][1] = float64(privateOffset)
}
newCFF.Write(charStringsData)
newCFF.Write(finalPrivData)
return newCFF.Bytes(), nil
var out bytes.Buffer
out.Grow(prefix + len(top) + len(chars) + len(private) + len(locals))
for _, part := range [][]byte{header, names, top, strings, globals, chars, private, locals} {
out.Write(part)
}
return out.Bytes()
}
// parseCFFAndCountGlyphs 解析CFF头部并统计字形数量
-25
View File
@@ -24,31 +24,6 @@ import (
// 坐标以左上角为原点,单位为毫米,子路径从Move开始,Close回到当前子路径起点
type GeometryPath []GeometrySegment
// stationaryEndpoint 判断贝塞尔曲线端点是否具有零一阶导数
// 返回: bool 是否存在驻点端点
func (p GeometryPath) stationaryEndpoint() bool {
var current, start Point
for _, segment := range p {
switch segment.Verb {
case GeometryMove:
start = segment.End
case GeometryCubic:
if segment.Control1 == current || segment.Control2 == segment.End {
return true
}
case GeometryQuad:
if segment.Control1 == current || segment.Control1 == segment.End {
return true
}
case GeometryClose:
current = start
continue
}
current = segment.End
}
return false
}
// GeometryVerb 表示几何路径指令
type GeometryVerb uint8
+3
View File
@@ -19,6 +19,7 @@ import (
"image"
"image/color"
"math"
"runtime"
"github.com/xiaoqidun/pdfgo"
)
@@ -83,6 +84,7 @@ func (p *pdfImporter) compositingCache() *pdfCompositeCache {
}
// coverage 复用已编译的覆盖场景,跳过与当前区域无交集的图元
// 几何编译前让出调度,避免单线程运行时积累已失效的临时对象
// 入参: node 图元, stroke 是否描边
// 返回: image.Image 当前区域覆盖或空值, error 编译或渲染错误
func (c *pdfCompositor) coverage(node pdfCompositeNode, stroke bool) (image.Image, error) {
@@ -95,6 +97,7 @@ func (c *pdfCompositor) coverage(node pdfCompositeNode, stroke bool) (image.Imag
key := pdfCompositeKey{path: node.path, text: node.text, image: node.image, stroke: stroke}
geometry, ok := c.cache.geometry[key]
if !ok {
runtime.Gosched()
var err error
geometry.scene, geometry.box, err = c.importer.compileGroup(func(local *pdfImporter) error { return node.coverage(local, stroke) })
if err != nil {
+1 -3
View File
@@ -201,7 +201,7 @@ func (CanvasBackend) Combine(left, right GeometryPath, operation GeometryOperati
return *geometryFromCanvasPath(result), nil
}
// Stroke 将绝对长度虚线与描边转换为填充区域,驻点曲线按指定精度展开
// Stroke 将绝对长度虚线与描边转换为填充区域,曲线按指定精度展开
// 入参: path 页面路径, options 描边样式
// 返回: GeometryPath 描边区域, error 样式或路径错误
func (CanvasBackend) Stroke(path GeometryPath, options StrokeOptions) (GeometryPath, error) {
@@ -223,9 +223,7 @@ func (CanvasBackend) Stroke(path GeometryPath, options StrokeOptions) (GeometryP
style.applyLineJoin(options.Join, options.MiterLimit)
style.lineCap = pathLineCap(options.Cap, style.lineCap)
p = p.Dash(options.DashOffset, options.Dashes...)
if path.stationaryEndpoint() {
p = p.Flatten(tolerance)
}
p = p.Stroke(options.Width, style.lineCap, style.lineJoin, tolerance)
return *geometryFromCanvasPath(p), nil
}