From 6d9f1fbe4160aa085c6074c46764f784953caa3d Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Mon, 28 Sep 2026 11:06:00 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=BD=AC=E6=8D=A2=E8=83=BD=E5=8A=9B):=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=AD=97=E4=BD=93=E8=BD=AC=E6=8D=A2=E4=B8=8E?= =?UTF-8?q?=E5=A4=8D=E6=9D=82=E5=9B=BE=E5=BD=A2=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ofdgo_font_cff.go | 116 ++++++++-------------------------- ofdgo_geom_path.go | 25 -------- ofdgo_pdf_composite.go | 3 + ofdgo_renderer_geom_canvas.go | 6 +- 4 files changed, 32 insertions(+), 118 deletions(-) diff --git a/ofdgo_font_cff.go b/ofdgo_font_cff.go index c9cbc29..0206ee8 100644 --- a/ofdgo_font_cff.go +++ b/ofdgo_font_cff.go @@ -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 + } + dict[17][0] = float64(charOffset) + dict[18][1] = float64(privateOffset) } - 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) + 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) } - newCFF.Write(charStringsData) - newCFF.Write(finalPrivData) - return newCFF.Bytes(), nil + return out.Bytes() } // parseCFFAndCountGlyphs 解析CFF头部并统计字形数量 diff --git a/ofdgo_geom_path.go b/ofdgo_geom_path.go index 5713915..2cb96c7 100644 --- a/ofdgo_geom_path.go +++ b/ofdgo_geom_path.go @@ -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 diff --git a/ofdgo_pdf_composite.go b/ofdgo_pdf_composite.go index 966bc27..a0a5fb7 100644 --- a/ofdgo_pdf_composite.go +++ b/ofdgo_pdf_composite.go @@ -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 { diff --git a/ofdgo_renderer_geom_canvas.go b/ofdgo_renderer_geom_canvas.go index 016b26b..cad605f 100644 --- a/ofdgo_renderer_geom_canvas.go +++ b/ofdgo_renderer_geom_canvas.go @@ -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.Flatten(tolerance) p = p.Stroke(options.Width, style.lineCap, style.lineJoin, tolerance) return *geometryFromCanvasPath(p), nil }