mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 13:56:32 +08:00
This commit is contained in:
1 parent
09a49b2c26
commit
b028a5d7c7
10 files changed
+307
-77
No files matched your search
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
+36
-25
@@ -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
|
||||
}
|
||||
|
||||
+9
-3
@@ -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
|
||||
}
|
||||
@@ -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 在单页内复用几何、图像分量及蒙版内容
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+29
-2
@@ -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,10 +275,18 @@ 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 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]
|
||||
for i, value := range values {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+36
-35
@@ -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 {
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
point = inverse.Apply(point)
|
||||
}
|
||||
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)
|
||||
}
|
||||
Reference in new issue
Block a user