Skip to content

Commit

Permalink
This closes qax-os#1405, add new function SetSheetBackgroundFromBytes…
Browse files Browse the repository at this point in the history
… used for setting background image by given image data

Change-Id: I83baac1107f66dfa5d9d2eebfa7e40f9f49b79d5
  • Loading branch information
houjianxin.rupert committed Nov 28, 2022
1 parent dde6b9c commit 422fd12
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 2 deletions.
154 changes: 154 additions & 0 deletions picture.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ package excelize

import (
"bytes"
"compress/gzip"
"encoding/json"
"encoding/xml"
"image"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
Expand Down Expand Up @@ -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
}
20 changes: 18 additions & 2 deletions sheet.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
20 changes: 20 additions & 0 deletions sheet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package excelize
import (
"encoding/xml"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
Expand Down Expand Up @@ -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())
}
Binary file added test/EmptyWorkbook.xlsx
Binary file not shown.
13 changes: 13 additions & 0 deletions xmlDrawing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 422fd12

Please sign in to comment.