diff --git a/picture.go b/picture.go index 4e8a652a25..03bc1bfe3a 100644 --- a/picture.go +++ b/picture.go @@ -13,10 +13,12 @@ package excelize import ( "bytes" + "compress/gzip" "encoding/json" "encoding/xml" "image" "io" + "io/ioutil" "os" "path" "path/filepath" @@ -721,3 +723,155 @@ func (f *File) drawingResize(sheet, cell string, width, height float64, opts *pi w, h = int(width*opts.XScale), int(height*opts.YScale) return } + +// getImageDataFormat provides a function to query format of image data +// by given data. +func getImageDataFormat(data []byte) string { + if len(data) == 0 { + return "" + } + for ext, cmpFunc := range supportedImageHeaderFormat { + if cmpFunc(data) { + return ext + } + } + return "" +} + +// isGIF provides a function to determine whether the format of given image data is gif +func isGIF(data []byte) bool { + pattern := []byte{0x47, 0x49, 0x46, 0x38} + if len(data) < len(pattern) { + return false + } + return bytes.Equal(data[0:len(pattern)], pattern) +} + +func isJPEG(data []byte) bool { + pattern := []byte{0xFF, 0xD8, 0xFF} + if len(data) < len(pattern) { + return false + } + return bytes.Equal(data[0:len(pattern)], pattern) +} + +// isPNG provides a function to determine whether the format of given image data is png +func isPNG(data []byte) bool { + pattern := []byte{0x89, 0x50, 0x4E} + if len(data) < len(pattern) { + return false + } + return bytes.Equal(data[0:len(pattern)], pattern) +} + +// isTIFF provides a function to determine whether the format of given image data is tiff +func isTIFF(data []byte) bool { + patterns := [][]byte{ + {0x4D, 0x4D}, + {0x49, 0x49}, + } + for _, pattern := range patterns { + if len(data) < len(pattern) { + continue + } + if bytes.Equal(data[0:len(pattern)], pattern) { + return true + } + } + return false +} + +// isEMF provides a function to determine whether the format of given image data is emf +func isEMF(data []byte) bool { + patterns := [][]byte{ + {0x01, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00}, + {0x01, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00}, + } + for _, pattern := range patterns { + if len(data) < len(pattern) { + continue + } + if bytes.Equal(data[0:len(pattern)], pattern) { + return true + } + } + return false +} + +// isWMF provides a function to determine whether the format of given image data is wmf +func isWMF(data []byte) bool { + patterns := [][]byte{ + {0x01, 0x00, 0x09, 0x00}, + {0x02, 0x00, 0x09, 0x00}, + {0xD7, 0xCD, 0xC6, 0x9A}, + } + for _, pattern := range patterns { + if len(data) < len(pattern) { + continue + } + if bytes.Equal(data[0:len(pattern)], pattern) { + return true + } + } + return false +} + +// isEMZ provides a function to determine whether the format of given image data is emz +func isEMZ(data []byte) bool { + pattern := []byte{0x1F, 0x8B} + if len(data) < len(pattern) { + return false + } + isEqual := bytes.Equal(data[0:len(pattern)], pattern) + if !isEqual { + return false + } + b := bytes.NewBuffer(data) + reader, err := gzip.NewReader(b) + if err != nil { + return false + } + res, err := ioutil.ReadAll(reader) + if err != nil { + return false + } + return isEMF(res) +} + +// isWMZ provides a function to determine whether the format of given image data is wmz +func isWMZ(data []byte) bool { + pattern := []byte{0x1F, 0x8B} + if len(data) < len(pattern) { + return false + } + isEqual := bytes.Equal(data[0:len(pattern)], pattern) + if !isEqual { + return false + } + b := bytes.NewBuffer(data) + reader, err := gzip.NewReader(b) + if err != nil { + return false + } + res, err := ioutil.ReadAll(reader) + if err != nil { + return false + } + return isWMF(res) +} + +// isSVG provides a function to determine whether the format of given image data is svg +func isSVG(data []byte) bool { + type Result struct { + XMLName xml.Name `xml:"svg"` + } + var res Result + err := xml.Unmarshal(data, &res) + if err != nil { + return false + } + if res.XMLName.Local == "svg" { + return true + } + return false +} diff --git a/sheet.go b/sheet.go index 3ac933bc21..c569315552 100644 --- a/sheet.go +++ b/sheet.go @@ -497,11 +497,27 @@ func (f *File) SetSheetBackground(sheet, picture string) error { return ErrImgExt } file, _ := os.ReadFile(filepath.Clean(picture)) - name := f.addMedia(file, ext) + return f.setSheetBackground(sheet, ext, file) +} + +// SetSheetBackgroundFromBytes provides a function to set background picture +// by given worksheet name and image data +func (f *File) SetSheetBackgroundFromBytes(sheet string, picture []byte) error { + ext, ok := supportedImageTypes[getImageDataFormat(picture)] + if !ok { + return ErrImgExt + } + return f.setSheetBackground(sheet, ext, picture) +} + +// setSheetBackground provides a function to set background picture by given +// worksheet name, file name extension and image data. +func (f *File) setSheetBackground(sheet, ext string, imageData []byte) error { + name := f.addMedia(imageData, ext) sheetXMLPath, _ := f.getSheetXMLPath(sheet) sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels" rID := f.addRels(sheetRels, SourceRelationshipImage, strings.Replace(name, "xl", "..", 1), "") - if err = f.addSheetPicture(sheet, rID); err != nil { + if err := f.addSheetPicture(sheet, rID); err != nil { return err } f.addSheetNameSpace(sheet, SourceRelationship) diff --git a/sheet_test.go b/sheet_test.go index 08c7c1a9ea..8d7b53dfc6 100644 --- a/sheet_test.go +++ b/sheet_test.go @@ -3,6 +3,8 @@ package excelize import ( "encoding/xml" "fmt" + "io/ioutil" + "os" "path/filepath" "strconv" "strings" @@ -463,3 +465,21 @@ func TestAttrValToFloat(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 42.1, got) } + +func TestSetSheetBackgroundFromBytes(t *testing.T) { + files := []string{"../../excelize.svg", "excel.emf", "excel.emz", "excel.gif", "excel.jpg", "excel.png", "excel.tif", "excel.wmf", "excel.wmz"} + f, err := OpenFile("./test/EmptyWorkbook.xlsx") + assert.NoError(t, err) + for i, file := range files { + img, err := os.Open(filepath.Join("test/images", file)) + assert.NoError(t, err) + content, err := ioutil.ReadAll(img) + assert.NoError(t, err) + assert.NoError(t, img.Close()) + assert.NoError(t, f.SetSheetBackgroundFromBytes("sheet1", content)) + assert.NoError(t, f.SaveAs(fmt.Sprintf("test/TestSetSheetBackgroundFromBytes%d.xlsx", i))) + } + assert.Error(t, f.SetSheetBackgroundFromBytes("sheet1", nil), ErrImgExt) + assert.Error(t, f.SetSheetBackgroundFromBytes("sheet1", []byte{123, 243, 235, 34, 6, 56, 134, 87, 36, 255, 23, 52}), ErrImgExt) + assert.NoError(t, f.Close()) +} diff --git a/test/EmptyWorkbook.xlsx b/test/EmptyWorkbook.xlsx new file mode 100644 index 0000000000..a4810cd824 Binary files /dev/null and b/test/EmptyWorkbook.xlsx differ diff --git a/xmlDrawing.go b/xmlDrawing.go index 56ddc0e780..83435ccc21 100644 --- a/xmlDrawing.go +++ b/xmlDrawing.go @@ -171,6 +171,19 @@ var supportedImageTypes = map[string]string{ ".tiff": ".tiff", ".wmf": ".wmf", ".wmz": ".wmz", } +// supportedImageHeaderFormat defined supported image data format. +var supportedImageHeaderFormat = map[string]func([]byte) bool{ + ".gif": isGIF, + ".jpeg": isJPEG, + ".png": isPNG, + ".tiff": isTIFF, + ".emf": isEMF, + ".wmf": isWMF, + ".emz": isEMZ, + ".wmz": isWMZ, + ".svg": isSVG, +} + // supportedContentTypes defined supported file format types. var supportedContentTypes = map[string]string{ ".xlam": ContentTypeAddinMacro,