Support add picture with offset and scaling.

This commit is contained in:
Ri Xu 2017-01-22 16:16:03 +08:00
parent 4a9b39afc6
commit 03234d6a25
12 changed files with 268 additions and 54 deletions

View File

@ -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
View File

@ -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
}

View File

@ -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)
}

View File

@ -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 {

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

View File

@ -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"`