瀏覽代碼

Support add picture with offset and scaling.

Ri Xu 8 年之前
父節點
當前提交
03234d6a25
共有 12 個文件被更改,包括 265 次插入51 次删除
  1. 9 1
      README.md
  2. 183 0
      col.go
  3. 14 21
      excelize_test.go
  4. 54 25
      picture.go
  5. 二進制
      test/Workbook1.xlsx
  6. 二進制
      test/images/excel.bmp
  7. 二進制
      test/images/excel.gif
  8. 二進制
      test/images/excel.ico
  9. 二進制
      test/images/excel.jpg
  10. 二進制
      test/images/excel.png
  11. 二進制
      test/images/excel.tif
  12. 5 4
      xmlDrawing.go

+ 9 - 1
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 - 0
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
+}

+ 14 - 21
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)
 	}

+ 54 - 25
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 {

二進制
test/Workbook1.xlsx


二進制
test/images/excel.bmp


二進制
test/images/excel.gif


二進制
test/images/excel.ico


二進制
test/images/excel.jpg


二進制
test/images/excel.png


二進制
test/images/excel.tif


+ 5 - 4
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"`