diff --git a/dml/dmlpic/pic.go b/dml/dmlpic/pic.go index 7ecb16e..f951bee 100644 --- a/dml/dmlpic/pic.go +++ b/dml/dmlpic/pic.go @@ -85,6 +85,9 @@ func (p Pic) MarshalXML(e *xml.Encoder, start xml.StartElement) error { } type TransformGroup struct { + Rotation *uint64 `xml:"rot,attr,omitempty"` + FlipH *bool `xml:"flipH, attr, omitempty"` + FlipV *bool `xml:"flipV, attr, omitempty"` Extent *dmlct.PSize2D `xml:"ext,omitempty"` Offset *Offset `xml:"off,omitempty"` } @@ -93,6 +96,9 @@ type TFGroupOption func(*TransformGroup) func NewTransformGroup(options ...TFGroupOption) *TransformGroup { tf := &TransformGroup{} + *tf.Rotation = 0 + *tf.FlipH = false + *tf.FlipV = false for _, opt := range options { opt(tf) @@ -109,6 +115,16 @@ func WithTFExtent(width units.Emu, height units.Emu) TFGroupOption { func (t TransformGroup) MarshalXML(e *xml.Encoder, start xml.StartElement) error { start.Name.Local = "a:xfrm" + start.Attr = []xml.Attr{} + if t.Rotation != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "rot"}, Value: strconv.FormatUint(*t.Rotation, 10)}) + } + if t.FlipH != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "flipH"}, Value: strconv.FormatBool(*t.FlipH)}) + } + if t.FlipV != nil { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "flipV"}, Value: strconv.FormatBool(*t.FlipV)}) + } err := e.EncodeToken(start) if err != nil { diff --git a/dml/dmlpic/pic_test.go b/dml/dmlpic/pic_test.go index 74007d6..4d6d3f1 100644 --- a/dml/dmlpic/pic_test.go +++ b/dml/dmlpic/pic_test.go @@ -4,6 +4,7 @@ import ( "encoding/xml" "strings" "testing" +// "fmt" "github.com/gomutex/godocx/dml/dmlct" "github.com/gomutex/godocx/dml/dmlprops" @@ -106,3 +107,119 @@ func TestPicUnmarshalXML(t *testing.T) { checkNotNil("BlipFill", pic.BlipFill) checkNotNil("PicShapeProp", pic.PicShapeProp) } +// Source - https://stackoverflow.com/a/28818489 +// Posted by icza, modified by community. See post 'Timeline' for change history +// Retrieved 2025-12-18, License - CC BY-SA 3.0 + +func newBool(bb bool) *bool { + b := true + if !bb {b= false} + return &b +} + +func newUint(num uint64) *uint64 { + return &num +} + +func TestPicV2MarshalXML(t *testing.T) { + p := &Pic{ + NonVisualPicProp: NonVisualPicProp{ + CNvPr: dmlct.CNvPr{ + ID: 1, + Name: "Pic 1", + Description: "Pic Description", + }, + CNvPicPr: CNvPicPr{ + PicLocks: &dmlprops.PicLocks{ + NoChangeAspect: dmlst.NewOptBool(true), + NoChangeArrowheads: dmlst.NewOptBool(true), + }, + }, + }, + BlipFill: BlipFill{ + Blip: &Blip{ + EmbedID: "rId1", + }, + FillModeProps: FillModeProps{ + Stretch: &shapes.Stretch{ + FillRect: &dmlct.RelativeRect{}, + }, + }, + }, + PicShapeProp: PicShapeProp{ + TransformGroup: &TransformGroup{ + Rotation: newUint(90000), + FlipH: newBool(true), + FlipV: newBool(false), + Offset: &Offset{ + X: 0, + Y: 0, + }, + Extent: &dmlct.PSize2D{ + Width: 100000, + Height: 100000, + }, + }, + PresetGeometry: &PresetGeometry{ + Preset: "rect", + }, + }, + } + + // Expected XML output + expectedXML := `` + + output, err := xml.Marshal(p) + if err != nil { + t.Fatalf("Error marshaling Pic to XML: %v", err) + } + + if strings.TrimSpace(string(output)) != strings.TrimSpace(expectedXML) { + t.Errorf("Generated XML does not match expected XML.\nExpected:\n%s\nGenerated:\n%s", expectedXML, output) + } +} + +func TestPicV2UnmarshalXML(t *testing.T) { + xmlData := ` + + + + + + + + + + + + + + + + + + + + ` + + var pic Pic + + err := xml.NewDecoder(strings.NewReader(xmlData)).Decode(&pic) + if err != nil { + t.Errorf("Error decoding XML: %v", err) + } + + checkNotNil := func(fieldName string, fieldValue interface{}) { + if fieldValue == nil { + t.Errorf("Expected field '%s' to be unmarshaled, but it was nil", fieldName) + } + } + + checkNotNil("NonVisualPicProp", pic.NonVisualPicProp) + checkNotNil("BlipFill", pic.BlipFill) + checkNotNil("PicShapeProp", pic.PicShapeProp) + + tfg :=pic.PicShapeProp.TransformGroup + + if tfg.Rotation != nil {if *tfg.Rotation != uint64(180000) {t.Errorf(" invalid Rotation: %d", *tfg.Rotation)}} +}