Support add picture with offset and scaling.
10
README.md
|
@ -114,13 +114,21 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
|
||||
"github.com/Luxurioust/excelize"
|
||||
)
|
||||
|
||||
func main() {
|
||||
xlsx := excelize.CreateFile()
|
||||
err := xlsx.AddPicture("Sheet1", "A2", "H9", "/tmp/image.jpg")
|
||||
// Insert a picture.
|
||||
err := xlsx.AddPicture("Sheet1", "A2", "/tmp/image1.jpg", 0, 0, 1, 1)
|
||||
// Insert a picture to sheet with scaling.
|
||||
err = xlsx.AddPicture("Sheet1", "D2", "/tmp/image1.png", 0, 0, 0.5, 0.5)
|
||||
// Insert a picture offset in the cell.
|
||||
err = xlsx.AddPicture("Sheet1", "H2", "/tmp/image3.gif", 15, 10, 1, 1)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
|
183
col.go
|
@ -2,9 +2,18 @@ package excelize
|
|||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Define the default cell size and EMU unit of measurement.
|
||||
const (
|
||||
defaultColWidthPixels int = 64
|
||||
defaultRowHeightPixels int = 20
|
||||
EMU int = 9525
|
||||
)
|
||||
|
||||
// SetColWidth provides function to set the width of a single column or multiple
|
||||
// columns. For example:
|
||||
//
|
||||
|
@ -41,3 +50,177 @@ func (f *File) SetColWidth(sheet, startcol, endcol string, width float64) {
|
|||
output, _ := xml.Marshal(xlsx)
|
||||
f.saveFileList(name, replaceWorkSheetsRelationshipsNameSpace(string(output)))
|
||||
}
|
||||
|
||||
// positionObjectPixels calculate the vertices that define the position of a
|
||||
// graphical object within the worksheet in pixels.
|
||||
//
|
||||
// +------------+------------+
|
||||
// | A | B |
|
||||
// +-----+------------+------------+
|
||||
// | |(x1,y1) | |
|
||||
// | 1 |(A1)._______|______ |
|
||||
// | | | | |
|
||||
// | | | | |
|
||||
// +-----+----| OBJECT |-----+
|
||||
// | | | | |
|
||||
// | 2 | |______________. |
|
||||
// | | | (B2)|
|
||||
// | | | (x2,y2)|
|
||||
// +-----+------------+------------+
|
||||
//
|
||||
// Example of an object that covers some of the area from cell A1 to B2.
|
||||
//
|
||||
// Based on the width and height of the object we need to calculate 8 vars:
|
||||
//
|
||||
// colStart, rowStart, colEnd, rowEnd, x1, y1, x2, y2.
|
||||
//
|
||||
// We also calculate the absolute x and y position of the top left vertex of
|
||||
// the object. This is required for images.
|
||||
//
|
||||
// The width and height of the cells that the object occupies can be
|
||||
// variable and have to be taken into account.
|
||||
//
|
||||
// The values of col_start and row_start are passed in from the calling
|
||||
// function. The values of col_end and row_end are calculated by
|
||||
// subtracting the width and height of the object from the width and
|
||||
// height of the underlying cells.
|
||||
//
|
||||
// colStart # Col containing upper left corner of object.
|
||||
// x1 # Distance to left side of object.
|
||||
//
|
||||
// rowStart # Row containing top left corner of object.
|
||||
// y1 # Distance to top of object.
|
||||
//
|
||||
// colEnd # Col containing lower right corner of object.
|
||||
// x2 # Distance to right side of object.
|
||||
//
|
||||
// rowEnd # Row containing bottom right corner of object.
|
||||
// y2 # Distance to bottom of object.
|
||||
//
|
||||
// width # Width of object frame.
|
||||
// height # Height of object frame.
|
||||
//
|
||||
// xAbs # Absolute distance to left side of object.
|
||||
// yAbs # Absolute distance to top side of object.
|
||||
//
|
||||
func (f *File) positionObjectPixels(sheet string, colStart, rowStart, x1, y1, width, height int) (int, int, int, int, int, int, int, int) {
|
||||
xAbs := 0
|
||||
yAbs := 0
|
||||
|
||||
// Calculate the absolute x offset of the top-left vertex.
|
||||
for colID := 1; colID <= colStart; colID++ {
|
||||
xAbs += f.getColWidth(sheet, colID)
|
||||
}
|
||||
xAbs += x1
|
||||
|
||||
// Calculate the absolute y offset of the top-left vertex.
|
||||
// Store the column change to allow optimisations.
|
||||
for rowID := 1; rowID <= rowStart; rowID++ {
|
||||
yAbs += f.getRowHeight(sheet, rowID)
|
||||
}
|
||||
yAbs += y1
|
||||
|
||||
// Adjust start column for offsets that are greater than the col width.
|
||||
for x1 >= f.getColWidth(sheet, colStart) {
|
||||
x1 -= f.getColWidth(sheet, colStart)
|
||||
colStart++
|
||||
}
|
||||
|
||||
// Adjust start row for offsets that are greater than the row height.
|
||||
for y1 >= f.getRowHeight(sheet, rowStart) {
|
||||
y1 -= f.getRowHeight(sheet, rowStart)
|
||||
rowStart++
|
||||
}
|
||||
|
||||
// Initialise end cell to the same as the start cell.
|
||||
colEnd := colStart
|
||||
rowEnd := rowStart
|
||||
|
||||
width += x1
|
||||
height += y1
|
||||
|
||||
// Subtract the underlying cell widths to find end cell of the object.
|
||||
for width >= f.getColWidth(sheet, colEnd) {
|
||||
colEnd++
|
||||
width -= f.getColWidth(sheet, colEnd)
|
||||
}
|
||||
|
||||
// Subtract the underlying cell heights to find end cell of the object.
|
||||
for height >= f.getRowHeight(sheet, rowEnd) {
|
||||
rowEnd++
|
||||
height -= f.getRowHeight(sheet, rowEnd)
|
||||
}
|
||||
|
||||
// The end vertices are whatever is left from the width and height.
|
||||
x2 := width
|
||||
y2 := height
|
||||
return colStart, rowStart, xAbs, yAbs, colEnd, rowEnd, x2, y2
|
||||
}
|
||||
|
||||
// getColWidth provides function to get column width in pixels by given sheet
|
||||
// name and column index.
|
||||
func (f *File) getColWidth(sheet string, col int) int {
|
||||
var xlsx xlsxWorksheet
|
||||
name := "xl/worksheets/" + strings.ToLower(sheet) + ".xml"
|
||||
xml.Unmarshal([]byte(f.readXML(name)), &xlsx)
|
||||
if xlsx.Cols != nil {
|
||||
var width float64
|
||||
for _, v := range xlsx.Cols.Col {
|
||||
if v.Min <= col && col <= v.Max {
|
||||
width = v.Width
|
||||
}
|
||||
}
|
||||
if width != 0 {
|
||||
return int(convertColWidthToPixels(width))
|
||||
}
|
||||
}
|
||||
// Optimisation for when the column widths haven't changed.
|
||||
return defaultColWidthPixels
|
||||
}
|
||||
|
||||
// getRowHeight provides function to get row height in pixels by given sheet
|
||||
// name and row index.
|
||||
func (f *File) getRowHeight(sheet string, row int) int {
|
||||
var xlsx xlsxWorksheet
|
||||
name := "xl/worksheets/" + strings.ToLower(sheet) + ".xml"
|
||||
xml.Unmarshal([]byte(f.readXML(name)), &xlsx)
|
||||
for _, v := range xlsx.SheetData.Row {
|
||||
if v.R == row && v.Ht != "" {
|
||||
ht, _ := strconv.ParseFloat(v.Ht, 64)
|
||||
return int(convertRowHeightToPixels(ht))
|
||||
}
|
||||
}
|
||||
// Optimisation for when the row heights haven't changed.
|
||||
return defaultRowHeightPixels
|
||||
}
|
||||
|
||||
// convertColWidthToPixels provieds function to convert the width of a cell from
|
||||
// user's units to pixels. Excel rounds the column width to the nearest pixel.
|
||||
// If the width hasn't been set by the user we use the default value. If the
|
||||
// column is hidden it has a value of zero.
|
||||
func convertColWidthToPixels(width float64) float64 {
|
||||
var padding float64 = 5
|
||||
var pixels float64
|
||||
var maxDigitWidth float64 = 7
|
||||
if width == 0 {
|
||||
return pixels
|
||||
}
|
||||
if width < 1 {
|
||||
pixels = (width * 12) + 0.5
|
||||
return math.Ceil(pixels)
|
||||
}
|
||||
pixels = (width*maxDigitWidth + 0.5) + padding
|
||||
return math.Ceil(pixels)
|
||||
}
|
||||
|
||||
// convertRowHeightToPixels provides function to convert the height of a cell
|
||||
// from user's units to pixels. If the height hasn't been set by the user we use
|
||||
// the default value. If the row is hidden it has a value of zero.
|
||||
func convertRowHeightToPixels(height float64) float64 {
|
||||
var pixels float64
|
||||
if height == 0 {
|
||||
return pixels
|
||||
}
|
||||
pixels = math.Ceil(4.0 / 3.0 * height)
|
||||
return pixels
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package excelize
|
||||
|
||||
import (
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
@ -92,29 +95,22 @@ func TestAddPicture(t *testing.T) {
|
|||
t.Log(err)
|
||||
}
|
||||
// Test add picture to sheet.
|
||||
err = xlsx.AddPicture("Sheet2", "I1", "L10", "./test/images/excel.jpg")
|
||||
err = xlsx.AddPicture("Sheet2", "I9", "./test/images/excel.jpg", 140, 120, 1, 1)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
err = xlsx.AddPicture("Sheet1", "F21", "G25", "./test/images/excel.png")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
err = xlsx.AddPicture("Sheet2", "L1", "O10", "./test/images/excel.bmp")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
err = xlsx.AddPicture("Sheet1", "G21", "H25", "./test/images/excel.ico")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
// Test add picture to sheet with unsupport file type.
|
||||
err = xlsx.AddPicture("Sheet1", "G21", "H25", "./test/images/excel.icon")
|
||||
// Test add picture to sheet with offset.
|
||||
err = xlsx.AddPicture("Sheet1", "F21", "./test/images/excel.png", 10, 10, 1, 1)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
// Test add picture to sheet with invalid file path.
|
||||
err = xlsx.AddPicture("Sheet1", "G21", "H25", "./test/Workbook1.xlsx")
|
||||
err = xlsx.AddPicture("Sheet1", "G21", "./test/images/excel.icon", 0, 0, 1, 1)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
// Test add picture to sheet with unsupport file type.
|
||||
err = xlsx.AddPicture("Sheet1", "G21", "./test/Workbook1.xlsx", 0, 0, 1, 1)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
|
@ -160,12 +156,12 @@ func TestCreateFile(t *testing.T) {
|
|||
xlsx.SetCellInt("Sheet2", "A23", 56)
|
||||
xlsx.SetCellStr("SHEET1", "B20", "42")
|
||||
xlsx.SetActiveSheet(0)
|
||||
// Test add picture to sheet.
|
||||
err := xlsx.AddPicture("Sheet1", "H2", "K12", "./test/images/excel.gif")
|
||||
// Test add picture to sheet with scaling.
|
||||
err := xlsx.AddPicture("Sheet1", "H2", "./test/images/excel.gif", 0, 0, 0.5, 0.5)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
err = xlsx.AddPicture("Sheet1", "C2", "F12", "./test/images/excel.tif")
|
||||
err = xlsx.AddPicture("Sheet1", "C2", "./test/images/excel.png", 0, 0, 1, 1)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
|
@ -176,13 +172,10 @@ func TestCreateFile(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSetColWidth(t *testing.T) {
|
||||
xlsx, err := OpenFile("./test/Workbook1.xlsx")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
xlsx := CreateFile()
|
||||
xlsx.SetColWidth("sheet1", "B", "A", 12)
|
||||
xlsx.SetColWidth("sheet1", "A", "B", 12)
|
||||
err = xlsx.Save()
|
||||
err := xlsx.WriteTo("./test/Workbook_4.xlsx")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
|
|
71
picture.go
|
@ -5,6 +5,7 @@ import (
|
|||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
|
@ -13,18 +14,42 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// AddPicture provide the method to add picture in a sheet by given xAxis, yAxis
|
||||
// AddPicture provides the method to add picture in a sheet by given xAxis, yAxis
|
||||
// and file path. For example:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "fmt"
|
||||
// "os"
|
||||
// _ "image/gif"
|
||||
// _ "image/jpeg"
|
||||
// _ "image/png"
|
||||
//
|
||||
// "github.com/Luxurioust/excelize"
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// xlsx := excelize.CreateFile()
|
||||
// err := xlsx.AddPicture("Sheet1", "A2", "H9", "./image.jpg")
|
||||
// // Insert a picture.
|
||||
// err := xlsx.AddPicture("Sheet1", "A2", "/tmp/image1.jpg", 0, 0, 1, 1)
|
||||
// // Insert a picture to sheet with scaling.
|
||||
// err = xlsx.AddPicture("Sheet1", "D2", "/tmp/image1.png", 0, 0, 0.5, 0.5)
|
||||
// // Insert a picture offset in the cell.
|
||||
// err = xlsx.AddPicture("Sheet1", "H2", "/tmp/image3.gif", 15, 10, 1, 1)
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// os.Exit(1)
|
||||
// }
|
||||
// err = xlsx.WriteTo("/tmp/Workbook.xlsx")
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// os.Exit(1)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
func (f *File) AddPicture(sheet string, xAxis string, yAxis string, picture string) error {
|
||||
var supportTypes = map[string]string{".bmp": ".jpeg", ".gif": ".gif", ".ico": ".png", ".tif": ".tiff", ".tiff": ".tiff", ".jpg": ".jpeg", ".jpeg": ".jpeg", ".png": ".png"}
|
||||
func (f *File) AddPicture(sheet, cell, picture string, offsetX, offsetY int, xScale, yScale float64) error {
|
||||
var supportTypes = map[string]string{".gif": ".gif", ".jpg": ".jpeg", ".jpeg": ".jpeg", ".png": ".png"}
|
||||
var err error
|
||||
// Check picture exists first.
|
||||
if _, err = os.Stat(picture); os.IsNotExist(err) {
|
||||
|
@ -34,6 +59,8 @@ func (f *File) AddPicture(sheet string, xAxis string, yAxis string, picture stri
|
|||
if !ok {
|
||||
return errors.New("Unsupported image extension")
|
||||
}
|
||||
readFile, _ := os.Open(picture)
|
||||
image, _, err := image.DecodeConfig(readFile)
|
||||
_, file := filepath.Split(picture)
|
||||
// Read sheet data.
|
||||
var xlsx xlsxWorksheet
|
||||
|
@ -57,7 +84,7 @@ func (f *File) AddPicture(sheet string, xAxis string, yAxis string, picture stri
|
|||
f.addSheetDrawing(sheet, rID)
|
||||
}
|
||||
drawingRID = f.addDrawingRelationships(drawingID, SourceRelationshipImage, "../media/image"+strconv.Itoa(pictureID)+ext)
|
||||
f.addDrawing(drawingXML, xAxis, yAxis, file, drawingRID)
|
||||
f.addDrawing(sheet, drawingXML, cell, file, offsetX, offsetY, image.Width, image.Height, drawingRID, xScale, yScale)
|
||||
f.addMedia(picture, ext)
|
||||
f.addDrawingContentTypePart(drawingID)
|
||||
return err
|
||||
|
@ -127,17 +154,15 @@ func (f *File) countDrawings() int {
|
|||
// yAxis, file name and relationship index. In order to solve the problem that
|
||||
// the label structure is changed after serialization and deserialization, two
|
||||
// different structures: decodeWsDr and encodeWsDr are defined.
|
||||
func (f *File) addDrawing(drawingXML string, xAxis string, yAxis string, file string, rID int) {
|
||||
xAxis = strings.ToUpper(xAxis)
|
||||
fromCol := string(strings.Map(letterOnlyMapF, xAxis))
|
||||
fromRow, _ := strconv.Atoi(strings.Map(intOnlyMapF, xAxis))
|
||||
fromXAxis := fromRow - 1
|
||||
fromYAxis := titleToNumber(fromCol)
|
||||
yAxis = strings.ToUpper(yAxis)
|
||||
ToCol := string(strings.Map(letterOnlyMapF, yAxis))
|
||||
ToRow, _ := strconv.Atoi(strings.Map(intOnlyMapF, yAxis))
|
||||
ToXAxis := ToRow - 1
|
||||
ToYAxis := titleToNumber(ToCol)
|
||||
func (f *File) addDrawing(sheet, drawingXML, cell, file string, offsetX, offsetY, width, height, rID int, xScale, yScale float64) {
|
||||
cell = strings.ToUpper(cell)
|
||||
fromCol := string(strings.Map(letterOnlyMapF, cell))
|
||||
fromRow, _ := strconv.Atoi(strings.Map(intOnlyMapF, cell))
|
||||
row := fromRow - 1
|
||||
col := titleToNumber(fromCol)
|
||||
width = int(float64(width) * xScale)
|
||||
height = int(float64(height) * yScale)
|
||||
colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, offsetX, offsetY, width, height)
|
||||
content := encodeWsDr{}
|
||||
content.WsDr.A = NameSpaceDrawingML
|
||||
content.WsDr.Xdr = NameSpaceSpreadSheetDrawing
|
||||
|
@ -157,11 +182,15 @@ func (f *File) addDrawing(drawingXML string, xAxis string, yAxis string, file st
|
|||
twoCellAnchor := xlsxTwoCellAnchor{}
|
||||
twoCellAnchor.EditAs = "oneCell"
|
||||
from := xlsxFrom{}
|
||||
from.Col = fromYAxis
|
||||
from.Row = fromXAxis
|
||||
from.Col = colStart
|
||||
from.ColOff = offsetX * EMU
|
||||
from.Row = rowStart
|
||||
from.RowOff = offsetY * EMU
|
||||
to := xlsxTo{}
|
||||
to.Col = ToYAxis
|
||||
to.Row = ToXAxis
|
||||
to.Col = colEnd
|
||||
to.ColOff = x2 * EMU
|
||||
to.Row = rowEnd
|
||||
to.RowOff = y2 * EMU
|
||||
twoCellAnchor.From = &from
|
||||
twoCellAnchor.To = &to
|
||||
pic := xlsxPic{}
|
||||
|
@ -245,7 +274,7 @@ func (f *File) addMedia(file string, ext string) {
|
|||
// in http://purl.oclc.org/ooxml/officeDocument/relationships/image and
|
||||
// appropriate content type.
|
||||
func (f *File) addDrawingContentTypePart(index int) {
|
||||
var imageTypes = map[string]bool{"jpeg": false, "png": false, "gif": false, "tiff": false}
|
||||
var imageTypes = map[string]bool{"jpeg": false, "png": false, "gif": false}
|
||||
var content xlsxTypes
|
||||
xml.Unmarshal([]byte(f.readXML(`[Content_Types].xml`)), &content)
|
||||
for _, v := range content.Defaults {
|
||||
|
|
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.8 KiB |
|
@ -115,10 +115,11 @@ type xlsxBlipFill struct {
|
|||
Stretch xlsxStretch `xml:"a:stretch"`
|
||||
}
|
||||
|
||||
// xlsxSpPr directly maps the spPr (Shape Properties). This element specifies the visual
|
||||
// shape properties that can be applied to a picture. These are the same properties that
|
||||
// are allowed to describe the visual properties of a shape but are used here to describe
|
||||
// the visual appearance of a picture within a document.
|
||||
// xlsxSpPr directly maps the spPr (Shape Properties). This element specifies
|
||||
// the visual shape properties that can be applied to a picture. These are the
|
||||
// same properties that are allowed to describe the visual properties of a shape
|
||||
// but are used here to describe the visual appearance of a picture within a
|
||||
// document.
|
||||
type xlsxSpPr struct {
|
||||
Xfrm xlsxXfrm `xml:"a:xfrm"`
|
||||
PrstGeom xlsxPrstGeom `xml:"a:prstGeom"`
|
||||
|
|