diff --git a/ofdgo_editor.go b/ofdgo_editor.go index 31bea1e..1787215 100644 --- a/ofdgo_editor.go +++ b/ofdgo_editor.go @@ -1035,7 +1035,7 @@ func transformEditorObject(object GraphicObject, dx, dy, scale float64) (Graphic default: return GraphicObject{}, fmt.Errorf("unsupported object type %q", object.Type) } - if clips := editorObjectClips(&object); scale != 1 && *clips != nil && (object.Type == "TextObject" || (*clips).TransFlag != nil && !*(*clips).TransFlag) { + if clips := editorObjectClips(&object); scale != 1 && *clips != nil && (object.Type == "TextObject" || !(*clips).usesObjectMatrix()) { *clips = transformObjectClips(*clips, Matrix{a: scale, d: scale}) } box, err := ParseBox(*boundary) diff --git a/ofdgo_editor_composite.go b/ofdgo_editor_composite.go index 200d911..2445e2b 100644 --- a/ofdgo_editor_composite.go +++ b/ofdgo_editor_composite.go @@ -470,10 +470,8 @@ func (e *Editor) compositeMembers(n *editorCompositeNode, reader *Reader, render boundary := n.parent.Multiply(TranslationMatrix(box.X, box.Y)) matrix := boundary.Multiply(NewMatrix(c.CTM)) clips, clipMatrix := c.Clips, matrix - if clips != nil && clips.TransFlag != nil && !*clips.TransFlag { - copy := *clips - copy.TransFlag = nil - clips, clipMatrix = ©, boundary + if !clips.usesObjectMatrix() { + clipMatrix = boundary } clip := n.clip var err error @@ -658,7 +656,7 @@ func (e *Editor) measureCompositeMembers(renderer *Renderer, nodes []*editorComp } if transform && node.object.Type == "ImageObject" && bounds.Bounds.W > 0 && bounds.Bounds.H > 0 { clips := node.object.ImageObject.Clips - _, capability.CropImage = node.matrix(clips == nil || clips.TransFlag == nil || *clips.TransFlag).Invert() + _, capability.CropImage = node.matrix(clips.usesObjectMatrix()).Invert() capability.FitImage = capability.CropImage && axisAlignedMatrix(NewMatrix(node.object.ImageObject.CTM)) capability.ResetCrop = node.states[editorObjectID(node.object)].crop != nil } @@ -903,7 +901,7 @@ func transformCompositeOffsets(data []byte, matrix Matrix) ([]byte, error) { } object := cloneEditorData(n.object) object = compositeBoundary(object, Matrix{a: matrix.a, b: matrix.b, c: matrix.c, d: matrix.d}, false) - if clips := editorObjectClips(&object); *clips != nil && (*clips).TransFlag != nil && !*(*clips).TransFlag { + if clips := editorObjectClips(&object); *clips != nil && !(*clips).usesObjectMatrix() { *clips = transformObjectClips(*clips, TranslationMatrix(matrix.e, matrix.f)) } fragment, err = editorXMLObject(n.data, n.node, n.object, object) @@ -943,7 +941,7 @@ func compositeBoundary(object GraphicObject, parent Matrix, inward bool) Graphic case "ImageObject": object.ImageObject.Boundary = editorBoxString(box) } - if clips := editorObjectClips(&object); *clips != nil && (*clips).TransFlag != nil && !*(*clips).TransFlag { + if clips := editorObjectClips(&object); *clips != nil && !(*clips).usesObjectMatrix() { *clips = transformObjectClips(*clips, clipMatrix) } return object diff --git a/ofdgo_editor_composite_copy.go b/ofdgo_editor_composite_copy.go index 7c85a24..dc41162 100644 --- a/ofdgo_editor_composite_copy.go +++ b/ofdgo_editor_composite_copy.go @@ -299,7 +299,7 @@ func (e *Editor) pasteCompositeSelection(selection *CompositeSelection, paste fu return err } clips := *editorObjectClips(©.object) - matrix, ok := copy.matrix(clips == nil || clips.TransFlag == nil || *clips.TransFlag).Invert() + matrix, ok := copy.matrix(clips.usesObjectMatrix()).Invert() if !ok { return fmt.Errorf("composite clip transform is not invertible") } diff --git a/ofdgo_editor_composite_image.go b/ofdgo_editor_composite_image.go index 8f02193..78bc914 100644 --- a/ofdgo_editor_composite_image.go +++ b/ofdgo_editor_composite_image.go @@ -43,7 +43,7 @@ func (e *Editor) CropCompositeImage(page int, path ObjectPath, index int, box Bo return fmt.Errorf("crop does not intersect the image") } clips := node.object.ImageObject.Clips - inverse, _ := node.matrix(clips == nil || clips.TransFlag == nil || *clips.TransFlag).Invert() + inverse, _ := node.matrix(clips.usesObjectMatrix()).Invert() shape, err := NewShape(ShapeRectangle, box) if err != nil { return err @@ -143,7 +143,7 @@ func (e *Editor) FitCompositeImage(page int, path ObjectPath, index int, mode st frame = TranslationMatrix(box.X, box.Y).Multiply(node.parent) } clips := object.ImageObject.Clips - inverse, _ := node.matrix(clips == nil || clips.TransFlag == nil || *clips.TransFlag).Invert() + inverse, _ := node.matrix(clips.usesObjectMatrix()).Invert() return appendCompositeClip(node, shape, inverse.Multiply(frame), true) }) } @@ -192,7 +192,7 @@ func appendCompositeClip(node *editorCompositeNode, shape PathObject, matrix Mat state.crop = &crop } data, err := encodeOFDXML(func(x *ofdXML) { - x.clips(&Clips{Clip: []Clip{clip}}) + x.clips(&Clips{Clip: []Clip{clip}}, "") }) if err != nil { return err @@ -209,8 +209,12 @@ func appendCompositeClip(node *editorCompositeNode, shape PathObject, matrix Mat patch = editorXMLContent(node.data, original, content) } else { data = editorXMLEncodedFragment(data, root) - content := append(data, node.data[node.node.open:node.node.close]...) - patch = editorXMLContent(node.data, node.node, content) + if actions := node.node.child("Actions"); actions != nil { + patch = editorXMLPatch{actions.end, actions.end, data} + } else { + content := append(data, node.data[node.node.open:node.node.close]...) + patch = editorXMLContent(node.data, node.node, content) + } } updated, err := newEditorCompositeNode(editorPatchXML(node.data, []editorXMLPatch{patch})) if err != nil { diff --git a/ofdgo_editor_erase.go b/ofdgo_editor_erase.go index 06047eb..2aebcdb 100644 --- a/ofdgo_editor_erase.go +++ b/ofdgo_editor_erase.go @@ -133,7 +133,7 @@ func (e *Editor) eraseCompositeObjects(page int, path ObjectPath, indexes []int, return fmt.Errorf("composite member cannot be erased") } clips := *editorObjectClips(&node.object) - inverse, ok := node.matrix(clips == nil || clips.TransFlag == nil || *clips.TransFlag).Invert() + inverse, ok := node.matrix(clips.usesObjectMatrix()).Invert() if !ok { return fmt.Errorf("composite clip transform is not invertible") } @@ -212,7 +212,7 @@ func (e *Editor) eraseObjects(page int, ids []string, box Box, points []Point, r *clips = &Clips{} } matrix := IdentityMatrix - if (*clips).TransFlag == nil || *(*clips).TransFlag { + if (*clips).usesObjectMatrix() { if object.Type == "ImageObject" && ctm == "" { ctm = Matrix{a: frame.W, d: frame.H}.String() } diff --git a/ofdgo_editor_reference.go b/ofdgo_editor_reference.go index 51b9ac5..02d55c6 100644 --- a/ofdgo_editor_reference.go +++ b/ofdgo_editor_reference.go @@ -261,6 +261,11 @@ func (x *ofdXML) referenceElement(name string, value any) { } var attrs ofdAttrs switch value := value.(type) { + case *Dest: + if value == nil { + return + } + attrs.add("PageID", value.PageID) case ShdColor: attrs.add("ColorSpace", value.ColorSpace) case *ShdColor: diff --git a/ofdgo_editor_transform.go b/ofdgo_editor_transform.go index ae8ccc4..2c72a0c 100644 --- a/ofdgo_editor_transform.go +++ b/ofdgo_editor_transform.go @@ -456,7 +456,7 @@ func transformEditorMatrix(object GraphicObject, matrix Matrix) GraphicObject { local := TranslationMatrix(-after.X, -after.Y).Multiply(matrix).Multiply(TranslationMatrix(before.X, before.Y)) *ctm = local.Multiply(NewMatrix(*ctm)).String() *boundary = editorBoxString(after) - if clips := editorObjectClips(&object); *clips != nil && (*clips).TransFlag != nil && !*(*clips).TransFlag { + if clips := editorObjectClips(&object); *clips != nil && !(*clips).usesObjectMatrix() { *clips = transformObjectClips(*clips, local) } if !axisAlignedMatrix(matrix) && before.W > 0 && before.H > 0 { @@ -469,13 +469,12 @@ func transformEditorMatrix(object GraphicObject, matrix Matrix) GraphicObject { *clips = © } clipMatrix := local - if (*clips).TransFlag == nil || *(*clips).TransFlag { + if (*clips).usesObjectMatrix() { if inverse, ok := NewMatrix(*ctm).Invert(); ok { clipMatrix = inverse.Multiply(local) } else { *clips = transformObjectClips(*clips, NewMatrix(*ctm)) - flag := false - (*clips).TransFlag = &flag + (*clips).TransFlag = nil } } fill, stroke := true, false @@ -526,17 +525,13 @@ func cropImageObject(image *ImageObject, box Box) error { image.Boundary = editorBoxString(box) image.Clips = nil if box != full { - inverse, ok := NewMatrix(image.CTM).Invert() - if !ok { - return fmt.Errorf("image transform is not invertible") - } path, err := NewShape(ShapeRectangle, Box{W: box.W, H: box.H}) if err != nil { return err } fill, stroke := true, false path.Fill, path.Stroke = &fill, &stroke - image.Clips = &Clips{Clip: []Clip{{Area: []ClipArea{{CTM: inverse.String(), Path: []PathObject{path}}}}}} + image.Clips = &Clips{Clip: []Clip{{Area: []ClipArea{{Path: []PathObject{path}}}}}} } return nil } diff --git a/ofdgo_editor_xml.go b/ofdgo_editor_xml.go index 227bac7..cac71c9 100644 --- a/ofdgo_editor_xml.go +++ b/ofdgo_editor_xml.go @@ -373,6 +373,19 @@ func editorXMLObject(data []byte, node *editorXML, before, after GraphicObject) // 入参: data 原文, node 原节点, oldXML 修改前编码, newXML 修改后编码 // 返回: []byte 更新节点, error 错误信息 func editorXMLMerge(data []byte, node *editorXML, oldXML, newXML []byte) ([]byte, error) { + legacyClip := node.name.Local == "Clips" && node.hasAttr("TransFlag") + if legacyClip { + var clips Clips + if err := xml.Unmarshal(data[node.start:node.end], &clips); err != nil { + return nil, err + } + clips.TransFlag = nil + var err error + oldXML, err = encodeOFDXML(func(x *ofdXML) { x.clips(&clips, "") }) + if err != nil { + return nil, err + } + } oldNode, err := parseEditorXML(oldXML) if err != nil { return nil, err @@ -382,6 +395,9 @@ func editorXMLMerge(data []byte, node *editorXML, oldXML, newXML []byte) ([]byte return nil, err } changes := make(map[string]string) + if legacyClip { + changes["TransFlag"] = "" + } for _, attr := range append(oldNode.attrs, newNode.attrs...) { if attr.Name.Space == "" && (oldNode.attr(attr.Name.Local) != newNode.attr(attr.Name.Local) || oldNode.hasAttr(attr.Name.Local) != newNode.hasAttr(attr.Name.Local)) { changes[attr.Name.Local] = newNode.attr(attr.Name.Local) diff --git a/ofdgo_geom_backend.go b/ofdgo_geom_backend.go index a2a36a8..ee994d1 100644 --- a/ofdgo_geom_backend.go +++ b/ofdgo_geom_backend.go @@ -47,7 +47,7 @@ type StrokeOptions struct { } // GeometryBackend 操作库自有路径,坐标以页面左上角为原点,不修改任何输入 -// Clip返回nil表示不裁剪,空路径指针表示完全裁去,矩阵映射到页面坐标 +// Clip返回nil表示不裁剪,空路径指针表示完全裁去,矩阵将裁剪坐标映射到页面坐标 type GeometryBackend interface { Backend Path(object PathObject) (GeometryPath, error) diff --git a/ofdgo_object.go b/ofdgo_object.go index 8abfdb4..94c95fc 100644 --- a/ofdgo_object.go +++ b/ofdgo_object.go @@ -47,6 +47,12 @@ func (o GraphicObject) Actions() []Action { return nil } +// usesObjectMatrix 判断旧式扩展是否要求裁剪跟随对象变换,标准裁剪以外接矩形为基准 +// 返回: bool 是否叠加对象CTM +func (clips *Clips) usesObjectMatrix() bool { + return clips != nil && clips.TransFlag != nil && *clips.TransFlag +} + // UnmarshalXML 解析路径并区分省略样式、显式实线与零线宽 // 入参: d XML解码器, start 起始节点 // 返回: error 错误信息 diff --git a/ofdgo_page.go b/ofdgo_page.go index d402be6..c0bee7e 100644 --- a/ofdgo_page.go +++ b/ofdgo_page.go @@ -62,7 +62,8 @@ type GraphicObject struct { state editorCompositeState } -// Clips 裁剪区域集合 +// Clips 相对于对象外接矩形的裁剪集合 +// TransFlag仅兼容旧式扩展,写入时折算为Area的CTM type Clips struct { TransFlag *bool `xml:"TransFlag,attr"` Clip []Clip `xml:"Clip"` diff --git a/ofdgo_pdf.go b/ofdgo_pdf.go index a320d61..451ff87 100644 --- a/ofdgo_pdf.go +++ b/ofdgo_pdf.go @@ -502,7 +502,7 @@ func (p *pdfImporter) clips(paths []pdfgo.Path, origin Box) (*Clips, error) { continue } if clips == nil { - clips = &Clips{TransFlag: new(false), Clip: make([]Clip, 0, len(paths))} + clips = &Clips{Clip: make([]Clip, 0, len(paths))} areas = make([]ClipArea, len(paths)) if origin.X != 0 || origin.Y != 0 { translation = pdfNumbers(1, 0, 0, 1, -origin.X, -origin.Y) diff --git a/ofdgo_pdf_clip_group.go b/ofdgo_pdf_clip_group.go index b15ff7e..dd26aef 100644 --- a/ofdgo_pdf_clip_group.go +++ b/ofdgo_pdf_clip_group.go @@ -74,7 +74,7 @@ func pdfPageClip(object GraphicObject) *Clips { return nil } clips := *editorObjectClips(&object) - if clips == nil || clips.TransFlag == nil || *clips.TransFlag || len(clips.Clip) == 0 { + if clips == nil || clips.usesObjectMatrix() || len(clips.Clip) == 0 { return nil } boundary, _ := editorGeometry(object) @@ -87,7 +87,7 @@ func pdfPageClip(object GraphicObject) *Clips { translation = pdfNumbers(1, 0, 0, 1, -box.X, -box.Y) } bytes := 0 - result := &Clips{TransFlag: clips.TransFlag, Clip: make([]Clip, len(clips.Clip))} + result := &Clips{Clip: make([]Clip, len(clips.Clip))} for index, clip := range clips.Clip { if len(clip.Area) != 1 || clip.Area[0].CTM != translation { return nil @@ -113,7 +113,7 @@ func pdfMatchesPageClip(object GraphicObject, pageClip *Clips) bool { return false } clips := *editorObjectClips(&object) - if clips == nil || clips.TransFlag == nil || *clips.TransFlag || len(clips.Clip) != len(pageClip.Clip) { + if clips == nil || clips.usesObjectMatrix() || len(clips.Clip) != len(pageClip.Clip) { return false } boundary, _ := editorGeometry(object) diff --git a/ofdgo_render_text.go b/ofdgo_render_text.go index 6090c37..b2b7fd4 100644 --- a/ofdgo_render_text.go +++ b/ofdgo_render_text.go @@ -373,10 +373,7 @@ func (r *Renderer) objectGeometryClip(clips *Clips, boundary string, local Matri return clip, nil } matrix, _ := renderObjectMatrix(boundary, local, state) - if clips.TransFlag != nil && !*clips.TransFlag { - copy := *clips - copy.TransFlag = nil - clips = © + if !clips.usesObjectMatrix() { box, _ := ParseBox(boundary) matrix = TranslationMatrix(box.X, box.Y) if state.BoundaryInCTM && state.Parent != nil { diff --git a/ofdgo_render_walk.go b/ofdgo_render_walk.go index e9260b7..64556b8 100644 --- a/ofdgo_render_walk.go +++ b/ofdgo_render_walk.go @@ -225,10 +225,8 @@ func (r *Renderer) walkComposite(object CompositeGraphicUnit, state RenderState, return err } clips, clipMatrix := object.Clips, matrix - if clips != nil && clips.TransFlag != nil && !*clips.TransFlag { - copy := *clips - copy.TransFlag = nil - clips, clipMatrix = ©, boundary + if !clips.usesObjectMatrix() { + clipMatrix = boundary } if clips != nil { geometry, err := r.Geometry() diff --git a/ofdgo_renderer_clip_canvas.go b/ofdgo_renderer_clip_canvas.go index fb8d71e..7bdd956 100644 --- a/ofdgo_renderer_clip_canvas.go +++ b/ofdgo_renderer_clip_canvas.go @@ -86,9 +86,9 @@ func (r *Renderer) buildObjectClipPath(clips *Clips, pageH float64, boundary str } // buildClipPath 构建裁剪路径 -// 入参: clips 裁剪对象, pageH 页面高度, bx 边界X坐标, by 边界Y坐标, objectCTM 对象CTM +// 入参: clips 裁剪对象, pageH 页面高度, bx 边界X坐标, by 边界Y坐标, matrix 裁剪坐标到页面的变换 // 返回: *canvas.Path 路径对象 -func (r *Renderer) buildClipPath(clips *Clips, pageH float64, bx, by float64, objectCTM Matrix) *canvas.Path { +func (r *Renderer) buildClipPath(clips *Clips, pageH float64, bx, by float64, matrix Matrix) *canvas.Path { if clips == nil { return nil } @@ -96,10 +96,7 @@ func (r *Renderer) buildClipPath(clips *Clips, pageH float64, bx, by float64, ob for _, clip := range clips.Clip { renderer := &clipRenderer{} for _, area := range clip.Area { - areaCTM := NewMatrix(area.CTM) - if clips.TransFlag == nil || *clips.TransFlag { - areaCTM = objectCTM.Multiply(areaCTM) - } + areaCTM := matrix.Multiply(NewMatrix(area.CTM)) for _, pathObj := range area.Path { renderer.add(r.buildClipAreaPath(pathObj, area.DrawParam, areaCTM, pageH, bx, by)) if r.renderError != nil { diff --git a/ofdgo_renderer_geom_canvas.go b/ofdgo_renderer_geom_canvas.go index f921f10..66aa8aa 100644 --- a/ofdgo_renderer_geom_canvas.go +++ b/ofdgo_renderer_geom_canvas.go @@ -181,7 +181,7 @@ func (CanvasBackend) Stroke(path GeometryPath, options StrokeOptions) (result Ge } // Clip 解析对象裁剪并与父裁剪相交,保留未裁剪与完全裁去的区别 -// 入参: r 渲染器, clips OFD裁剪, matrix 页面变换, parent 父裁剪 +// 入参: r 渲染器, clips OFD裁剪, matrix 裁剪坐标到页面的变换, parent 父裁剪 // 返回: *GeometryPath 页面裁剪, error 无效父路径错误 func (CanvasBackend) Clip(r *Renderer, clips *Clips, matrix Matrix, parent *GeometryPath) (*GeometryPath, error) { p, err := geometryToCanvasPath(parent) diff --git a/ofdgo_writer.go b/ofdgo_writer.go index c218308..4234d58 100644 --- a/ofdgo_writer.go +++ b/ofdgo_writer.go @@ -870,17 +870,22 @@ func (x *ofdXML) object(object GraphicObject, root bool) { x.start(object.Type, attrs) x.actions(actions) if object.Type == "ImageObject" { - x.clips(object.ImageObject.Clips) + ctm := object.ImageObject.CTM + if ctm == "" && object.ImageObject.Clips.usesObjectMatrix() { + box, _ := ParseBox(object.ImageObject.Boundary) + ctm = (Matrix{a: box.W, d: box.H}).String() + } + x.clips(object.ImageObject.Clips, ctm) x.border(object.ImageObject.Border) } else if object.Type == "CompositeObject" || object.Type == "CompositeGraphicUnit" { - x.clips(object.CompositeGraphicUnit.Clips) + x.clips(object.CompositeGraphicUnit.Clips, object.CompositeGraphicUnit.CTM) } else if object.Type == "PathObject" || object.Type == "Path" { - x.clips(object.PathObject.Clips) + x.clips(object.PathObject.Clips, object.PathObject.CTM) x.color("StrokeColor", stroke) x.color("FillColor", fill) x.text("AbbreviatedData", object.PathObject.AbbreviatedData) } else if object.Type == "TextObject" || object.Type == "Text" { - x.clips(object.TextObject.Clips) + x.clips(object.TextObject.Clips, object.TextObject.CTM) x.color("FillColor", fill) x.color("StrokeColor", stroke) offset, index := 0, 0 @@ -917,15 +922,17 @@ func (x *ofdXML) object(object GraphicObject, root bool) { x.end(object.Type) } -// clips 写出对象裁剪,保留区域内的路径与文字 -// 入参: clips 裁剪集合,nil不输出节点 -func (x *ofdXML) clips(clips *Clips) { +// clips 写出标准裁剪,将旧式随动裁剪折算为区域变换 +// 入参: clips 裁剪集合,nil不输出节点, ctm 对象变换矩阵 +func (x *ofdXML) clips(clips *Clips, ctm string) { if clips == nil || !x.checkContext() { return } - var attrs ofdAttrs - attrs.flag("TransFlag", clips.TransFlag) - x.start("Clips", attrs) + matrix := IdentityMatrix + if clips.usesObjectMatrix() { + matrix = NewMatrix(ctm) + } + x.start("Clips", nil) for _, clip := range clips.Clip { if x.err != nil { return @@ -936,6 +943,9 @@ func (x *ofdXML) clips(clips *Clips) { return } var attrs ofdAttrs + if clips.usesObjectMatrix() { + area.CTM = matrix.Multiply(NewMatrix(area.CTM)).String() + } attrs.add("CTM", area.CTM) attrs.add("DrawParam", area.DrawParam) for _, path := range area.Path {