feat(渲染引擎): 修复裁剪并优化渲染
build.yaml / build (push) Successful in 42s

This commit is contained in:
xiaoqidun committed 2026-10-03 18:36:53 +08:00
1 parent aa1fcd4cd6
commit 54995fb60e
10 files changed
+60 -20

No files matched your search

+1 -1
View File
@@ -10,7 +10,7 @@ require (
github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6 github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
github.com/xiaoqidun/jbig2 v1.1.1 github.com/xiaoqidun/jbig2 v1.1.1
github.com/xiaoqidun/pdfgo v0.0.0-20261003042717-ffb073d5634d github.com/xiaoqidun/pdfgo v0.0.0-20261003103143-2420f94d9b97
golang.org/x/crypto v0.57.0 golang.org/x/crypto v0.57.0
golang.org/x/image v0.46.0 golang.org/x/image v0.46.0
golang.org/x/text v0.42.0 golang.org/x/text v0.42.0
+2 -2
View File
@@ -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/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 h1:SjliS9ZO0e9HX5oYUfBuXrfFsXhWricrSz+XY4zORDI=
github.com/xiaoqidun/jbig2 v1.1.1/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ= github.com/xiaoqidun/jbig2 v1.1.1/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ=
github.com/xiaoqidun/pdfgo v0.0.0-20261003042717-ffb073d5634d h1:WiG6LgYU1hZBAELi72qP6UnCyA8cPe7Ap6nto0ofGnA= github.com/xiaoqidun/pdfgo v0.0.0-20261003103143-2420f94d9b97 h1:1YHWQ3BBFJd4RlWAEGhHHQ6D+OI/nriU91YIUzxZo8o=
github.com/xiaoqidun/pdfgo v0.0.0-20261003042717-ffb073d5634d/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ= github.com/xiaoqidun/pdfgo v0.0.0-20261003103143-2420f94d9b97/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA= github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
+8 -1
View File
@@ -330,7 +330,14 @@ func (e *Editor) documentRoot() string {
// 返回: bool 是否为同一文档 // 返回: bool 是否为同一文档
func (e *Editor) sameDocument(other *Editor) bool { func (e *Editor) sameDocument(other *Editor) bool {
if e.source == nil || other.source == nil { if e.source == nil || other.source == nil {
return e.source == nil && other.source == nil if e.source == nil && other.source == nil {
return true
}
fresh, packaged := e, other
if fresh.source != nil {
fresh, packaged = other, e
}
return len(packaged.source.reader.OFD.DocBody) == 1 && (packaged.source.fromNew || fresh.Info.DocID != "" && fresh.Info.DocID == packaged.source.info.DocID)
} }
return e.documentRoot() == other.documentRoot() && e.source.info.DocID == other.source.info.DocID return e.documentRoot() == other.documentRoot() && e.source.info.DocID == other.source.info.DocID
} }
+1
View File
@@ -684,6 +684,7 @@ func (e *Editor) importBase() (*editorSource, error) {
return nil, err return nil, err
} }
editor.source.idsReady = true editor.source.idsReady = true
editor.source.fromNew = true
return editor.source, nil return editor.source, nil
} }
+1
View File
@@ -36,6 +36,7 @@ type editorSource struct {
info DocInfo info DocInfo
fallbackDocID string fallbackDocID string
directory string directory string
fromNew bool
pages map[string]*editorSourcePage pages map[string]*editorSourcePage
origins map[string]*editorObjectOrigin origins map[string]*editorObjectOrigin
idsReady bool idsReady bool
+1 -1
View File
@@ -78,7 +78,7 @@ type pdfImporter struct {
pages map[pdfgo.Reference]*pdfgo.Page pages map[pdfgo.Reference]*pdfgo.Page
pageIDs map[pdfgo.Reference]string pageIDs map[pdfgo.Reference]string
pageIndexes map[pdfgo.Reference]int pageIndexes map[pdfgo.Reference]int
imageIDs map[pdfImageKey]string imageIDs map[pdfImageKey][]pdfImageResource
attachmentIDs map[pdfAttachmentKey]string attachmentIDs map[pdfAttachmentKey]string
maskClips map[*pdfgo.SoftMask]pdfgo.Path maskClips map[*pdfgo.SoftMask]pdfgo.Path
pageBox pdfgo.Rectangle pageBox pdfgo.Rectangle
+17 -5
View File
@@ -20,6 +20,7 @@ import (
"image" "image"
"image/color" "image/color"
"math" "math"
"reflect"
"strconv" "strconv"
"unicode/utf8" "unicode/utf8"
@@ -32,6 +33,12 @@ type pdfImageKey struct {
intent pdfgo.Name intent pdfgo.Name
} }
// pdfImageResource 保存图像的有效颜色空间及对应资源,避免跨资源重映射复用
type pdfImageResource struct {
space pdfgo.Object
id string
}
// pdfStencilImage 按当前填充色读取模板图像,不分配整幅彩色样本 // pdfStencilImage 按当前填充色读取模板图像,不分配整幅彩色样本
type pdfStencilImage struct { type pdfStencilImage struct {
source image.Image source image.Image
@@ -103,9 +110,14 @@ func (p *pdfImporter) image(mark pdfgo.ImageMark) error {
} }
source := mark.Image source := mark.Image
key := pdfImageKey{stream: source.Stream, intent: source.Intent} key := pdfImageKey{stream: source.Stream, intent: source.Intent}
id := p.imageIDs[key] id := ""
if source.ImageMask { if !source.ImageMask {
id = "" for _, resource := range p.imageIDs[key] {
if reflect.DeepEqual(resource.space, source.ColorSpace) {
id = resource.id
break
}
}
} }
if id == "" { if id == "" {
jbig2Original, err := source.JBIG2File() jbig2Original, err := source.JBIG2File()
@@ -155,9 +167,9 @@ func (p *pdfImporter) image(mark pdfgo.ImageMark) error {
} }
if !source.ImageMask { if !source.ImageMask {
if p.imageIDs == nil { if p.imageIDs == nil {
p.imageIDs = map[pdfImageKey]string{} p.imageIDs = map[pdfImageKey][]pdfImageResource{}
} }
p.imageIDs[key] = id p.imageIDs[key] = append(p.imageIDs[key], pdfImageResource{space: source.ColorSpace, id: id})
} }
} }
return p.appendImage(mark, id) return p.appendImage(mark, id)
+26 -7
View File
@@ -160,12 +160,24 @@ func intersectClipPath(parent, current *canvas.Path) *canvas.Path {
if parent.Empty() || current.Empty() { if parent.Empty() || current.Empty() {
return &canvas.Path{} return &canvas.Path{}
} }
if parentOK && parentRect.Contains(current.FastBounds()) { if parentOK {
bounds := current.Bounds()
if parentRect.Contains(bounds) {
return current return current
} }
if currentOK && currentRect.Contains(parent.FastBounds()) { if !parentRect.Overlaps(bounds) {
return &canvas.Path{}
}
}
if currentOK {
bounds := parent.Bounds()
if currentRect.Contains(bounds) {
return parent return parent
} }
if !currentRect.Overlaps(bounds) {
return &canvas.Path{}
}
}
if result, ok := intersectConvexCanvasPaths(parent, current); ok { if result, ok := intersectConvexCanvasPaths(parent, current); ok {
return result return result
} }
@@ -199,7 +211,7 @@ func applyClipPath(path, clip *canvas.Path) *canvas.Path {
return &canvas.Path{} return &canvas.Path{}
} }
if rect, ok := rectangularPath(clip); ok { if rect, ok := rectangularPath(clip); ok {
bounds := path.FastBounds() bounds := path.Bounds()
if rect.Contains(bounds) { if rect.Contains(bounds) {
return path return path
} }
@@ -224,18 +236,25 @@ func rectangularPath(path *canvas.Path) (canvas.Rect, bool) {
return canvas.Rect{}, false return canvas.Rect{}, false
} }
data := path.Data() data := path.Data()
if len(data) != 20 {
return canvas.Rect{}, false
}
for i := 0; i < len(data); i += 4 { for i := 0; i < len(data); i += 4 {
if i+4 > len(data) || (data[i] != canvas.MoveToCmd && data[i] != canvas.LineToCmd && data[i] != canvas.CloseCmd) { if data[i] != canvas.MoveToCmd && data[i] != canvas.LineToCmd && data[i] != canvas.CloseCmd {
return canvas.Rect{}, false return canvas.Rect{}, false
} }
} }
points := path.Coords() if !(canvas.Point{X: data[1], Y: data[2]}).Equals(canvas.Point{X: data[17], Y: data[18]}) {
if len(points) != 5 || !points[0].Equals(points[4]) {
return canvas.Rect{}, false return canvas.Rect{}, false
} }
rect := path.FastBounds() rect := path.FastBounds()
corners := 0 corners := 0
for _, point := range points[:4] { for i := 0; i < 16; i += 4 {
point := canvas.Point{X: data[i+1], Y: data[i+2]}
next := (i + 4) % 16
if !canvas.Equal(point.X, data[next+1]) && !canvas.Equal(point.Y, data[next+2]) {
return canvas.Rect{}, false
}
corner := 0 corner := 0
switch { switch {
case canvas.Equal(point.X, rect.X0) && canvas.Equal(point.Y, rect.Y0): case canvas.Equal(point.X, rect.X0) && canvas.Equal(point.Y, rect.Y0):
+1 -1
View File
@@ -368,7 +368,7 @@ func (r *Renderer) renderPattern(ctx *canvas.Context, pattern *PatternPaint, pag
if !ok { if !ok {
return return
} }
bounds := clip.FastBounds() bounds := clip.Bounds()
points := [][2]float64{ points := [][2]float64{
{bounds.X0, pageH - bounds.Y0}, {bounds.X0, pageH - bounds.Y0},
{bounds.X1, pageH - bounds.Y0}, {bounds.X1, pageH - bounds.Y0},
+2 -2
View File
@@ -60,7 +60,7 @@ func (r *Renderer) drawSampledShading(ctx *canvas.Context, path *canvas.Path, vi
if path.Empty() { if path.Empty() {
return return
} }
bounds := path.FastBounds().And(shdCanvasBounds(ctx)) bounds := path.Bounds().And(shdCanvasBounds(ctx))
if bounds.W() <= 0 || bounds.H() <= 0 { if bounds.W() <= 0 || bounds.H() <= 0 {
return return
} }
@@ -243,7 +243,7 @@ func drawRepeatAxialPath(ctx *canvas.Context, path *canvas.Path, gradient *repea
p = p.Transform(view.Inv()) p = p.Transform(view.Inv())
d := gradient.End.Sub(gradient.Start).Mul(gradient.period) d := gradient.End.Sub(gradient.Start).Mul(gradient.period)
axis := canvas.Matrix{{d.X, -d.Y, gradient.Start.X}, {d.Y, d.X, gradient.Start.Y}} axis := canvas.Matrix{{d.X, -d.Y, gradient.Start.X}, {d.Y, d.X, gradient.Start.Y}}
bounds := p.FastBounds().And(shdCanvasBounds(ctx).Transform(view.Inv())) bounds := p.Bounds().And(shdCanvasBounds(ctx).Transform(view.Inv()))
area := bounds.Transform(axis.Inv()) area := bounds.Transform(axis.Inv())
origin := ctx.CoordView().Dot(canvas.Point{}) origin := ctx.CoordView().Dot(canvas.Point{})
m := ctx.CoordSystemView().Mul(ctx.View()).Translate(origin.X, origin.Y).Mul(view) m := ctx.CoordSystemView().Mul(ctx.View()).Translate(origin.X, origin.Y).Mul(view)