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)}}
+}