diff --git a/picture.go b/picture.go index 42be183..96a9133 100644 --- a/picture.go +++ b/picture.go @@ -295,6 +295,16 @@ func (f *File) addSheetLegacyDrawing(sheet string, rID int) { } } +// addSheetLegacyDrawingHF provides a function to add legacy drawing +// header/footer element to xl/worksheets/sheet%d.xml by given +// worksheet name and relationship index. +func (f *File) addSheetLegacyDrawingHF(sheet string, rID int) { + ws, _ := f.workSheetReader(sheet) + ws.LegacyDrawingHF = &xlsxLegacyDrawingHF{ + RID: "rId" + strconv.Itoa(rID), + } +} + // addSheetDrawing provides a function to add drawing element to // xl/worksheets/sheet%d.xml by given worksheet name and relationship index. func (f *File) addSheetDrawing(sheet string, rID int) { diff --git a/sheet.go b/sheet.go index 48cf884..c52fb94 100644 --- a/sheet.go +++ b/sheet.go @@ -1239,7 +1239,7 @@ func attrValToBool(name string, attrs []xml.Attr) (val bool, err error) { // | // &F | Current workbook's file name // | -// &G | Drawing object as background (Not support currently) +// &G | Drawing object as background (Use SetLegacyDrawingHF) // | // &H | Shadow text format // | diff --git a/vml.go b/vml.go index 3f0f470..53ef815 100644 --- a/vml.go +++ b/vml.go @@ -1070,3 +1070,82 @@ func extractVMLFont(font []decodeVMLFont) []RichTextRun { } return runs } + +// SetLegacyDrawingHF provides a mechanism to set the graphics that +// can be referenced in the Header/Footer defitions via &G. +// +// The extension should be provided with a "." in front, e.g. ".png". +// The width/height should have units in them, e.g. "100pt". +func (f *File) SetLegacyDrawingHF(sheet string, g *HeaderFooterGraphics) error { + vmlID := f.countVMLDrawing() + 1 + + vml := &vmlDrawing{ + XMLNSv: "urn:schemas-microsoft-com:vml", + XMLNSo: "urn:schemas-microsoft-com:office:office", + XMLNSx: "urn:schemas-microsoft-com:office:excel", + ShapeLayout: &xlsxShapeLayout{ + Ext: "edit", IDmap: &xlsxIDmap{Ext: "edit", Data: vmlID}, + }, + ShapeType: &xlsxShapeType{ + ID: "_x0000_t75", + CoordSize: "21600,21600", + Spt: 75, + PreferRelative: "t", + Path: "m@4@5l@4@11@9@11@9@5xe", + Filled: "f", + Stroked: "f", + Stroke: &xlsxStroke{JoinStyle: "miter"}, + VFormulas: &vFormulas{ + Formulas: []vFormula{ + {Equation: "if lineDrawn pixelLineWidth 0"}, + {Equation: "sum @0 1 0"}, + {Equation: "sum 0 0 @1"}, + {Equation: "prod @2 1 2"}, + {Equation: "prod @3 21600 pixelWidth"}, + {Equation: "prod @3 21600 pixelHeight"}, + {Equation: "sum @0 0 1"}, + {Equation: "prod @6 1 2"}, + {Equation: "prod @7 21600 pixelWidth"}, + {Equation: "sum @8 21600 0"}, + {Equation: "prod @7 21600 pixelHeight"}, + {Equation: "sum @10 21600 0"}, + }, + }, + VPath: &vPath{ExtrusionOK: "f", GradientShapeOK: "t", ConnectType: "rect"}, + Lock: &oLock{Ext: "edit", AspectRatio: "t"}, + }, + } + + style := fmt.Sprintf("position:absolute;margin-left:0;margin-top:0;width:%s;height:%s;z-index:1", g.Width, g.Height) + drawingVML := "xl/drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml" + drawingVMLRels := "xl/drawings/_rels/vmlDrawing" + strconv.Itoa(vmlID) + ".vml.rels" + + mediaStr := ".." + strings.TrimPrefix(f.addMedia(g.File, g.Extension), "xl") + imageID := f.addRels(drawingVMLRels, SourceRelationshipImage, mediaStr, "") + + shape := xlsxShape{ + ID: "RH", + Spid: "_x0000_s1025", + Type: "#_x0000_t75", + Style: style, + } + s, _ := xml.Marshal(encodeShape{ + ImageData: &vImageData{RelID: "rId" + strconv.Itoa(imageID)}, + Lock: &oLock{Ext: "edit", Rotation: "t"}, + }) + shape.Val = string(s[13 : len(s)-14]) + vml.Shape = append(vml.Shape, shape) + f.VMLDrawing[drawingVML] = vml + + sheetRelationshipsDrawingVML := "../drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml" + sheetXMLPath, _ := f.getSheetXMLPath(sheet) + sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels" + + drawingID := f.addRels(sheetRels, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "") + f.addSheetNameSpace(sheet, SourceRelationship) + f.addSheetLegacyDrawingHF(sheet, drawingID) + if err := f.setContentTypePartImageExtensions(); err != nil { + return err + } + return f.setContentTypePartVMLExtensions() +} diff --git a/vmlDrawing.go b/vmlDrawing.go index 2c3a69a..c021018 100644 --- a/vmlDrawing.go +++ b/vmlDrawing.go @@ -20,7 +20,7 @@ type vmlDrawing struct { XMLNSv string `xml:"xmlns:v,attr"` XMLNSo string `xml:"xmlns:o,attr"` XMLNSx string `xml:"xmlns:x,attr"` - XMLNSmv string `xml:"xmlns:mv,attr"` + XMLNSmv string `xml:"xmlns:mv,attr,omitempty"` ShapeLayout *xlsxShapeLayout `xml:"o:shapelayout"` ShapeType *xlsxShapeType `xml:"v:shapetype"` Shape []xlsxShape `xml:"v:shape"` @@ -44,6 +44,7 @@ type xlsxIDmap struct { type xlsxShape struct { XMLName xml.Name `xml:"v:shape"` ID string `xml:"id,attr"` + Spid string `xml:"o:spid,attr,omitempty"` Type string `xml:"type,attr"` Style string `xml:"style,attr"` Button string `xml:"o:button,attr,omitempty"` @@ -57,12 +58,17 @@ type xlsxShape struct { // xlsxShapeType directly maps the shapetype element. type xlsxShapeType struct { - ID string `xml:"id,attr"` - CoordSize string `xml:"coordsize,attr"` - Spt int `xml:"o:spt,attr"` - Path string `xml:"path,attr"` - Stroke *xlsxStroke `xml:"v:stroke"` - VPath *vPath `xml:"v:path"` + ID string `xml:"id,attr"` + CoordSize string `xml:"coordsize,attr"` + Spt int `xml:"o:spt,attr"` + PreferRelative string `xml:"o:preferrelative,attr,omitempty"` + Path string `xml:"path,attr"` + Filled string `xml:"filled,attr,omitempty"` + Stroked string `xml:"stroked,attr,omitempty"` + Stroke *xlsxStroke `xml:"v:stroke"` + VFormulas *vFormulas `xml:"v:formulas"` + VPath *vPath `xml:"v:path"` + Lock *oLock `xml:"o:lock"` } // xlsxStroke directly maps the stroke element. @@ -72,10 +78,28 @@ type xlsxStroke struct { // vPath directly maps the v:path element. type vPath struct { + ExtrusionOK string `xml:"o:extrusionok,attr,omitempty"` GradientShapeOK string `xml:"gradientshapeok,attr,omitempty"` ConnectType string `xml:"o:connecttype,attr"` } +// oLock directly maps the o:lock element. +type oLock struct { + Ext string `xml:"v:ext,attr"` + Rotation string `xml:"rotation,attr,omitempty"` + AspectRatio string `xml:"aspectratio,attr,omitempty"` +} + +// vFormulas directly maps to the v:formulas element +type vFormulas struct { + Formulas []vFormula `xml:"v:f"` +} + +// vFormula directly maps to the v:f element +type vFormula struct { + Equation string `xml:"eqn,attr"` +} + // vFill directly maps the v:fill element. This element must be defined within a // Shape element. type vFill struct { @@ -106,6 +130,13 @@ type vTextBox struct { Div *xlsxDiv `xml:"div"` } +// vImageData directly maps the v:imagedata element. This element must be +// defined within a Shape element. +type vImageData struct { + RelID string `xml:"o:relid,attr"` + Title string `xml:"o:title,attr,omitempty"` +} + // xlsxDiv directly maps the div element. type xlsxDiv struct { Style string `xml:"style,attr"` @@ -254,7 +285,9 @@ type encodeShape struct { Shadow *vShadow `xml:"v:shadow"` Path *vPath `xml:"v:path"` TextBox *vTextBox `xml:"v:textbox"` + ImageData *vImageData `xml:"v:imagedata"` ClientData *xClientData `xml:"x:ClientData"` + Lock *oLock `xml:"o:lock"` } // formCtrlPreset defines the structure used to form control presets. @@ -301,3 +334,12 @@ type FormControl struct { Type FormControlType Format GraphicOptions } + +// HeaderFooterGraphics defines the settings for an image to be +// accessible from the header/footer options. +type HeaderFooterGraphics struct { + File []byte + Extension string + Width string + Height string +} diff --git a/vml_test.go b/vml_test.go index d05b67f..b79e63d 100644 --- a/vml_test.go +++ b/vml_test.go @@ -412,3 +412,35 @@ func TestExtractFormControl(t *testing.T) { _, err := extractFormControl(string(MacintoshCyrillicCharset)) assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } + +func TestSetLegacyDrawingHF(t *testing.T) { + f := NewFile() + sheet := "Sheet1" + headerFooterOptions := HeaderFooterOptions{ + OddHeader: "&LExcelize&R&G", + } + assert.NoError(t, f.SetHeaderFooter(sheet, &headerFooterOptions)) + file, err := os.ReadFile(filepath.Join("test", "images", "excel.png")) + assert.NoError(t, err) + assert.NoError(t, f.SetLegacyDrawingHF(sheet, &HeaderFooterGraphics{ + Extension: ".png", + File: file, + Width: "50pt", + Height: "32pt", + })) + assert.NoError(t, f.SetCellValue(sheet, "A1", "Example")) + assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetLegacyDrawingHF.xlsx"))) + assert.NoError(t, f.Close()) + + // Test set legacy drawing header/footer with unsupported charset content types + f = NewFile() + f.ContentTypes = nil + f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) + assert.EqualError(t, f.SetLegacyDrawingHF(sheet, &HeaderFooterGraphics{ + Extension: ".png", + File: file, + Width: "50pt", + Height: "32pt", + }), "XML syntax error on line 1: invalid UTF-8") + assert.NoError(t, f.Close()) +}