Skip to content

Commit

Permalink
Add a way to set graphics in a header/footer
Browse files Browse the repository at this point in the history
The full functionality of the VML drawing is not addressed here. However
this seems to work for embedding regular images.
  • Loading branch information
imirkin committed Nov 3, 2024
1 parent 0d5d1c5 commit 2e43add
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 8 deletions.
9 changes: 9 additions & 0 deletions picture.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,15 @@ func (f *File) addSheetLegacyDrawing(sheet string, rID int) {
}
}

// addSheetLegacyDrawing provides a function to add legacy drawing 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) {
Expand Down
2 changes: 1 addition & 1 deletion sheet.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
// |
Expand Down
80 changes: 80 additions & 0 deletions vml.go
Original file line number Diff line number Diff line change
Expand Up @@ -1070,3 +1070,83 @@ 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{
vFormula{Equation: "if lineDrawn pixelLineWidth 0"},
vFormula{Equation: "sum @0 1 0"},
vFormula{Equation: "sum 0 0 @1"},
vFormula{Equation: "prod @2 1 2"},
vFormula{Equation: "prod @3 21600 pixelWidth"},
vFormula{Equation: "prod @3 21600 pixelHeight"},
vFormula{Equation: "sum @0 0 1"},
vFormula{Equation: "prod @6 1 2"},
vFormula{Equation: "prod @7 21600 pixelWidth"},
vFormula{Equation: "sum @8 21600 0"},
vFormula{Equation: "prod @7 21600 pixelHeight"},
vFormula{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)
err := f.setContentTypePartImageExtensions()
if err != nil {
return err
}
return f.setContentTypePartVMLExtensions()
}
56 changes: 49 additions & 7 deletions vmlDrawing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -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"`
Expand All @@ -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.
Expand All @@ -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 {
Expand Down Expand Up @@ -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"`
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
20 changes: 20 additions & 0 deletions vml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,3 +412,23 @@ func TestExtractFormControl(t *testing.T) {
_, err := extractFormControl(string(MacintoshCyrillicCharset))
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}

func ExampleFile_SetLegacyDrawingHF() {
f := NewFile()
sheet := "Sheet1"
headerFooterOptions := HeaderFooterOptions{
OddHeader: "&LExcelize&R&G",
}
f.SetHeaderFooter(sheet, &headerFooterOptions)
file, _ := os.ReadFile("test/images/excel.png")
f.SetLegacyDrawingHF(sheet, &HeaderFooterGraphics{
Extension: ".png",
File: file,
Width: "50pt",
Height: "32pt",
})
f.SetCellValue(sheet, "A1", "Example")
out, _ := os.CreateTemp("", "header-graphics-*.xlsx")
f.Write(out)
f.Close()
}

0 comments on commit 2e43add

Please sign in to comment.