Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions dml/dmlpic/pic.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
Expand All @@ -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)
Expand All @@ -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 {
Expand Down
117 changes: 117 additions & 0 deletions dml/dmlpic/pic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/xml"
"strings"
"testing"
// "fmt"

"github.com/gomutex/godocx/dml/dmlct"
"github.com/gomutex/godocx/dml/dmlprops"
Expand Down Expand Up @@ -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 := `<pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture"><pic:nvPicPr><pic:cNvPr id="1" name="Pic 1" descr="Pic Description"></pic:cNvPr><pic:cNvPicPr><a:picLocks noChangeAspect="1" noChangeArrowheads="1"></a:picLocks></pic:cNvPicPr></pic:nvPicPr><pic:blipFill><a:blip r:embed="rId1"></a:blip><a:stretch><a:fillRect></a:fillRect></a:stretch></pic:blipFill><pic:spPr><a:xfrm rot="90000" flipH="true" flipV="false"><a:off x="0" y="0"></a:off><a:ext cx="100000" cy="100000"></a:ext></a:xfrm><a:prstGeom prst="rect"></a:prstGeom></pic:spPr></pic:pic>`

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 := `<pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:nvPicPr>
<pic:cNvPr id="1" name="Pic 1" descr="Description"></pic:cNvPr>
<pic:cNvPicPr>
<a:picLocks noChangeAspect="1" noChangeArrowheads="1"></a:picLocks>
</pic:cNvPicPr>
</pic:nvPicPr>
<pic:blipFill>
<a:blip r:embed="rId1"></a:blip>
<a:stretch>
<a:fillRect></a:fillRect>
</a:stretch>
</pic:blipFill>
<pic:spPr>
<a:xfrm rot="180000" flipH="false" flipV="true">
<a:off x="0" y="0"></a:off>
<a:ext cx="100000" cy="100000"></a:ext>
</a:xfrm>
<a:prstGeom prst="rect"></a:prstGeom>
</pic:spPr>
</pic:pic>`

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