This closes #1395, add new function SetLegacyDrawingHF support to set graphics in a header/footer (#2018)

This commit is contained in:
Ilia Mirkin 2024-11-03 21:39:55 -05:00 committed by GitHub
parent 0d5d1c53b2
commit b7375bc6d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 171 additions and 8 deletions

View File

@ -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 // addSheetDrawing provides a function to add drawing element to
// xl/worksheets/sheet%d.xml by given worksheet name and relationship index. // xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
func (f *File) addSheetDrawing(sheet string, rID int) { func (f *File) addSheetDrawing(sheet string, rID int) {

View File

@ -1239,7 +1239,7 @@ func attrValToBool(name string, attrs []xml.Attr) (val bool, err error) {
// | // |
// &F | Current workbook's file name // &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 // &H | Shadow text format
// | // |

79
vml.go
View File

@ -1070,3 +1070,82 @@ func extractVMLFont(font []decodeVMLFont) []RichTextRun {
} }
return runs 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()
}

View File

@ -20,7 +20,7 @@ type vmlDrawing struct {
XMLNSv string `xml:"xmlns:v,attr"` XMLNSv string `xml:"xmlns:v,attr"`
XMLNSo string `xml:"xmlns:o,attr"` XMLNSo string `xml:"xmlns:o,attr"`
XMLNSx string `xml:"xmlns:x,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"` ShapeLayout *xlsxShapeLayout `xml:"o:shapelayout"`
ShapeType *xlsxShapeType `xml:"v:shapetype"` ShapeType *xlsxShapeType `xml:"v:shapetype"`
Shape []xlsxShape `xml:"v:shape"` Shape []xlsxShape `xml:"v:shape"`
@ -44,6 +44,7 @@ type xlsxIDmap struct {
type xlsxShape struct { type xlsxShape struct {
XMLName xml.Name `xml:"v:shape"` XMLName xml.Name `xml:"v:shape"`
ID string `xml:"id,attr"` ID string `xml:"id,attr"`
Spid string `xml:"o:spid,attr,omitempty"`
Type string `xml:"type,attr"` Type string `xml:"type,attr"`
Style string `xml:"style,attr"` Style string `xml:"style,attr"`
Button string `xml:"o:button,attr,omitempty"` Button string `xml:"o:button,attr,omitempty"`
@ -57,12 +58,17 @@ type xlsxShape struct {
// xlsxShapeType directly maps the shapetype element. // xlsxShapeType directly maps the shapetype element.
type xlsxShapeType struct { type xlsxShapeType struct {
ID string `xml:"id,attr"` ID string `xml:"id,attr"`
CoordSize string `xml:"coordsize,attr"` CoordSize string `xml:"coordsize,attr"`
Spt int `xml:"o:spt,attr"` Spt int `xml:"o:spt,attr"`
Path string `xml:"path,attr"` PreferRelative string `xml:"o:preferrelative,attr,omitempty"`
Stroke *xlsxStroke `xml:"v:stroke"` Path string `xml:"path,attr"`
VPath *vPath `xml:"v:path"` 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. // xlsxStroke directly maps the stroke element.
@ -72,10 +78,28 @@ type xlsxStroke struct {
// vPath directly maps the v:path element. // vPath directly maps the v:path element.
type vPath struct { type vPath struct {
ExtrusionOK string `xml:"o:extrusionok,attr,omitempty"`
GradientShapeOK string `xml:"gradientshapeok,attr,omitempty"` GradientShapeOK string `xml:"gradientshapeok,attr,omitempty"`
ConnectType string `xml:"o:connecttype,attr"` 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 // vFill directly maps the v:fill element. This element must be defined within a
// Shape element. // Shape element.
type vFill struct { type vFill struct {
@ -106,6 +130,13 @@ type vTextBox struct {
Div *xlsxDiv `xml:"div"` 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. // xlsxDiv directly maps the div element.
type xlsxDiv struct { type xlsxDiv struct {
Style string `xml:"style,attr"` Style string `xml:"style,attr"`
@ -254,7 +285,9 @@ type encodeShape struct {
Shadow *vShadow `xml:"v:shadow"` Shadow *vShadow `xml:"v:shadow"`
Path *vPath `xml:"v:path"` Path *vPath `xml:"v:path"`
TextBox *vTextBox `xml:"v:textbox"` TextBox *vTextBox `xml:"v:textbox"`
ImageData *vImageData `xml:"v:imagedata"`
ClientData *xClientData `xml:"x:ClientData"` ClientData *xClientData `xml:"x:ClientData"`
Lock *oLock `xml:"o:lock"`
} }
// formCtrlPreset defines the structure used to form control presets. // formCtrlPreset defines the structure used to form control presets.
@ -301,3 +334,12 @@ type FormControl struct {
Type FormControlType Type FormControlType
Format GraphicOptions 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
}

View File

@ -412,3 +412,35 @@ func TestExtractFormControl(t *testing.T) {
_, err := extractFormControl(string(MacintoshCyrillicCharset)) _, err := extractFormControl(string(MacintoshCyrillicCharset))
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") 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())
}