summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRi Xu <xuri.me@gmail.com>2017-01-22 16:16:03 +0800
committerRi Xu <xuri.me@gmail.com>2017-01-22 16:16:03 +0800
commit03234d6a254ea9fca674bf11564e88a5bd4d054f (patch)
tree159334fb000c95b0f37260f9241ab45c0ce6142a
parent4a9b39afc634a2399c4729f4fb47c9f290ab1ee5 (diff)
Support add picture with offset and scaling.
-rw-r--r--README.md10
-rw-r--r--col.go183
-rw-r--r--excelize_test.go35
-rw-r--r--picture.go79
-rw-r--r--test/Workbook1.xlsxbin22867 -> 22958 bytes
-rw-r--r--test/images/excel.bmpbin32824 -> 0 bytes
-rw-r--r--test/images/excel.gifbin4669 -> 4952 bytes
-rw-r--r--test/images/excel.icobin67646 -> 0 bytes
-rw-r--r--test/images/excel.jpgbin3883 -> 3960 bytes
-rw-r--r--test/images/excel.pngbin8792 -> 8991 bytes
-rw-r--r--test/images/excel.tifbin31408 -> 0 bytes
-rw-r--r--xmlDrawing.go9
12 files changed, 265 insertions, 51 deletions
diff --git a/README.md b/README.md
index f9ead95..0346fe8 100644
--- a/README.md
+++ b/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)
diff --git a/col.go b/col.go
index 7b583c5..60adab4 100644
--- a/col.go
+++ b/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
+}
diff --git a/excelize_test.go b/excelize_test.go
index 278912b..8da4a99 100644
--- a/excelize_test.go
+++ b/excelize_test.go
@@ -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")
+ // 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)
}
- 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")
+ // Test add picture to sheet with invalid file path.
+ 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", "H25", "./test/images/excel.icon")
- 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/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)
}
diff --git a/picture.go b/picture.go
index bbd7cf3..e29ffd3 100644
--- a/picture.go
+++ b/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:
//
-// xlsx := excelize.CreateFile()
-// err := xlsx.AddPicture("Sheet1", "A2", "H9", "./image.jpg")
-// if err != nil {
-// fmt.Println(err)
-// os.Exit(1)
+// package main
+//
+// import (
+// "fmt"
+// "os"
+// _ "image/gif"
+// _ "image/jpeg"
+// _ "image/png"
+//
+// "github.com/Luxurioust/excelize"
+// )
+//
+// func main() {
+// xlsx := excelize.CreateFile()
+// // 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 {
diff --git a/test/Workbook1.xlsx b/test/Workbook1.xlsx
index f6f713c..d287361 100644
--- a/test/Workbook1.xlsx
+++ b/test/Workbook1.xlsx
Binary files differ
diff --git a/test/images/excel.bmp b/test/images/excel.bmp
deleted file mode 100644
index 424619c..0000000
--- a/test/images/excel.bmp
+++ /dev/null
Binary files differ
diff --git a/test/images/excel.gif b/test/images/excel.gif
index 1a34a18..bf7499a 100644
--- a/test/images/excel.gif
+++ b/test/images/excel.gif
Binary files differ
diff --git a/test/images/excel.ico b/test/images/excel.ico
deleted file mode 100644
index 9bb8d6c..0000000
--- a/test/images/excel.ico
+++ /dev/null
Binary files differ
diff --git a/test/images/excel.jpg b/test/images/excel.jpg
index 67c84f3..49edfef 100644
--- a/test/images/excel.jpg
+++ b/test/images/excel.jpg
Binary files differ
diff --git a/test/images/excel.png b/test/images/excel.png
index cbf3646..52c36db 100644
--- a/test/images/excel.png
+++ b/test/images/excel.png
Binary files differ
diff --git a/test/images/excel.tif b/test/images/excel.tif
deleted file mode 100644
index 4eba87d..0000000
--- a/test/images/excel.tif
+++ /dev/null
Binary files differ
diff --git a/xmlDrawing.go b/xmlDrawing.go
index efcfa81..1b80975 100644
--- a/xmlDrawing.go
+++ b/xmlDrawing.go
@@ -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"`