From b028a5d7c7a95912836060d8357fb9831056a573 Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Mon, 5 Oct 2026 22:57:00 +0800 Subject: [PATCH] =?UTF-8?q?refactor(=E9=87=8D=E6=9E=84=E6=8F=90=E5=8D=87):?= =?UTF-8?q?=20=E4=BC=98=E5=8C=96=E5=9B=BE=E6=A1=88=E7=9D=80=E8=89=B2?= =?UTF-8?q?=E9=99=8D=E4=BD=8E=E5=86=85=E5=AD=98=E5=8D=A0=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 2 +- go.sum | 4 +- ofdgo_pdf.go | 61 +++++++++------- ofdgo_pdf_clip.go | 12 +++- ofdgo_pdf_composite.go | 6 +- ofdgo_pdf_composite_draw.go | 46 ++++++++++-- ofdgo_pdf_composite_pattern.go | 23 +++++- ofdgo_pdf_mesh.go | 35 +++++++-- ofdgo_pdf_pattern.go | 128 +++++++++++++++++++++++++++++++++ ofdgo_pdf_shading.go | 73 +++++++++---------- 10 files changed, 310 insertions(+), 80 deletions(-) create mode 100644 ofdgo_pdf_pattern.go diff --git a/go.mod b/go.mod index 22c6afc..2ec1f33 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( 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-20261005090911-e5eb2af5dff1 + github.com/xiaoqidun/pdfgo v0.0.0-20261005144606-00d3cdef7848 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 854dbef..6094822 100644 --- a/go.sum +++ b/go.sum @@ -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-20261005090911-e5eb2af5dff1 h1:AoxEA4tvcE+8tP3i4fE2f3It3DzbNBmvEVVMTp59sDM= -github.com/xiaoqidun/pdfgo v0.0.0-20261005090911-e5eb2af5dff1/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ= +github.com/xiaoqidun/pdfgo v0.0.0-20261005144606-00d3cdef7848 h1:jacPfuuqF5qQVchUp935MHaE75ubIRBDjpJgEJD0Hk4= +github.com/xiaoqidun/pdfgo v0.0.0-20261005144606-00d3cdef7848/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.go b/ofdgo_pdf.go index c00b93d..30cb93c 100644 --- a/ofdgo_pdf.go +++ b/ofdgo_pdf.go @@ -99,6 +99,7 @@ type pdfImporter struct { compositeCache *pdfCompositeCache compositeNodes []pdfCompositeNode compositeSpace *pdfgo.ColorSpace + patternCells *renderCache[pdfPatternCellKey, *Pattern] annotationData []byte resolveFile pdfgo.FileResolver resolveReference pdfgo.ReferenceResolver @@ -210,6 +211,7 @@ func ImportPDF(ctx context.Context, source io.ReaderAt, size int64, options PDFI importer.compositeCache = nil importer.compositeNodes = nil importer.compositeSpace = nil + importer.patternCells = nil importer.clipTexts = make(map[*pdfgo.TextClip][]TextObject) importer.clipPaths = nil importer.annotationData = nil @@ -457,7 +459,7 @@ func (p *pdfImporter) pathDataContext(ctx context.Context, path pdfgo.Path, orig return string(data), nil } -// clips 保持裁剪路径的交集,不随对象CTM重复变换 +// clips 复用只读裁剪几何,以区域CTM保留对象位移及路径交集 // 入参: paths 裁剪路径, origin 对象边界 // 返回: *Clips OFD裁剪区域,无裁剪时为空, error 未解析的裁剪字形 func (p *pdfImporter) clips(paths []pdfgo.Path, origin Box) (*Clips, error) { @@ -465,23 +467,24 @@ func (p *pdfImporter) clips(paths []pdfgo.Path, origin Box) (*Clips, error) { return nil, nil } flag := false - clips := &Clips{TransFlag: &flag} - for _, path := range paths { + clips := &Clips{TransFlag: &flag, Clip: make([]Clip, len(paths))} + areas := make([]ClipArea, len(paths)) + translation := "" + if origin.X != 0 || origin.Y != 0 { + translation = pdfNumbers(1, 0, 0, 1, -origin.X, -origin.Y) + } + for index, path := range paths { if err := p.ctx.Err(); err != nil { return nil, err } - area := ClipArea{} + area := &areas[index] + area.CTM = translation if len(path.Segments) != 0 { encoded, err := p.clipPath(path) if err != nil { return nil, err } - box := encoded.box - rule := "NonZero" - if path.EvenOdd { - rule = "Even-Odd" - } - area.Path = append(area.Path, PathObject{Boundary: pdfBoundary(Box{box.X - origin.X, box.Y - origin.Y, box.W, box.H}), AbbreviatedData: encoded.data, Rule: rule}) + area.Path = encoded.path[:1:1] } for _, mark := range path.Text { objects, ok := p.clipTexts[mark] @@ -498,16 +501,13 @@ func (p *pdfImporter) clips(paths []pdfgo.Path, origin Box) (*Clips, error) { p.report.TextObjects = textCount objects = p.clipTexts[mark] } - for _, object := range objects { - box, err := ParseBox(object.Boundary) - if err != nil { - return nil, err - } - object.Boundary = pdfBoundary(Box{box.X - origin.X, box.Y - origin.Y, box.W, box.H}) - area.Text = append(area.Text, object) + if len(path.Text) == 1 { + area.Text = objects[:len(objects):len(objects)] + } else { + area.Text = append(area.Text, objects...) } } - clips.Clip = append(clips.Clip, Clip{Area: []ClipArea{area}}) + clips.Clip[index].Area = areas[index : index+1 : index+1] } return clips, nil } @@ -736,10 +736,10 @@ func (p *pdfImporter) paintColor(paint pdfgo.Paint, box Box) (*FillColor, error) return color, nil } -// tilingPattern 保留PDF图案单元与页面坐标之间的仿射关系 +// buildTilingPattern 保留PDF图案单元与页面坐标之间的仿射关系 // 入参: source 平铺图案, base 无色图案基色 // 返回: *Pattern OFD图案, error 转换错误 -func (p *pdfImporter) tilingPattern(source *pdfgo.TilingPattern, base pdfgo.Paint) (*Pattern, error) { +func (p *pdfImporter) buildTilingPattern(source *pdfgo.TilingPattern, base pdfgo.Paint) (*Pattern, error) { const unit = 25.4 / 72 box := source.BBox width, height := box.XMax-box.XMin, box.YMax-box.YMin @@ -753,6 +753,10 @@ func (p *pdfImporter) tilingPattern(source *pdfgo.TilingPattern, base pdfgo.Pain cell := *p cell.matrix = pdfgo.Matrix{unit, 0, 0, -unit, -box.XMin * unit, box.YMax * unit} cell.pageWidth, cell.pageHeight = width*unit, height*unit + cell.rasterDPI *= m.MaxScale() + if !finite(cell.rasterDPI) || cell.rasterDPI < 0 { + return nil, fmt.Errorf("invalid PDF tiling pattern raster scale") + } cell.objects, cell.pendingPath = nil, nil visitor := pdfgo.Visitor{Path: cell.path, Text: cell.text, Image: cell.image, Reference: p.referencePage} visitor.Group = func(mark pdfgo.GroupMark, walk func(pdfgo.Visitor) error) error { @@ -769,24 +773,31 @@ func (p *pdfImporter) tilingPattern(source *pdfgo.TilingPattern, base pdfgo.Pain return nil, err } pattern.CellContent.Objects = objects - if width > xstep || height > ystep { + if len(objects) != 0 && (width > xstep || height > ystep) { columns, rows := math.Ceil(width/xstep), math.Ceil(height/ystep) - if !finite(columns*rows) || columns*rows > float64(int(^uint(0)>>1)) { + if !finite(columns*rows) || columns*rows >= float64(uint(^uint(0)>>1)+1) { return nil, fmt.Errorf("PDF tiling pattern instance count overflow") } bounds := make([]Box, len(objects)) for index, object := range objects { + boundary := "" if object.Type == "PathObject" && object.PathObject.Stroke != nil && !*object.PathObject.Stroke { + boundary = object.PathObject.Boundary + } else if object.Type == "ImageObject" && object.ImageObject.Border == nil { + boundary = object.ImageObject.Boundary + } + if boundary != "" { var err error - bounds[index], err = ParseBox(object.PathObject.Boundary) + bounds[index], err = ParseBox(boundary) if err != nil { return nil, err } } } + left, right, top, bottom := pdfPatternInstanceRange(columns, rows, bounds, pattern.Width, pattern.Height, xstep*unit, ystep*unit) var expanded pdfObjectBuffer - for row := 1 - int(rows); row <= 0; row++ { - for column := 1 - int(columns); column <= 0; column++ { + for row := top; row <= bottom; row++ { + for column := left; column <= right; column++ { if err := p.ctx.Err(); err != nil { return nil, err } diff --git a/ofdgo_pdf_clip.go b/ofdgo_pdf_clip.go index e1e1bcb..ae12434 100644 --- a/ofdgo_pdf_clip.go +++ b/ofdgo_pdf_clip.go @@ -23,10 +23,11 @@ import ( "github.com/xiaoqidun/pdfgo" ) -// pdfClipPath 保存不可变的裁剪路径编码,不缓存对象位移或文字裁剪 +// pdfClipPath 保存导入器只读的裁剪几何,不缓存对象位移或文字裁剪 type pdfClipPath struct { box Box data string + path *[1]PathObject } // pdfClipPathKey 按路径完整内容与变换生成键,不依赖可变切片的地址 @@ -94,7 +95,12 @@ func (p *pdfImporter) clipPath(path pdfgo.Path) (pdfClipPath, error) { if err != nil { return pdfClipPath{}, err } - result := pdfClipPath{box: box, data: data} - p.clipPaths.put(key, result, len(data)+256) + rule := "NonZero" + if path.EvenOdd { + rule = "Even-Odd" + } + boundary := pdfBoundary(box) + result := pdfClipPath{box: box, data: data, path: &[1]PathObject{{Boundary: boundary, AbbreviatedData: data, Rule: rule}}} + p.clipPaths.put(key, result, len(data)+len(boundary)+512) return result, nil } diff --git a/ofdgo_pdf_composite.go b/ofdgo_pdf_composite.go index 79c529a..199bded 100644 --- a/ofdgo_pdf_composite.go +++ b/ofdgo_pdf_composite.go @@ -73,13 +73,17 @@ type pdfCompositeGeometry struct { box Box } -// pdfCompositeColorSampler 在单次绘制内复用完全相同参数的轴向颜色,不近似函数 +// pdfCompositeColorSampler 复用渐变几何和相同轴向参数的颜色,不近似函数 type pdfCompositeColorSampler struct { paint pdfgo.Paint space *pdfgo.ColorSpace intent pdfgo.Name conversion pdfgo.ColorConversion values map[float64][4]float64 + position pdfgo.GradientPosition + converter pdfgo.ColorConverter + prepared bool + converted bool } // pdfCompositeCache 在单页内复用几何、图像分量及蒙版内容 diff --git a/ofdgo_pdf_composite_draw.go b/ofdgo_pdf_composite_draw.go index 55ce157..2d1aaff 100644 --- a/ofdgo_pdf_composite_draw.go +++ b/ofdgo_pdf_composite_draw.go @@ -182,8 +182,10 @@ func (c *pdfCompositor) drawPaint(node pdfCompositeNode, outline bool, paint pdf var uniformValues [4]float64 uniformReady := false var sampler *pdfCompositeColorSampler - if g := paint.Axial; node.image == nil && shading == nil && g != nil && g.Bounds == nil && g.Background == nil && - (g.Start.Y == g.End.Y && c.inverse[2] == 0 || g.Start.X == g.End.X && c.inverse[1] == 0) { + if g := paint.Axial; node.image == nil && shading == nil && g != nil && g.Bounds == nil && g.Background == nil { + sampler = &pdfCompositeColorSampler{paint: paint, space: space, intent: style.RenderingIntent, conversion: style.ColorConversion} + } + if g := paint.Radial; node.image == nil && shading == nil && g != nil && g.Bounds == nil && g.Background == nil { sampler = &pdfCompositeColorSampler{paint: paint, space: space, intent: style.RenderingIntent, conversion: style.ColorConversion} } geometry, err := c.geometry(node, outline) @@ -826,18 +828,50 @@ func pdfCompositeColor(paint pdfgo.Paint, point pdfgo.Point, space *pdfgo.ColorS // 返回: [4]float64 混合分量, bool 是否着色, error 颜色错误 func (s *pdfCompositeColorSampler) color(point pdfgo.Point) ([4]float64, bool, error) { g := s.paint.Axial - if g == nil || g.Bounds != nil || g.Background != nil || s.paint.Function != nil || s.paint.Tiling != nil { + r := s.paint.Radial + if g == nil && r == nil || g != nil && (g.Bounds != nil || g.Background != nil) || r != nil && (r.Bounds != nil || r.Background != nil) || s.paint.Function != nil || s.paint.Tiling != nil { return pdfCompositeColor(s.paint, point, s.space, s.intent, s.conversion) } - position, visible := pdfShadingPosition(s.paint, point) + if !s.prepared { + var err error + if g != nil { + s.position, err = g.PreparePosition() + } else { + s.position, err = r.PreparePosition() + } + if err != nil { + return [4]float64{}, false, err + } + s.prepared = true + } + position, visible := s.position.PositionAt(point) if !visible { return [4]float64{}, false, nil } if values, ok := s.values[position]; ok { return values, true, nil } - values, visible, err := pdfCompositeColor(s.paint, point, s.space, s.intent, s.conversion) - if err == nil && visible && len(s.values) < 2*pdfCompositeTileSize { + var values [4]float64 + var source *pdfgo.ColorSpace + var intent pdfgo.Name + var err error + if g != nil { + values, err = g.ValuesAt(position) + source, intent = g.Space, g.Intent + } else { + values, err = r.ValuesAt(position) + source, intent = r.Space, r.Intent + } + if err == nil { + if !s.converted { + s.converter, err = s.space.PrepareConversion(source, intent, s.conversion) + s.converted = err == nil + } + if err == nil { + values, err = s.converter.Convert(values[:source.Components()]) + } + } + if err == nil && g != nil && len(s.values) < 2*pdfCompositeTileSize { if s.values == nil { s.values = make(map[float64][4]float64) } diff --git a/ofdgo_pdf_composite_pattern.go b/ofdgo_pdf_composite_pattern.go index 517f25f..91b5225 100644 --- a/ofdgo_pdf_composite_pattern.go +++ b/ofdgo_pdf_composite_pattern.go @@ -58,7 +58,18 @@ func (c *pdfCompositor) tiling(paint pdfgo.Paint, backdrop []pdfCompositePixel, } } result := c.acquirePixels(len(backdrop)) + complete := false + defer func() { + if !complete { + c.releasePixels(result) + } + }() for i, pixel := range backdrop { + if i%pdfCompositeTileSize == 0 { + if err := c.importer.ctx.Err(); err != nil { + return nil, err + } + } result[i] = pdfCompositePixel{values: pixel.values, alpha: pixel.alpha} } candidate := c.acquirePixels(len(backdrop)) @@ -83,6 +94,9 @@ func (c *pdfCompositor) tiling(paint pdfgo.Paint, backdrop []pdfCompositePixel, } pixels := candidate[:width*height] for y := range height { + if err := c.importer.ctx.Err(); err != nil { + return nil, err + } for x := range width { index := (top+y)*c.width + left + x initial := result[index] @@ -94,11 +108,15 @@ func (c *pdfCompositor) tiling(paint pdfgo.Paint, backdrop []pdfCompositePixel, } cell := pdfCompositor{importer: &pattern.local, box: Box{X: c.box.X + float64(left)*pixelStep - dx, Y: c.box.Y + float64(top)*pixelStep - dy, W: float64(width) * pixelStep, H: float64(height) * pixelStep}, width: width, height: height, inverse: localInverse, cache: pattern.local.compositingCache(), scratch: c.scratch, masks: map[*pdfgo.SoftMask][]float64{}} - if err := cell.draw(pattern.nodes, pixels, space); err != nil { + err := cell.draw(pattern.nodes, pixels, space) + cell.releaseMasks() + if err != nil { return nil, err } - cell.releaseMasks() for y := range height { + if err := c.importer.ctx.Err(); err != nil { + return nil, err + } for x := range width { index := (top+y)*c.width + left + x pdfCompositePatternCell(&result[index], backdrop[index], pixels[y*width+x], disjoint) @@ -120,6 +138,7 @@ func (c *pdfCompositor) tiling(paint pdfgo.Paint, backdrop []pdfCompositePixel, } pixel.alpha = pixel.effect } + complete = true return result, nil } diff --git a/ofdgo_pdf_mesh.go b/ofdgo_pdf_mesh.go index ca32731..80b189f 100644 --- a/ofdgo_pdf_mesh.go +++ b/ofdgo_pdf_mesh.go @@ -91,6 +91,9 @@ func (p *pdfImporter) meshTriangles(mesh *pdfgo.MeshGradient) ([]pdfMeshTriangle matrix := p.matrix.Mul(mesh.Matrix) var triangles []pdfMeshTriangle for index, patch := range mesh.Patches { + if err := p.ctx.Err(); err != nil { + return nil, err + } for i := range patch.Points { for j := range patch.Points[i] { patch.Points[i][j] = matrix.Apply(patch.Points[i][j]) @@ -121,6 +124,11 @@ func (p *pdfImporter) meshTriangles(mesh *pdfgo.MeshGradient) ([]pdfMeshTriangle n := int(steps) row := make([]pdfMeshVertex, n+1) for i := range row { + if i%pdfCompositeTileSize == 0 { + if err := p.ctx.Err(); err != nil { + return nil, err + } + } u := float64(i) / steps row[i] = pdfMeshVertex{patch.PointAt(u, 0), u, 0} } @@ -158,10 +166,17 @@ func (p *pdfImporter) meshTriangles(mesh *pdfgo.MeshGradient) ([]pdfMeshTriangle // 入参: mesh 原曲面, space 输出混合空间, conversion 设备转换函数 // 返回: []pdfShadingPixel 颜色及覆盖, error 几何或颜色错误 func (c *pdfCompositor) mesh(mesh *pdfgo.MeshGradient, space *pdfgo.ColorSpace, conversion pdfgo.ColorConversion) ([]pdfShadingPixel, error) { + if err := c.importer.ctx.Err(); err != nil { + return nil, err + } key := pdfMeshKey{mesh, space, conversion} if pixels, ok := c.meshes[key]; ok { return pixels, nil } + converter, err := space.PrepareConversion(mesh.Space, mesh.Intent, conversion) + if err != nil { + return nil, err + } triangles, ok := c.cache.meshes[mesh] if !ok { var err error @@ -200,6 +215,9 @@ func (c *pdfCompositor) mesh(mesh *pdfgo.MeshGradient, space *pdfgo.ColorSpace, endX := int(math.Min(float64(w), math.Ceil(maxX))) endY := int(math.Min(float64(h), math.Ceil(maxY))) for y := int(math.Max(0, math.Floor(minY))); y < endY; y++ { + if err := c.importer.ctx.Err(); err != nil { + return nil, err + } for x := int(math.Max(0, math.Floor(minX))); x < endX; x++ { px, py := float64(x)+.5, float64(y)+.5 l0 := ((b.Y-d.Y)*(px-d.X) + (d.X-b.X)*(py-d.Y)) / denominator @@ -221,7 +239,7 @@ func (c *pdfCompositor) mesh(mesh *pdfgo.MeshGradient, space *pdfgo.ColorSpace, var background [4]float64 if mesh.Background != nil { var err error - background, err = space.ConvertWith(mesh.Background[:mesh.Space.Components()], mesh.Space, mesh.Intent, conversion) + background, err = converter.Convert(mesh.Background[:mesh.Space.Components()]) if err != nil { return nil, err } @@ -234,6 +252,7 @@ func (c *pdfCompositor) mesh(mesh *pdfgo.MeshGradient, space *pdfgo.ColorSpace, return nil, fmt.Errorf("singular mesh bounds transform") } } + colors := make(map[[4]float64][4]float64) for y := 0; y < h; y++ { if err := c.importer.ctx.Err(); err != nil { return nil, err @@ -256,9 +275,17 @@ func (c *pdfCompositor) mesh(mesh *pdfgo.MeshGradient, space *pdfgo.ColorSpace, if err != nil { return nil, err } - values, err = space.ConvertWith(values[:mesh.Space.Components()], mesh.Space, mesh.Intent, conversion) - if err != nil { - return nil, err + if cached, ok := colors[values]; ok { + values = cached + } else { + source := values + values, err = converter.Convert(values[:mesh.Space.Components()]) + if err != nil { + return nil, err + } + if len(colors) < 2*pdfCompositeTileSize { + colors[source] = values + } } } pixel := &pixels[(y/samples)*c.width+x/samples] diff --git a/ofdgo_pdf_pattern.go b/ofdgo_pdf_pattern.go new file mode 100644 index 0000000..68275f4 --- /dev/null +++ b/ofdgo_pdf_pattern.go @@ -0,0 +1,128 @@ +// 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" + "reflect" + + "github.com/xiaoqidun/pdfgo" +) + +// pdfPatternCellLimit 限制单页图案矢量快照的估算字节数 +const pdfPatternCellLimit = 8 << 20 + +// pdfPatternCellKey 隔离图案来源、基色、坐标、精度与目标文档资源 +type pdfPatternCellKey struct { + source *pdfgo.TilingPattern + base pdfgo.Paint + matrix pdfgo.Matrix + dpi float64 + editor *Editor +} + +// pdfPatternInstanceRange 收缩已知路径及图片的实例范围,未知边界保留完整范围 +// 入参: columns 横向实例数, rows 纵向实例数, bounds 对象边界, width 单元宽, height 单元高, xstep 横向步距, ystep 纵向步距 +// 返回: int 左界, int 右界, int 上界, int 下界 +func pdfPatternInstanceRange(columns, rows float64, bounds []Box, width, height, xstep, ystep float64) (int, int, int, int) { + left, right, top, bottom := 0.0, 1-columns, 0.0, 1-rows + for _, box := range bounds { + if box.W <= 0 || box.H <= 0 || !finite(box.X) || !finite(box.Y) || !finite(box.X+box.W) || !finite(box.Y+box.H) { + return 1 - int(columns), 0, 1 - int(rows), 0 + } + left = math.Min(left, math.Max(1-columns, math.Ceil(-(box.X+box.W)/xstep)-1)) + right = math.Max(right, math.Min(0, math.Floor((width-box.X)/xstep)+1)) + top = math.Min(top, math.Max(1-rows, math.Ceil(-(box.Y+box.H)/ystep)-1)) + bottom = math.Max(bottom, math.Min(0, math.Floor((height-box.Y)/ystep)+1)) + } + return int(left), int(right), int(top), int(bottom) +} + +// tilingPattern 有界复用矢量图案,返回可独立编辑的对象副本 +// 入参: source 平铺图案, base 无色图案基色 +// 返回: *Pattern OFD图案, error 转换或取消错误 +func (p *pdfImporter) tilingPattern(source *pdfgo.TilingPattern, base pdfgo.Paint) (*Pattern, error) { + if err := p.ctx.Err(); err != nil { + return nil, err + } + if p.resolveFile != nil || p.resolveReference != nil { + return p.buildTilingPattern(source, base) + } + if p.patternCells == nil { + p.patternCells = &renderCache[pdfPatternCellKey, *Pattern]{limit: pdfPatternCellLimit} + } + key := pdfPatternCellKey{source: source, matrix: p.matrix, dpi: p.rasterDPI, editor: p.editor} + if source.PaintType == 2 { + base.Tiling, base.Axial, base.Radial, base.Mesh, base.Function, base.Shading = nil, nil, nil, nil, nil, nil + base.Alpha = 1 + key.base = base + } + if cached, ok := p.patternCells.get(key); ok { + return cloneEditorData(cached), p.ctx.Err() + } + pattern, err := p.buildTilingPattern(source, base) + if err != nil { + return nil, err + } + if err := p.ctx.Err(); err != nil { + return nil, err + } + cost := pdfPatternCellCost(reflect.ValueOf(pattern), p.patternCells.limit, 0) + if cost <= p.patternCells.limit { + p.patternCells.put(key, cloneEditorData(pattern), cost) + } + return pattern, p.ctx.Err() +} + +// pdfPatternCellCost 有界估算公开数据的快照成本,不扫描共享的私有排版来源 +// 入参: value 对象值, budget 剩余预算, depth 嵌套深度 +// 返回: int 字节成本,超预算时返回预算加一 +func pdfPatternCellCost(value reflect.Value, budget, depth int) int { + if depth > 128 || budget < 0 { + return budget + 1 + } + cost := int(value.Type().Size()) + if cost > budget { + return budget + 1 + } + switch value.Kind() { + case reflect.String: + if value.Len() > budget-cost { + return budget + 1 + } + cost += value.Len() + case reflect.Pointer: + if !value.IsNil() { + cost += pdfPatternCellCost(value.Elem(), budget-cost, depth+1) + } + case reflect.Slice, reflect.Array: + for i := range value.Len() { + cost += pdfPatternCellCost(value.Index(i), budget-cost, depth+1) + if cost > budget { + return budget + 1 + } + } + case reflect.Struct: + for i := range value.NumField() { + if value.Type().Field(i).IsExported() { + cost += pdfPatternCellCost(value.Field(i), budget-cost, depth+1) + if cost > budget { + return budget + 1 + } + } + } + } + return cost +} diff --git a/ofdgo_pdf_shading.go b/ofdgo_pdf_shading.go index e3a6ef0..b78330f 100644 --- a/ofdgo_pdf_shading.go +++ b/ofdgo_pdf_shading.go @@ -17,7 +17,6 @@ package ofdgo import ( "fmt" "image" - "math" "github.com/xiaoqidun/pdfgo" ) @@ -242,6 +241,22 @@ func (c *pdfCompositor) gradient(paint pdfgo.Paint, space *pdfgo.ColorSpace, nod return make([]pdfShadingPixel, c.width*c.height), nil } } + var position pdfgo.GradientPosition + if paint.Axial != nil || paint.Radial != nil { + var err error + if paint.Axial != nil { + position, err = paint.Axial.PreparePosition() + } else { + position, err = paint.Radial.PreparePosition() + } + if err != nil { + return nil, err + } + } + converter, err := space.PrepareConversion(source, intent, conversion) + if err != nil { + return nil, err + } step := 25.4 / c.importer.rasterDPI pixels := make([]pdfShadingPixel, c.width*c.height) for y := range c.height { @@ -283,11 +298,22 @@ func (c *pdfCompositor) gradient(paint pdfgo.Paint, space *pdfgo.ColorSpace, nod if visible { values, err = g.ValuesAt(local) if err == nil { - values, err = space.ConvertWith(values[:source.Components()], source, intent, conversion) + values, err = converter.Convert(values[:source.Components()]) } } } else { - values, visible, err = pdfCompositeColor(paint, point, space, intent, conversion) + var parameter float64 + parameter, visible = position.PositionAt(point) + if visible { + if paint.Axial != nil { + values, err = paint.Axial.ValuesAt(parameter) + } else { + values, err = paint.Radial.ValuesAt(parameter) + } + if err == nil { + values, err = converter.Convert(values[:source.Components()]) + } + } } if err != nil { return nil, err @@ -359,40 +385,15 @@ func pdfShadingClippedNode(node pdfCompositeNode, box pdfgo.Rectangle, matrix pd // 入参: paint 渐变画刷, point 页面坐标 // 返回: float64 归一化参数, bool 是否着色 func pdfShadingPosition(paint pdfgo.Paint, point pdfgo.Point) (float64, bool) { + var geometry pdfgo.GradientPosition + var err error if g := paint.Axial; g != nil { - dx, dy := g.End.X-g.Start.X, g.End.Y-g.Start.Y - t := ((point.X-g.Start.X)*dx + (point.Y-g.Start.Y)*dy) / (dx*dx + dy*dy) - return t, finite(t) && (t >= 0 || g.Extend[0]) && (t <= 1 || g.Extend[1]) + geometry, err = g.PreparePosition() + } else { + geometry, err = paint.Radial.PreparePosition() } - g := paint.Radial - if g.Matrix != (pdfgo.Matrix{}) { - inverse, ok := g.Matrix.Inverse() - if !ok { - return 0, false - } - point = inverse.Apply(point) + if err != nil { + return 0, false } - dx, dy, dr := g.End.X-g.Start.X, g.End.Y-g.Start.Y, g.EndRadius-g.StartRadius - px, py := point.X-g.Start.X, point.Y-g.Start.Y - a, b, c := dx*dx+dy*dy-dr*dr, -2*(px*dx+py*dy+g.StartRadius*dr), px*px+py*py-g.StartRadius*g.StartRadius - roots := [2]float64{math.NaN(), math.NaN()} - if a == 0 { - if b != 0 { - roots[0] = -c / b - } - } else if discriminant := b*b - 4*a*c; discriminant >= 0 { - q := -.5 * (b + math.Copysign(math.Sqrt(discriminant), b)) - if q == 0 { - roots[0] = -b / (2 * a) - } else { - roots[0], roots[1] = q/a, c/q - } - } - t := math.Inf(-1) - for _, root := range roots { - if finite(root) && g.StartRadius+root*dr >= 0 && (root >= 0 || g.Extend[0]) && (root <= 1 || g.Extend[1]) && root > t { - t = root - } - } - return t, finite(t) + return geometry.PositionAt(point) }