mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 14:16:55 +08:00
1 parent
e4af1e7318
commit
59a2c4d8fe
9 files changed
+187
-83
No files matched your search
@@ -9,7 +9,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-20260930062146-7c39a20d9430
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20260930081439-0ce33d5532fb
|
||||
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-20260930062146-7c39a20d9430 h1:UBP+ZmqxoxQ7p6dFZI/6yGSLmgqfTQUvO7h+DUfWsWY=
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20260930062146-7c39a20d9430/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20260930081439-0ce33d5532fb h1:OKuM5n02cLQB0rIcJoNfnQl+udhBNWgjLMAUlAk087s=
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20260930081439-0ce33d5532fb/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=
|
||||
|
||||
+16
-30
@@ -2987,14 +2987,18 @@ func styleObjects(args []js.Value) (any, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
patterns := make(map[bool]*ofdgo.PatternStyle)
|
||||
for key, stroke := range map[string]bool{"fillPatternStyle": false, "strokePatternStyle": true} {
|
||||
appearance := ofdgo.ObjectAppearance{SetLink: !linkField.IsUndefined(), Link: link, Border: border}
|
||||
for i, key := range []string{"fillPatternStyle", "strokePatternStyle"} {
|
||||
if field := args[2].Get(key); !field.IsUndefined() {
|
||||
var pattern ofdgo.PatternStyle
|
||||
if err := json.Unmarshal([]byte(field.String()), &pattern); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
patterns[stroke] = &pattern
|
||||
if i == 0 {
|
||||
appearance.FillPattern = &pattern
|
||||
} else {
|
||||
appearance.StrokePattern = &pattern
|
||||
}
|
||||
}
|
||||
}
|
||||
if field := args[2].Get("fillPaint"); !field.IsUndefined() {
|
||||
@@ -3008,36 +3012,18 @@ func styleObjects(args []js.Value) (any, error) {
|
||||
}
|
||||
}
|
||||
return changeObjects(func() error {
|
||||
return currentEditor.Transaction(func(editor *ofdgo.Editor) error {
|
||||
page, ids := args[0].Int(), stringsFromJS(args[1])
|
||||
if !linkField.IsUndefined() {
|
||||
if err := editor.SetObjectLink(page, ids, link); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if border != nil {
|
||||
if err := resolveStylePaint((*ofdgo.FillColor)(border.Color)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := editor.StyleImageBorders(page, ids, *border); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, paint := range []*ofdgo.FillColor{style.FillColor, (*ofdgo.FillColor)(style.StrokeColor)} {
|
||||
if err := resolveStylePaint(paint); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := editor.StyleObjects(page, ids, style); err != nil {
|
||||
if border != nil {
|
||||
if err := resolveStylePaint((*ofdgo.FillColor)(border.Color)); err != nil {
|
||||
return err
|
||||
}
|
||||
for stroke, pattern := range patterns {
|
||||
if err := editor.StylePatterns(page, ids, stroke, *pattern); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, paint := range []*ofdgo.FillColor{style.FillColor, (*ofdgo.FillColor)(style.StrokeColor)} {
|
||||
if err := resolveStylePaint(paint); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
appearance.Style = style
|
||||
return currentEditor.ApplyAppearance(args[0].Int(), stringsFromJS(args[1]), appearance)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -562,7 +562,11 @@ func (e *Editor) updateObjectOrigins(page int, objects []GraphicObject, preserve
|
||||
if origin != nil {
|
||||
capability := e.objectCapabilities(before[i], orderable)
|
||||
if !capability.Transform {
|
||||
return fmt.Errorf("object %q is read-only for this operation: %w", ids[i], capability.editError())
|
||||
beforeBox, beforeCTM := editorGeometry(before[i])
|
||||
afterBox, afterCTM := editorGeometry(object)
|
||||
if !capability.Update && !(preserved && capability.Delete) || before[i].Type != object.Type || beforeBox != afterBox || beforeCTM != afterCTM {
|
||||
return fmt.Errorf("object %q is read-only for this operation: %w", ids[i], capability.editError())
|
||||
}
|
||||
}
|
||||
if !preserved && !capability.Update && editorPreservedObject(before[i], object) && capability.Paint {
|
||||
if err := e.validatePreservedAppearance(before[i], object); err != nil {
|
||||
|
||||
@@ -208,8 +208,9 @@ func (e *Editor) RGBColor(value color.NRGBA) (*FillColor, error) {
|
||||
}
|
||||
}
|
||||
for _, resource := range e.resources {
|
||||
if resource.space != nil {
|
||||
result.ColorSpace = resource.space.ID
|
||||
space := resource.space
|
||||
if space != nil && space.Type == "RGB" && (space.BitsPerComponent == 0 || space.BitsPerComponent == 8) && len(space.Palette) == 0 {
|
||||
result.ColorSpace = space.ID
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,46 @@ type ImageBorderStyle struct {
|
||||
Color *StrokeColor
|
||||
}
|
||||
|
||||
// ObjectAppearance 对象外观组合更新,nil字段保持原值
|
||||
// SetLink为true时更新链接,Link为nil时删除链接
|
||||
type ObjectAppearance struct {
|
||||
Style ObjectStyle
|
||||
SetLink bool
|
||||
Link *AnnotationLink
|
||||
Border *ImageBorderStyle
|
||||
FillPattern *PatternStyle
|
||||
StrokePattern *PatternStyle
|
||||
}
|
||||
|
||||
// ApplyAppearance 原子更新外观、链接、图片边框及图案布局,支持一次撤销
|
||||
// 入参: page 页面索引, ids 对象标识, appearance 外观增量
|
||||
// 返回: error 错误信息
|
||||
func (e *Editor) ApplyAppearance(page int, ids []string, appearance ObjectAppearance) error {
|
||||
return e.Transaction(func(editor *Editor) error {
|
||||
if appearance.SetLink {
|
||||
if err := editor.SetObjectLink(page, ids, appearance.Link); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if appearance.Border != nil {
|
||||
if err := editor.StyleImageBorders(page, ids, *appearance.Border); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := editor.StyleObjects(page, ids, appearance.Style); err != nil {
|
||||
return err
|
||||
}
|
||||
for i, pattern := range []*PatternStyle{appearance.FillPattern, appearance.StrokePattern} {
|
||||
if pattern != nil {
|
||||
if err := editor.StylePatterns(page, ids, i == 1, *pattern); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// DrawParam 读取绘制参数,resolved为true时合并继承链,返回独立副本
|
||||
// 入参: id 资源标识, resolved 是否解析继承
|
||||
// 返回: *DrawParam 绘制参数, error 错误信息
|
||||
|
||||
@@ -124,6 +124,7 @@ func ImportPDF(ctx context.Context, source io.ReaderAt, size int64, options PDFI
|
||||
if err != nil {
|
||||
return nil, PDFImportReport{}, err
|
||||
}
|
||||
defer reader.Close()
|
||||
editor := NewEditor()
|
||||
editor.SetRenderBackends(renderer.Backends())
|
||||
editor.fontDirs, editor.fontFS = renderer.FontSources()
|
||||
|
||||
+10
-21
@@ -207,25 +207,7 @@ func (p *pdfImporter) compositePage(space *pdfgo.ColorSpace, walk func(pdfgo.Vis
|
||||
}
|
||||
if space == nil {
|
||||
space = &pdfgo.ColorSpace{Model: "DeviceRGB"}
|
||||
var needsProcess func([]pdfCompositeNode) (bool, error)
|
||||
needsProcess = func(items []pdfCompositeNode) (bool, error) {
|
||||
for _, item := range items {
|
||||
if item.group != nil {
|
||||
found, err := needsProcess(item.children)
|
||||
if err != nil || found {
|
||||
return found, err
|
||||
}
|
||||
}
|
||||
if item.image != nil && item.image.Style.FillOverprint {
|
||||
_, found, err := pdfImageProcessColorants(item.image.Image)
|
||||
if err != nil || found {
|
||||
return found, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
found, err := needsProcess(nodes)
|
||||
found, err := p.processOverprint(nodes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -244,7 +226,11 @@ func (p *pdfImporter) compositePage(space *pdfgo.ColorSpace, walk func(pdfgo.Vis
|
||||
func (p *pdfImporter) compositeObjects(nodes []pdfCompositeNode) error {
|
||||
space := p.compositeSpace
|
||||
for i, node := range nodes {
|
||||
if node.opaque(space) || space.SRGBEquivalent() && node.direct() {
|
||||
direct, err := p.directCompositeNode(node, space)
|
||||
if err != nil {
|
||||
return fmt.Errorf("inspect graphic %d: %w", i+1, err)
|
||||
}
|
||||
if direct {
|
||||
if err := node.emit(p.visitor()); err != nil {
|
||||
return fmt.Errorf("convert graphic %d: %w", i+1, err)
|
||||
}
|
||||
@@ -445,11 +431,14 @@ func (p *pdfImporter) compositeRegion(backdrop []pdfCompositeNode, node pdfCompo
|
||||
output := image.NewNRGBA(image.Rect(0, 0, w, h))
|
||||
cache := p.compositingCache()
|
||||
step := 25.4 / p.rasterDPI
|
||||
buffer := make([]pdfCompositePixel, min(256, w)*min(256, h))
|
||||
for y := 0; y < h; y += 256 {
|
||||
for x := 0; x < w; x += 256 {
|
||||
runtime.Gosched()
|
||||
width, height := min(256, w-x), min(256, h-y)
|
||||
c := pdfCompositor{importer: p, box: Box{X: box.X + float64(x)*step, Y: box.Y + float64(y)*step, W: float64(width) * step, H: float64(height) * step}, width: width, height: height, inverse: inverse, cache: cache, masks: map[*pdfgo.SoftMask][]float64{}}
|
||||
pixels := make([]pdfCompositePixel, width*height)
|
||||
pixels := buffer[:width*height]
|
||||
clear(pixels)
|
||||
if err := c.draw(backdrop, pixels, space); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+109
-26
@@ -33,37 +33,14 @@ type pdfCompositePattern struct {
|
||||
// 返回: []pdfCompositePixel 去除背景贡献的图案像素, error 解析或绘制错误
|
||||
func (c *pdfCompositor) tiling(paint pdfgo.Paint, backdrop []pdfCompositePixel, space *pdfgo.ColorSpace) ([]pdfCompositePixel, error) {
|
||||
source := paint.Tiling
|
||||
paint.Alpha = 1
|
||||
if c.cache.patterns == nil {
|
||||
c.cache.patterns = make(map[pdfgo.Paint]*pdfCompositePattern)
|
||||
}
|
||||
pattern := c.cache.patterns[paint]
|
||||
matrix := c.importer.matrix.Mul(source.Matrix)
|
||||
inverse, ok := matrix.Inverse()
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid PDF tiling pattern matrix")
|
||||
}
|
||||
if pattern == nil {
|
||||
nodes, err := c.importer.collectCompositeNodes(func(v pdfgo.Visitor) error { return source.Walk(c.importer.ctx, paint, v) })
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b := source.BBox
|
||||
box := pdfBounds([]pdfgo.Point{
|
||||
matrix.Apply(pdfgo.Point{X: b.XMin, Y: b.YMin}), matrix.Apply(pdfgo.Point{X: b.XMax, Y: b.YMin}),
|
||||
matrix.Apply(pdfgo.Point{X: b.XMax, Y: b.YMax}), matrix.Apply(pdfgo.Point{X: b.XMin, Y: b.YMax}),
|
||||
})
|
||||
if box.W <= 0 || box.H <= 0 {
|
||||
return nil, fmt.Errorf("invalid PDF tiling pattern bounds")
|
||||
}
|
||||
local := *c.importer
|
||||
local.matrix = matrix
|
||||
local.matrix[4] -= box.X
|
||||
local.matrix[5] -= box.Y
|
||||
local.pageWidth, local.pageHeight = box.W, box.H
|
||||
local.compositeCache = nil
|
||||
pattern = &pdfCompositePattern{nodes: nodes, local: local, box: box}
|
||||
c.cache.patterns[paint] = pattern
|
||||
pattern, err := c.importer.compositePattern(paint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
xstep, ystep := math.Abs(source.XStep), math.Abs(source.YStep)
|
||||
area := pdfBounds([]pdfgo.Point{
|
||||
@@ -108,3 +85,109 @@ func (c *pdfCompositor) tiling(paint pdfgo.Paint, backdrop []pdfCompositePixel,
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// compositePattern 复用图案单元解析及几何上下文,供检查和局部合成共同使用
|
||||
// 入参: paint 图案画刷
|
||||
// 返回: *pdfCompositePattern 单元内容, error 解析或边界错误
|
||||
func (p *pdfImporter) compositePattern(paint pdfgo.Paint) (*pdfCompositePattern, error) {
|
||||
paint.Alpha = 1
|
||||
cache := p.compositingCache()
|
||||
if cache.patterns == nil {
|
||||
cache.patterns = make(map[pdfgo.Paint]*pdfCompositePattern)
|
||||
}
|
||||
if pattern := cache.patterns[paint]; pattern != nil {
|
||||
return pattern, nil
|
||||
}
|
||||
source := paint.Tiling
|
||||
nodes, err := p.collectCompositeNodes(func(v pdfgo.Visitor) error { return source.Walk(p.ctx, paint, v) })
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matrix := p.matrix.Mul(source.Matrix)
|
||||
b := source.BBox
|
||||
box := pdfBounds([]pdfgo.Point{
|
||||
matrix.Apply(pdfgo.Point{X: b.XMin, Y: b.YMin}), matrix.Apply(pdfgo.Point{X: b.XMax, Y: b.YMin}),
|
||||
matrix.Apply(pdfgo.Point{X: b.XMax, Y: b.YMax}), matrix.Apply(pdfgo.Point{X: b.XMin, Y: b.YMax}),
|
||||
})
|
||||
if box.W <= 0 || box.H <= 0 {
|
||||
return nil, fmt.Errorf("invalid PDF tiling pattern bounds")
|
||||
}
|
||||
local := *p
|
||||
local.matrix = matrix
|
||||
local.matrix[4] -= box.X
|
||||
local.matrix[5] -= box.Y
|
||||
local.pageWidth, local.pageHeight = box.W, box.H
|
||||
local.compositeCache = nil
|
||||
pattern := &pdfCompositePattern{nodes: nodes, local: local, box: box}
|
||||
cache.patterns[paint] = pattern
|
||||
return pattern, nil
|
||||
}
|
||||
|
||||
// directCompositeNode 检查图元及其图案单元能否独立保留,不忽略单元内的背景混合
|
||||
// 入参: node 图元, space 混合空间
|
||||
// 返回: bool 是否可直接转换, error 图案解析错误
|
||||
func (p *pdfImporter) directCompositeNode(node pdfCompositeNode, space *pdfgo.ColorSpace) (bool, error) {
|
||||
if !node.opaque(space) && !(space.SRGBEquivalent() && node.direct()) {
|
||||
return false, nil
|
||||
}
|
||||
if node.group != nil {
|
||||
for _, child := range node.children {
|
||||
if direct, err := p.directCompositeNode(child, space); err != nil || !direct {
|
||||
return direct, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
style, fill, stroke := node.style()
|
||||
for i, paint := range []pdfgo.Paint{style.Fill, style.Stroke} {
|
||||
if i == 0 && !fill || i == 1 && !stroke || paint.Tiling == nil {
|
||||
continue
|
||||
}
|
||||
pattern, err := p.compositePattern(paint)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, child := range pattern.nodes {
|
||||
if direct, err := pattern.local.directCompositeNode(child, space); err != nil || !direct {
|
||||
return direct, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// processOverprint 检查图元、透明组和图案单元是否需要设备四色套印
|
||||
// 入参: nodes 原始图元
|
||||
// 返回: bool 是否需要四色混合, error 图像或图案解析错误
|
||||
func (p *pdfImporter) processOverprint(nodes []pdfCompositeNode) (bool, error) {
|
||||
for _, node := range nodes {
|
||||
if node.group != nil {
|
||||
if found, err := p.processOverprint(node.children); err != nil || found {
|
||||
return found, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
style, fill, stroke := node.style()
|
||||
if node.image != nil && style.FillOverprint {
|
||||
if _, found, err := pdfImageProcessColorants(node.image.Image); err != nil || found {
|
||||
return found, err
|
||||
}
|
||||
}
|
||||
if fill && style.FillOverprint && pdfOverprintNeedsSeparation(style.Fill) || stroke && style.StrokeOverprint && pdfOverprintNeedsSeparation(style.Stroke) {
|
||||
return true, nil
|
||||
}
|
||||
for i, paint := range []pdfgo.Paint{style.Fill, style.Stroke} {
|
||||
if i == 0 && !fill || i == 1 && !stroke || paint.Tiling == nil {
|
||||
continue
|
||||
}
|
||||
pattern, err := p.compositePattern(paint)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if found, err := pattern.local.processOverprint(pattern.nodes); err != nil || found {
|
||||
return found, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
Reference in new issue
Block a user