Skip to content

Commit 30d3561

Browse files
authored
Rename SetLegacyDrawingHF to AddHeaderFooterImage (#2023)
- Add new exported HeaderFooterImagePositionType enumeration - An error will be return if the image format is unsupported - Rename exported data type HeaderFooterGraphics to HeaderFooterImageOptions - Support add and update exist header and footer images - Changes the VML data ID to sheet ID - Update unit tests - Update dependencies modules
1 parent d2be5cd commit 30d3561

File tree

6 files changed

+226
-84
lines changed

6 files changed

+226
-84
lines changed

go.mod

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ require (
88
github.com/stretchr/testify v1.8.4
99
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d
1010
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7
11-
golang.org/x/crypto v0.28.0
11+
golang.org/x/crypto v0.29.0
1212
golang.org/x/image v0.18.0
13-
golang.org/x/net v0.30.0
14-
golang.org/x/text v0.19.0
13+
golang.org/x/net v0.31.0
14+
golang.org/x/text v0.20.0
1515
)
1616

1717
require (

go.sum

+6-6
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d h1:llb0neMWDQe87IzJLS4Ci7
1515
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
1616
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 h1:hPVCafDV85blFTabnqKgNhDCkJX25eik94Si9cTER4A=
1717
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
18-
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
19-
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
18+
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
19+
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
2020
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
2121
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
22-
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
23-
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
24-
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
25-
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
22+
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
23+
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
24+
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
25+
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
2626
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
2727
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
2828
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

sheet.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1239,7 +1239,7 @@ func attrValToBool(name string, attrs []xml.Attr) (val bool, err error) {
12391239
// |
12401240
// &F | Current workbook's file name
12411241
// |
1242-
// &G | Drawing object as background (Use SetLegacyDrawingHF)
1242+
// &G | Drawing object as background (Use AddHeaderFooterImage)
12431243
// |
12441244
// &H | Shadow text format
12451245
// |

vml.go

+124-54
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ const (
3636
FormControlScrollBar
3737
)
3838

39+
// HeaderFooterImagePositionType is the type of header and footer image position.
40+
type HeaderFooterImagePositionType byte
41+
42+
// Worksheet header and footer image position types enumeration.
43+
const (
44+
HeaderFooterImagePositionLeft HeaderFooterImagePositionType = iota
45+
HeaderFooterImagePositionCenter
46+
HeaderFooterImagePositionRight
47+
)
48+
3949
// GetComments retrieves all comments in a worksheet by given worksheet name.
4050
func (f *File) GetComments(sheet string) ([]Comment, error) {
4151
var comments []Comment
@@ -519,6 +529,7 @@ func (f *File) addVMLObject(opts vmlOptions) error {
519529
}
520530
vmlID = f.countVMLDrawing() + 1
521531
}
532+
sheetID := f.getSheetID(opts.sheet)
522533
drawingVML := "xl/drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml"
523534
sheetRelationshipsDrawingVML := "../drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml"
524535
sheetXMLPath, _ := f.getSheetXMLPath(opts.sheet)
@@ -534,7 +545,7 @@ func (f *File) addVMLObject(opts vmlOptions) error {
534545
f.addSheetNameSpace(opts.sheet, SourceRelationship)
535546
f.addSheetLegacyDrawing(opts.sheet, rID)
536547
}
537-
if err = f.addDrawingVML(vmlID, drawingVML, prepareFormCtrlOptions(&opts)); err != nil {
548+
if err = f.addDrawingVML(sheetID, drawingVML, prepareFormCtrlOptions(&opts)); err != nil {
538549
return err
539550
}
540551
if !opts.formCtrl {
@@ -823,7 +834,7 @@ func (f *File) addFormCtrlShape(preset formCtrlPreset, col, row int, anchor stri
823834
// anchor value is a comma-separated list of data written out as: LeftColumn,
824835
// LeftOffset, TopRow, TopOffset, RightColumn, RightOffset, BottomRow,
825836
// BottomOffset.
826-
func (f *File) addDrawingVML(dataID int, drawingVML string, opts *vmlOptions) error {
837+
func (f *File) addDrawingVML(sheetID int, drawingVML string, opts *vmlOptions) error {
827838
col, row, err := CellNameToCoordinates(opts.FormControl.Cell)
828839
if err != nil {
829840
return err
@@ -843,7 +854,7 @@ func (f *File) addDrawingVML(dataID int, drawingVML string, opts *vmlOptions) er
843854
XMLNSx: "urn:schemas-microsoft-com:office:excel",
844855
XMLNSmv: "http://macVmlSchemaUri",
845856
ShapeLayout: &xlsxShapeLayout{
846-
Ext: "edit", IDmap: &xlsxIDmap{Ext: "edit", Data: dataID},
857+
Ext: "edit", IDmap: &xlsxIDmap{Ext: "edit", Data: sheetID},
847858
},
848859
ShapeType: &xlsxShapeType{
849860
ID: fmt.Sprintf("_x0000_t%d", vmlID),
@@ -1071,79 +1082,138 @@ func extractVMLFont(font []decodeVMLFont) []RichTextRun {
10711082
return runs
10721083
}
10731084

1074-
// SetLegacyDrawingHF provides a mechanism to set the graphics that
1075-
// can be referenced in the Header/Footer defitions via &G.
1085+
// AddHeaderFooterImage provides a mechanism to set the graphics that can be
1086+
// referenced in the header and footer definitions via &G, file base name,
1087+
// extension name and file bytes, supported image types: EMF, EMZ, GIF, JPEG,
1088+
// JPG, PNG, SVG, TIF, TIFF, WMF, and WMZ.
10761089
//
10771090
// The extension should be provided with a "." in front, e.g. ".png".
1078-
// The width/height should have units in them, e.g. "100pt".
1079-
func (f *File) SetLegacyDrawingHF(sheet string, g *HeaderFooterGraphics) error {
1091+
// The width and height should have units in them, e.g. "100pt".
1092+
func (f *File) AddHeaderFooterImage(sheet string, opts *HeaderFooterImageOptions) error {
1093+
ws, err := f.workSheetReader(sheet)
1094+
if err != nil {
1095+
return err
1096+
}
1097+
ext, ok := supportedImageTypes[strings.ToLower(opts.Extension)]
1098+
if !ok {
1099+
return ErrImgExt
1100+
}
1101+
sheetID := f.getSheetID(sheet)
10801102
vmlID := f.countVMLDrawing() + 1
1103+
drawingVML := "xl/drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml"
1104+
sheetRelationshipsDrawingVML := "../drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml"
1105+
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
1106+
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
1107+
if ws.LegacyDrawingHF != nil {
1108+
// The worksheet already has a VML relationships, use the relationships drawing ../drawings/vmlDrawing%d.vml.
1109+
sheetRelationshipsDrawingVML = f.getSheetRelationshipsTargetByID(sheet, ws.LegacyDrawingHF.RID)
1110+
vmlID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingVML, "../drawings/vmlDrawing"), ".vml"))
1111+
drawingVML = strings.ReplaceAll(sheetRelationshipsDrawingVML, "..", "xl")
1112+
} else {
1113+
// Add first VML drawing for given sheet.
1114+
rID := f.addRels(sheetRels, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "")
1115+
f.addSheetNameSpace(sheet, SourceRelationship)
1116+
f.addSheetLegacyDrawingHF(sheet, rID)
1117+
}
10811118

1082-
vml := &vmlDrawing{
1083-
XMLNSv: "urn:schemas-microsoft-com:vml",
1084-
XMLNSo: "urn:schemas-microsoft-com:office:office",
1085-
XMLNSx: "urn:schemas-microsoft-com:office:excel",
1086-
ShapeLayout: &xlsxShapeLayout{
1087-
Ext: "edit", IDmap: &xlsxIDmap{Ext: "edit", Data: vmlID},
1088-
},
1089-
ShapeType: &xlsxShapeType{
1090-
ID: "_x0000_t75",
1091-
CoordSize: "21600,21600",
1092-
Spt: 75,
1093-
PreferRelative: "t",
1094-
Path: "m@4@5l@4@11@9@11@9@5xe",
1095-
Filled: "f",
1096-
Stroked: "f",
1097-
Stroke: &xlsxStroke{JoinStyle: "miter"},
1098-
VFormulas: &vFormulas{
1099-
Formulas: []vFormula{
1100-
{Equation: "if lineDrawn pixelLineWidth 0"},
1101-
{Equation: "sum @0 1 0"},
1102-
{Equation: "sum 0 0 @1"},
1103-
{Equation: "prod @2 1 2"},
1104-
{Equation: "prod @3 21600 pixelWidth"},
1105-
{Equation: "prod @3 21600 pixelHeight"},
1106-
{Equation: "sum @0 0 1"},
1107-
{Equation: "prod @6 1 2"},
1108-
{Equation: "prod @7 21600 pixelWidth"},
1109-
{Equation: "sum @8 21600 0"},
1110-
{Equation: "prod @7 21600 pixelHeight"},
1111-
{Equation: "sum @10 21600 0"},
1119+
shapeID := map[HeaderFooterImagePositionType]string{
1120+
HeaderFooterImagePositionLeft: "L",
1121+
HeaderFooterImagePositionCenter: "C",
1122+
HeaderFooterImagePositionRight: "R",
1123+
}[opts.Position] +
1124+
map[bool]string{false: "H", true: "F"}[opts.IsFooter] +
1125+
map[bool]string{false: "", true: "FIRST"}[opts.FirstPage]
1126+
vml := f.VMLDrawing[drawingVML]
1127+
if vml == nil {
1128+
vml = &vmlDrawing{
1129+
XMLNSv: "urn:schemas-microsoft-com:vml",
1130+
XMLNSo: "urn:schemas-microsoft-com:office:office",
1131+
XMLNSx: "urn:schemas-microsoft-com:office:excel",
1132+
ShapeLayout: &xlsxShapeLayout{
1133+
Ext: "edit", IDmap: &xlsxIDmap{Ext: "edit", Data: sheetID},
1134+
},
1135+
ShapeType: &xlsxShapeType{
1136+
ID: "_x0000_t75",
1137+
CoordSize: "21600,21600",
1138+
Spt: 75,
1139+
PreferRelative: "t",
1140+
Path: "m@4@5l@4@11@9@11@9@5xe",
1141+
Filled: "f",
1142+
Stroked: "f",
1143+
Stroke: &xlsxStroke{JoinStyle: "miter"},
1144+
VFormulas: &vFormulas{
1145+
Formulas: []vFormula{
1146+
{Equation: "if lineDrawn pixelLineWidth 0"},
1147+
{Equation: "sum @0 1 0"},
1148+
{Equation: "sum 0 0 @1"},
1149+
{Equation: "prod @2 1 2"},
1150+
{Equation: "prod @3 21600 pixelWidth"},
1151+
{Equation: "prod @3 21600 pixelHeight"},
1152+
{Equation: "sum @0 0 1"},
1153+
{Equation: "prod @6 1 2"},
1154+
{Equation: "prod @7 21600 pixelWidth"},
1155+
{Equation: "sum @8 21600 0"},
1156+
{Equation: "prod @7 21600 pixelHeight"},
1157+
{Equation: "sum @10 21600 0"},
1158+
},
11121159
},
1160+
VPath: &vPath{ExtrusionOK: "f", GradientShapeOK: "t", ConnectType: "rect"},
1161+
Lock: &oLock{Ext: "edit", AspectRatio: "t"},
11131162
},
1114-
VPath: &vPath{ExtrusionOK: "f", GradientShapeOK: "t", ConnectType: "rect"},
1115-
Lock: &oLock{Ext: "edit", AspectRatio: "t"},
1116-
},
1163+
}
1164+
// Load exist VML shapes from xl/drawings/vmlDrawing%d.vml
1165+
d, err := f.decodeVMLDrawingReader(drawingVML)
1166+
if err != nil {
1167+
return err
1168+
}
1169+
if d != nil {
1170+
vml.ShapeType.ID = d.ShapeType.ID
1171+
vml.ShapeType.CoordSize = d.ShapeType.CoordSize
1172+
vml.ShapeType.Spt = d.ShapeType.Spt
1173+
vml.ShapeType.PreferRelative = d.ShapeType.PreferRelative
1174+
vml.ShapeType.Path = d.ShapeType.Path
1175+
vml.ShapeType.Filled = d.ShapeType.Filled
1176+
vml.ShapeType.Stroked = d.ShapeType.Stroked
1177+
for _, v := range d.Shape {
1178+
s := xlsxShape{
1179+
ID: v.ID,
1180+
SpID: v.SpID,
1181+
Type: v.Type,
1182+
Style: v.Style,
1183+
Val: v.Val,
1184+
}
1185+
vml.Shape = append(vml.Shape, s)
1186+
}
1187+
}
11171188
}
11181189

1119-
style := fmt.Sprintf("position:absolute;margin-left:0;margin-top:0;width:%s;height:%s;z-index:1", g.Width, g.Height)
1120-
drawingVML := "xl/drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml"
1190+
for idx, shape := range vml.Shape {
1191+
if shape.ID == shapeID {
1192+
vml.Shape = append(vml.Shape[:idx], vml.Shape[idx+1:]...)
1193+
}
1194+
}
1195+
1196+
style := fmt.Sprintf("position:absolute;margin-left:0;margin-top:0;width:%s;height:%s;z-index:1", opts.Width, opts.Height)
11211197
drawingVMLRels := "xl/drawings/_rels/vmlDrawing" + strconv.Itoa(vmlID) + ".vml.rels"
11221198

1123-
mediaStr := ".." + strings.TrimPrefix(f.addMedia(g.File, g.Extension), "xl")
1199+
mediaStr := ".." + strings.TrimPrefix(f.addMedia(opts.File, ext), "xl")
11241200
imageID := f.addRels(drawingVMLRels, SourceRelationshipImage, mediaStr, "")
11251201

11261202
shape := xlsxShape{
1127-
ID: "RH",
1128-
Spid: "_x0000_s1025",
1203+
ID: shapeID,
1204+
SpID: "_x0000_s1025",
11291205
Type: "#_x0000_t75",
11301206
Style: style,
11311207
}
1132-
s, _ := xml.Marshal(encodeShape{
1208+
sp, _ := xml.Marshal(encodeShape{
11331209
ImageData: &vImageData{RelID: "rId" + strconv.Itoa(imageID)},
11341210
Lock: &oLock{Ext: "edit", Rotation: "t"},
11351211
})
1136-
shape.Val = string(s[13 : len(s)-14])
1212+
1213+
shape.Val = string(sp[13 : len(sp)-14])
11371214
vml.Shape = append(vml.Shape, shape)
11381215
f.VMLDrawing[drawingVML] = vml
11391216

1140-
sheetRelationshipsDrawingVML := "../drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml"
1141-
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
1142-
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
1143-
1144-
drawingID := f.addRels(sheetRels, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "")
1145-
f.addSheetNameSpace(sheet, SourceRelationship)
1146-
f.addSheetLegacyDrawingHF(sheet, drawingID)
11471217
if err := f.setContentTypePartImageExtensions(); err != nil {
11481218
return err
11491219
}

vmlDrawing.go

+15-8
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ type xlsxIDmap struct {
4444
type xlsxShape struct {
4545
XMLName xml.Name `xml:"v:shape"`
4646
ID string `xml:"id,attr"`
47-
Spid string `xml:"o:spid,attr,omitempty"`
47+
SpID string `xml:"o:spid,attr,omitempty"`
4848
Type string `xml:"type,attr"`
4949
Style string `xml:"style,attr"`
5050
Button string `xml:"o:button,attr,omitempty"`
@@ -193,15 +193,19 @@ type decodeVmlDrawing struct {
193193
// decodeShapeType defines the structure used to parse the shapetype element in
194194
// the file xl/drawings/vmlDrawing%d.vml.
195195
type decodeShapeType struct {
196-
ID string `xml:"id,attr"`
197-
CoordSize string `xml:"coordsize,attr"`
198-
Spt int `xml:"spt,attr"`
199-
Path string `xml:"path,attr"`
196+
ID string `xml:"id,attr"`
197+
CoordSize string `xml:"coordsize,attr"`
198+
Spt int `xml:"spt,attr"`
199+
PreferRelative string `xml:"preferrelative,attr,omitempty"`
200+
Path string `xml:"path,attr"`
201+
Filled string `xml:"filled,attr,omitempty"`
202+
Stroked string `xml:"stroked,attr,omitempty"`
200203
}
201204

202205
// decodeShape defines the structure used to parse the particular shape element.
203206
type decodeShape struct {
204207
ID string `xml:"id,attr"`
208+
SpID string `xml:"spid,attr,omitempty"`
205209
Type string `xml:"type,attr"`
206210
Style string `xml:"style,attr"`
207211
Button string `xml:"button,attr,omitempty"`
@@ -335,10 +339,13 @@ type FormControl struct {
335339
Format GraphicOptions
336340
}
337341

338-
// HeaderFooterGraphics defines the settings for an image to be
339-
// accessible from the header/footer options.
340-
type HeaderFooterGraphics struct {
342+
// HeaderFooterImageOptions defines the settings for an image to be accessible
343+
// from the worksheet header and footer options.
344+
type HeaderFooterImageOptions struct {
345+
Position HeaderFooterImagePositionType
341346
File []byte
347+
IsFooter bool
348+
FirstPage bool
342349
Extension string
343350
Width string
344351
Height string

0 commit comments

Comments
 (0)