diff --git a/go.mod b/go.mod index aa2c0f4..22c6afc 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,14 @@ module github.com/xiaoqidun/ofdgo go 1.26.0 require ( - github.com/emmansun/gmsm v0.44.1 + github.com/emmansun/gmsm v0.45.0 github.com/go-fonts/latin-modern v0.3.3 github.com/go-text/typesetting v0.3.5 github.com/mububoki/jpeg2000 v1.0.0 github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6 github.com/xiaoqidun/jbig2 v1.1.1 - github.com/xiaoqidun/pdfgo v0.0.0-20261005044242-0007b05b8143 + github.com/xiaoqidun/pdfgo v0.0.0-20261005090911-e5eb2af5dff1 golang.org/x/crypto v0.57.0 golang.org/x/image v0.46.0 golang.org/x/text v0.42.0 diff --git a/go.sum b/go.sum index 8e09f4c..854dbef 100644 --- a/go.sum +++ b/go.sum @@ -28,8 +28,8 @@ github.com/benoitkugler/textlayout v0.3.2/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7 github.com/benoitkugler/textlayout-testdata v0.1.1/go.mod h1:i/qZl09BbUOtd7Bu/W1CAubRwTWrEXWq6JwMkw8wYxo= github.com/benoitkugler/textprocessing v0.0.6 h1:obkMyj62GEPg3xUVYqROlCN22z1OleuZm6ULqX9Om1g= github.com/benoitkugler/textprocessing v0.0.6/go.mod h1:Io0gN08/PXEzrSOWFa88xHx2Xv3VjvLMY7H76YoI23A= -github.com/emmansun/gmsm v0.44.1 h1:zDTkdtLWFG0vCbhPV+k9pte14tix/eK71At9Iai9fP4= -github.com/emmansun/gmsm v0.44.1/go.mod h1:p6RIUta0/KboFHrOxr1x8q+pd8RZtdaTO7XNp0RmMQM= +github.com/emmansun/gmsm v0.45.0 h1:PU/6ObyR3g05YN3jn1L5GZDowzyAi8Xhkq4svzYNyiA= +github.com/emmansun/gmsm v0.45.0/go.mod h1:HgLSvmdePGLo+Fo2PNgdTQU6lqAepSyriSQJRpECY6w= github.com/go-fonts/latin-modern v0.3.3 h1:g2xNgI8yzdNzIVm+qvbMryB6yGPe0pSMss8QT3QwlJ0= github.com/go-fonts/latin-modern v0.3.3/go.mod h1:tHaiWDGze4EPB0Go4cLT5M3QzRY3peya09Z/8KSCrpY= github.com/go-text/typesetting v0.3.5 h1:XZPUooClHY0Vf/rFyUyuPRNEkawARaFzLMQcXLSEyPk= @@ -62,8 +62,8 @@ github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk= github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= github.com/xiaoqidun/jbig2 v1.1.1 h1:SjliS9ZO0e9HX5oYUfBuXrfFsXhWricrSz+XY4zORDI= github.com/xiaoqidun/jbig2 v1.1.1/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ= -github.com/xiaoqidun/pdfgo v0.0.0-20261005044242-0007b05b8143 h1:OELHUuQnFJHk9qyHQX2gzdMvSQNbnEyD70YeB5Eoldg= -github.com/xiaoqidun/pdfgo v0.0.0-20261005044242-0007b05b8143/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ= +github.com/xiaoqidun/pdfgo v0.0.0-20261005090911-e5eb2af5dff1 h1:AoxEA4tvcE+8tP3i4fE2f3It3DzbNBmvEVVMTp59sDM= +github.com/xiaoqidun/pdfgo v0.0.0-20261005090911-e5eb2af5dff1/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA= diff --git a/ofdgo_pdf_composite.go b/ofdgo_pdf_composite.go index 9f58937..79c529a 100644 --- a/ofdgo_pdf_composite.go +++ b/ofdgo_pdf_composite.go @@ -56,6 +56,7 @@ type pdfCompositor struct { gradients map[pdfGradientKey][]pdfShadingPixel transfers []*pdfgo.TransferFunction transferBlocked bool + shadingSource bool } // pdfCompositeKey 区分图元的填充与描边几何 @@ -83,16 +84,18 @@ type pdfCompositeColorSampler struct { // pdfCompositeCache 在单页内复用几何、图像分量及蒙版内容 type pdfCompositeCache struct { - geometry map[pdfCompositeKey]pdfCompositeGeometry - images map[*pdfgo.Image]*pdfgo.ImageComponents - imageMatrix map[*pdfgo.ImageMark]pdfgo.Matrix - masks map[*pdfgo.SoftMask][]pdfCompositeNode - meshes map[*pdfgo.MeshGradient][]pdfMeshTriangle - patterns map[pdfgo.Paint]*pdfCompositePattern - shadings map[pdfGradientKey]pdfCompositeGeometry - transfers map[pdfTransferKey]*pdfgo.TransferFunction - textDisjoint map[*pdfgo.GroupMark]bool - groups map[*pdfgo.GroupMark]Box + geometry map[pdfCompositeKey]pdfCompositeGeometry + images map[*pdfgo.Image]*pdfgo.ImageComponents + imageMatrix map[*pdfgo.ImageMark]pdfgo.Matrix + masks map[*pdfgo.SoftMask][]pdfCompositeNode + meshes map[*pdfgo.MeshGradient][]pdfMeshTriangle + patterns map[pdfgo.Paint]*pdfCompositePattern + shadingPatterns map[*pdfgo.ShadingPattern][]pdfCompositeNode + shadingSources map[pdfShadingSourceKey][]pdfCompositeNode + shadings map[pdfGradientKey]pdfCompositeGeometry + transfers map[pdfTransferKey]*pdfgo.TransferFunction + textDisjoint map[*pdfgo.GroupMark]bool + groups map[*pdfgo.GroupMark]Box } // compositingCache 按需创建单页合成缓存,页面切换时由导入器释放 @@ -531,6 +534,9 @@ func (n pdfCompositeNode) direct() bool { return true } s, fill, stroke := n.style() + if (n.image == nil || n.image.Image.ImageMask) && (fill && s.Fill.Shading != nil || stroke && s.Stroke.Shading != nil) { + return false + } if fill && stroke && (s.Fill.Alpha != 1 || s.Stroke.Alpha != 1 || s.SoftMask != nil) { return false } diff --git a/ofdgo_pdf_composite_draw.go b/ofdgo_pdf_composite_draw.go index c77f796..55ce157 100644 --- a/ofdgo_pdf_composite_draw.go +++ b/ofdgo_pdf_composite_draw.go @@ -138,13 +138,24 @@ func (c *pdfCompositor) drawPaint(node pdfCompositeNode, outline bool, paint pdf } } step := 25.4 / c.importer.rasterDPI - uniform := node.image == nil && paint.Axial == nil && paint.Radial == nil && paint.Function == nil && paint.Tiling == nil && paint.Mesh == nil + uniform := node.image == nil && paint.Axial == nil && paint.Radial == nil && paint.Function == nil && paint.Tiling == nil && paint.Mesh == nil && paint.Shading == nil compatible := overprint && !processOverprint && style.OverprintMode == 1 && uniform && paint.SourceSpace == "DeviceCMYK" && space.Model == "DeviceCMYK" && !space.Calibrated() var marked *[4]bool if processOverprint { marked = &processMask } var pattern []pdfCompositePixel + if paint.Shading != nil && (node.image == nil || node.image.Image.ImageMask) { + backdrop := pixels + if initial != nil { + backdrop = initial + } + pattern, err = c.shadingPattern(paint.Shading, node, outline, backdrop, space) + if err != nil { + return err + } + defer c.releasePixels(pattern) + } if paint.Tiling != nil && (node.image == nil || node.image.Image.ImageMask) { backdrop := pixels if initial != nil { @@ -199,11 +210,15 @@ func (c *pdfCompositor) drawPaint(node pdfCompositeNode, outline bool, paint pdf if pattern != nil { sample := pattern[index] values = sample.values - shape *= sample.shape + if paint.Shading != nil { + shape = sample.shape + } else { + shape *= sample.shape + } if sample.shape != 0 { opacity *= sample.alpha / sample.shape } - if node.image != nil { + if node.image != nil && paint.Shading == nil { point := c.inverse.Apply(pdfgo.Point{X: c.box.X + (float64(x)+.5)*step, Y: c.box.Y + (float64(y)+.5)*step}) var alpha float64 _, alpha, err = c.imageColor(node.image, point, space) @@ -226,7 +241,11 @@ func (c *pdfCompositor) drawPaint(node pdfCompositeNode, outline bool, paint pdf if imageShape { selectionShape = alpha } - opacity *= alpha + if c.shadingSource { + shape *= alpha + } else { + opacity *= alpha + } } } else if uniform { if !uniformReady { @@ -242,7 +261,11 @@ func (c *pdfCompositor) drawPaint(node pdfCompositeNode, outline bool, paint pdf if imageShape { selectionShape = alpha } - opacity *= alpha + if c.shadingSource { + shape *= alpha + } else { + opacity *= alpha + } } else { var visible bool if sampler != nil { @@ -651,7 +674,7 @@ func (c *pdfCompositor) imageColor(mark *pdfgo.ImageMark, point pdfgo.Point, spa } } if mark.Image.ImageMask { - if mark.Style.Fill.Tiling != nil { + if mark.Style.Fill.Tiling != nil || mark.Style.Fill.Shading != nil { return [4]float64{}, alpha * (1 - values[0]), nil } color, visible, err := pdfCompositeColor(mark.Style.Fill, mark.Matrix.Apply(point), space, mark.Style.RenderingIntent, mark.Style.ColorConversion) @@ -706,6 +729,9 @@ func pdfImageProcessColorants(image *pdfgo.Image) ([4]bool, bool, error) { // 入参: paint 画刷, point 页面坐标, space 混合空间, intent 渲染意图, conversion 设备转换函数 // 返回: [4]float64 混合分量, bool 是否着色, error 颜色错误 func pdfCompositeColor(paint pdfgo.Paint, point pdfgo.Point, space *pdfgo.ColorSpace, intent pdfgo.Name, conversion pdfgo.ColorConversion) ([4]float64, bool, error) { + if paint.Shading != nil { + return [4]float64{}, false, &pdfgo.UnsupportedError{Feature: "local shading pattern compositing"} + } if paint.Tiling != nil { return [4]float64{}, false, &pdfgo.UnsupportedError{Feature: "local tiling pattern compositing"} } diff --git a/ofdgo_pdf_composite_pattern.go b/ofdgo_pdf_composite_pattern.go index 3e61506..517f25f 100644 --- a/ofdgo_pdf_composite_pattern.go +++ b/ofdgo_pdf_composite_pattern.go @@ -294,6 +294,15 @@ func (p *pdfImporter) processOverprint(nodes []pdfCompositeNode) (bool, error) { return true, nil } for i, paint := range []pdfgo.Paint{style.Fill, style.Stroke} { + if paint.Shading != nil && (i == 0 && fill || i == 1 && stroke) { + nodes, err := p.compositeShadingPattern(paint.Shading) + if err != nil { + return false, err + } + if found, err := p.processOverprint(nodes); err != nil || found { + return found, err + } + } if i == 0 && !fill || i == 1 && !stroke || paint.Tiling == nil { continue } diff --git a/ofdgo_pdf_composite_shading.go b/ofdgo_pdf_composite_shading.go new file mode 100644 index 0000000..c75ddcc --- /dev/null +++ b/ofdgo_pdf_composite_shading.go @@ -0,0 +1,201 @@ +// 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 ( + "math" + + "github.com/xiaoqidun/pdfgo" +) + +// pdfShadingSourceKey 区分图案的使用几何及填充或描边 +type pdfShadingSourceKey struct { + pattern *pdfgo.ShadingPattern + geometry pdfCompositeKey +} + +// compositeShadingPattern 在页面内复用图案内部图元,不缓存依赖背景的像素 +// 入参: pattern 只读着色图案 +// 返回: []pdfCompositeNode 隐式挖空组, error 遍历或资源错误 +func (p *pdfImporter) compositeShadingPattern(pattern *pdfgo.ShadingPattern) ([]pdfCompositeNode, error) { + cache := p.compositingCache() + if nodes, found := cache.shadingPatterns[pattern]; found { + return nodes, nil + } + inverse, ok := p.matrix.Inverse() + if !ok { + return nil, &pdfgo.UnsupportedError{Feature: "shading pattern page transform"} + } + box := pdfBounds([]pdfgo.Point{ + inverse.Apply(pdfgo.Point{}), inverse.Apply(pdfgo.Point{X: p.pageWidth}), + inverse.Apply(pdfgo.Point{X: p.pageWidth, Y: p.pageHeight}), inverse.Apply(pdfgo.Point{Y: p.pageHeight}), + }) + nodes, err := p.collectCompositeNodes(func(visitor pdfgo.Visitor) error { + return pattern.Walk(p.ctx, pdfgo.Rectangle{XMin: box.X, YMin: box.Y, XMax: box.X + box.W, YMax: box.Y + box.H}, visitor) + }) + if err != nil { + return nil, err + } + if cache.shadingPatterns == nil { + cache.shadingPatterns = make(map[*pdfgo.ShadingPattern][]pdfCompositeNode) + } + cache.shadingPatterns[pattern] = nodes + return nodes, nil +} + +// shadingPattern 统一对象和图案的几何覆盖,按相交面积合成背景及内部着色 +// 入参: pattern 着色图案, node 使用图元, outline 是否描边, backdrop 初始背景, space 父级混合空间 +// 返回: []pdfCompositePixel 源颜色、形状及不透明度, error 状态或合成错误 +func (c *pdfCompositor) shadingPattern(pattern *pdfgo.ShadingPattern, node pdfCompositeNode, outline bool, backdrop []pdfCompositePixel, space *pdfgo.ColorSpace) ([]pdfCompositePixel, error) { + nodes, err := c.importer.shadingSources(pattern, node, outline) + if err != nil { + return nil, err + } + local := *c + local.transfers, local.shadingSource = nil, true + result, err := local.shadingSourcePixels(nodes[len(nodes)-1], backdrop, space) + if err != nil || len(nodes) == 1 { + return result, err + } + background, err := local.shadingSourcePixels(nodes[0], backdrop, space) + if err != nil { + c.releasePixels(result) + return nil, err + } + defer c.releasePixels(background) + coverage, err := c.coverage(nodes[0], outline) + if err != nil { + c.releasePixels(result) + return nil, err + } + step := 25.4 / c.importer.rasterDPI + for i := range result { + if i%pdfCompositeTileSize == 0 { + if err := c.importer.ctx.Err(); err != nil { + c.releasePixels(result) + return nil, err + } + } + shape := 0.0 + if coverage != nil { + _, _, _, a := coverage.At(i%c.width, i/c.width).RGBA() + shape = float64(a) / 65535 + } + if nodes[0].image != nil && shape != 0 { + point := c.inverse.Apply(pdfgo.Point{X: c.box.X + (float64(i%c.width)+.5)*step, Y: c.box.Y + (float64(i/c.width)+.5)*step}) + _, alpha, err := c.imageColor(nodes[0].image, point, space) + if err != nil { + c.releasePixels(result) + return nil, err + } + shape *= alpha + } + remaining := 1.0 + if shape != 0 { + remaining -= math.Min(1, result[i].shape/shape) + } + alpha := result[i].alpha + background[i].alpha*remaining + if alpha != 0 { + for j := 0; j < space.Components(); j++ { + result[i].values[j] = (result[i].values[j]*result[i].alpha + background[i].values[j]*background[i].alpha*remaining) / alpha + } + } + result[i].alpha, result[i].effect = alpha, alpha + result[i].shape += background[i].shape * remaining + } + return result, nil +} + +// shadingSources 将内部状态绑定到使用图元,几何和裁剪仅计算一次 +// 入参: pattern 只读着色图案, node 使用图元, outline 是否描边 +// 返回: []pdfCompositeNode 背景及着色图元, error 图案遍历错误 +func (p *pdfImporter) shadingSources(pattern *pdfgo.ShadingPattern, node pdfCompositeNode, outline bool) ([]pdfCompositeNode, error) { + cache := p.compositingCache() + key := pdfShadingSourceKey{pattern, pdfCompositeKey{path: node.path, text: node.text, image: node.image, stroke: outline}} + if sources, ok := cache.shadingSources[key]; ok { + return sources, nil + } + nodes, err := p.compositeShadingPattern(pattern) + if err != nil { + return nil, err + } + outer, _, _ := node.style() + sources := make([]pdfCompositeNode, 0, len(nodes[0].children)) + for _, child := range nodes[0].children { + style := child.path.Style + style.LineWidth, style.Cap, style.Join, style.MiterLimit = outer.LineWidth, outer.Cap, outer.Join, outer.MiterLimit + style.Dash, style.DashPhase, style.StrokeAdjust = outer.Dash, outer.DashPhase, outer.StrokeAdjust + style.Clips = append(append([]pdfgo.Path(nil), outer.Clips...), style.Clips...) + style.Stroke, style.StrokeOverprint = style.Fill, style.FillOverprint + source := node + if node.path != nil { + mark := *node.path + mark.Style, mark.Fill, mark.Stroke = style, !outline, outline + source.path = &mark + } else if node.text != nil { + mark := *node.text + mark.Style, mark.Mode = style, 0 + if outline { + mark.Mode = 1 + } + source.text = &mark + } else { + mark := *node.image + mark.Style = style + source.image = &mark + } + sources = append(sources, source) + } + if cache.shadingSources == nil { + cache.shadingSources = make(map[pdfShadingSourceKey][]pdfCompositeNode) + } + cache.shadingSources[key] = sources + return sources, nil +} + +// shadingSourcePixels 求值单个内部图元并移除初始背景贡献 +// 入参: node 内部图元, backdrop 初始背景, space 混合空间 +// 返回: []pdfCompositePixel 源颜色和覆盖, error 合成或取消错误 +func (c *pdfCompositor) shadingSourcePixels(node pdfCompositeNode, backdrop []pdfCompositePixel, space *pdfgo.ColorSpace) ([]pdfCompositePixel, error) { + result := c.acquirePixels(len(backdrop)) + complete := false + defer func() { + if !complete { + c.releasePixels(result) + } + }() + for i, pixel := range backdrop { + result[i] = pdfCompositePixel{values: pixel.values, alpha: pixel.alpha} + } + if err := c.drawMark(node, result, space); err != nil { + return nil, err + } + for i := range result { + if i%pdfCompositeTileSize == 0 { + if err := c.importer.ctx.Err(); err != nil { + return nil, err + } + } + pixel := &result[i] + if pixel.effect != 0 { + for j := 0; j < space.Components(); j++ { + pixel.values[j] = math.Max(0, math.Min(1, (pixel.values[j]*pixel.alpha-backdrop[i].values[j]*backdrop[i].alpha*(1-pixel.effect))/pixel.effect)) + } + } + pixel.alpha = pixel.effect + } + complete = true + return result, nil +} diff --git a/ofdgo_pdf_group.go b/ofdgo_pdf_group.go index 0ff7239..d1c480e 100644 --- a/ofdgo_pdf_group.go +++ b/ofdgo_pdf_group.go @@ -168,7 +168,7 @@ func (p *pdfImporter) groupPaths(mark pdfgo.GroupMark, walk func(pdfgo.Visitor) if stroke { color, overprint = path.Style.Stroke, path.Style.StrokeOverprint } - if color.Tiling != nil || color.Axial != nil || color.Radial != nil || color.Function != nil || color.Mesh != nil || overprint && pdfOverprintNeedsSeparation(color) { + if color.Tiling != nil || color.Shading != nil || color.Axial != nil || color.Radial != nil || color.Function != nil || color.Mesh != nil || overprint && pdfOverprintNeedsSeparation(color) { return fmt.Errorf("%w: patterned or overprinted group content", errPDFGroupRaster) } if color.Alpha == 0 { @@ -615,7 +615,7 @@ func (p *pdfImporter) maskClip(mask *pdfgo.SoftMask) (pdfgo.Path, error) { return err } paint := mark.Style.Fill - if !mark.Fill || mark.Stroke || paint.Alpha != 1 || paint.CMYK != nil || paint.Axial != nil || paint.Radial != nil || paint.Function != nil || paint.Mesh != nil || paint.Tiling != nil || paint.Space != nil && paint.Space.Calibrated() || paint.RGB[0] != paint.RGB[1] || paint.RGB[1] != paint.RGB[2] || mark.Style.SoftMask != nil || !pdfNormalBlend(mark.Style.BlendMode) { + if !mark.Fill || mark.Stroke || paint.Alpha != 1 || paint.CMYK != nil || paint.Axial != nil || paint.Radial != nil || paint.Function != nil || paint.Mesh != nil || paint.Tiling != nil || paint.Shading != nil || paint.Space != nil && paint.Space.Calibrated() || paint.RGB[0] != paint.RGB[1] || paint.RGB[1] != paint.RGB[2] || mark.Style.SoftMask != nil || !pdfNormalBlend(mark.Style.BlendMode) { return &pdfgo.UnsupportedError{Feature: "nonbinary mask graphic"} } visible, err := opacity(paint.RGB[0]) diff --git a/ofdgo_pdf_object.go b/ofdgo_pdf_object.go index 9ec7ea7..8467107 100644 --- a/ofdgo_pdf_object.go +++ b/ofdgo_pdf_object.go @@ -94,7 +94,7 @@ func (p *pdfImporter) image(mark pdfgo.ImageMark) error { if err := p.flushPath(); err != nil { return err } - if mark.Image.ImageMask && (mark.Style.Fill.Axial != nil || mark.Style.Fill.Radial != nil || mark.Style.Fill.Function != nil || mark.Style.Fill.Mesh != nil) { + if mark.Image.ImageMask && (mark.Style.Fill.Axial != nil || mark.Style.Fill.Radial != nil || mark.Style.Fill.Function != nil || mark.Style.Fill.Mesh != nil || mark.Style.Fill.Shading != nil) { if p.warning != nil { return p.compositeRegion(nil, pdfCompositeNode{image: &mark}, &pdfgo.ColorSpace{Model: "DeviceRGB"}, false) } diff --git a/ofdgo_pdf_shading.go b/ofdgo_pdf_shading.go index b6defb4..e3a6ef0 100644 --- a/ofdgo_pdf_shading.go +++ b/ofdgo_pdf_shading.go @@ -81,6 +81,9 @@ func pdfGradientStopsError(stops []pdfgo.GradientStop, space *pdfgo.ColorSpace) // 入参: paint 画刷 // 返回: error 不可等价表达的渐变 func pdfGradientError(paint pdfgo.Paint) error { + if paint.Shading != nil { + return &pdfgo.UnsupportedError{Feature: "shading pattern state compositing"} + } if paint.Function != nil { return &pdfgo.UnsupportedError{Feature: "function shading conversion"} } diff --git a/ofdgo_pdf_transfer.go b/ofdgo_pdf_transfer.go index 4d7eeea..4a6bd33 100644 --- a/ofdgo_pdf_transfer.go +++ b/ofdgo_pdf_transfer.go @@ -81,6 +81,12 @@ func (c *pdfCompositor) selectTransfer(node pdfCompositeNode, outline bool) (*pd if style.Transfer == nil { return nil, nil } + if pattern := paint.Shading; pattern != nil && (node.image == nil || node.image.Image.ImageMask) { + inner := pattern.Style + if inner.Fill.Alpha != 1 || inner.SoftMask != nil || !pdfNormalBlend(inner.BlendMode) { + return nil, nil + } + } if node.image != nil { soft, err := node.image.Image.HasSoftMask() if err != nil || soft { @@ -148,6 +154,12 @@ func (p *pdfImporter) transferOpaque(node pdfCompositeNode) (bool, error) { } } for i, paint := range [2]pdfgo.Paint{style.Fill, style.Stroke} { + if pattern := paint.Shading; pattern != nil && (i == 0 && fill || i == 1 && stroke) { + inner := pattern.Style + if inner.Fill.Alpha != 1 || inner.SoftMask != nil || !pdfNormalBlend(inner.BlendMode) { + return false, nil + } + } if i == 0 && !fill || i == 1 && !stroke || paint.Tiling == nil { continue }