diff --git a/adjust.go b/adjust.go index 28e54e5809..4e2d2815a9 100644 --- a/adjust.go +++ b/adjust.go @@ -38,10 +38,10 @@ var adjustHelperFunc = [9]func(*File, *xlsxWorksheet, string, adjustDirection, i return f.adjustDataValidations(ws, sheet, dir, num, offset, sheetID) }, func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { - return f.adjustDefinedNames(ws, sheet, dir, num, offset, sheetID) + return f.adjustDefinedNames(sheet, dir, num, offset) }, func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { - return f.adjustDrawings(ws, sheet, dir, num, offset, sheetID) + return f.adjustDrawings(ws, sheet, dir, num, offset) }, func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { return f.adjustMergeCells(ws, sheet, dir, num, offset, sheetID) @@ -1034,7 +1034,7 @@ func (from *xlsxFrom) adjustDrawings(dir adjustDirection, num, offset int, editA // adjustDrawings updates the ending anchor of the two cell anchor pictures // and charts object when inserting or deleting rows or columns. -func (to *xlsxTo) adjustDrawings(dir adjustDirection, num, offset int, editAs string, ok bool) error { +func (to *xlsxTo) adjustDrawings(dir adjustDirection, num, offset int, ok bool) error { if dir == columns && to.Col+1 >= num && to.Col+offset >= 0 && ok { if to.Col+offset >= MaxColumns { return ErrColumnNumber @@ -1054,32 +1054,38 @@ func (to *xlsxTo) adjustDrawings(dir adjustDirection, num, offset int, editAs st // inserting or deleting rows or columns. func (a *xdrCellAnchor) adjustDrawings(dir adjustDirection, num, offset int) error { editAs := a.EditAs - if a.From == nil || a.To == nil || editAs == "absolute" { + if (a.From == nil && (a.To == nil || a.Ext == nil)) || editAs == "absolute" { return nil } ok, err := a.From.adjustDrawings(dir, num, offset, editAs) if err != nil { return err } - return a.To.adjustDrawings(dir, num, offset, editAs, ok || editAs == "") + if a.To != nil { + return a.To.adjustDrawings(dir, num, offset, ok || editAs == "") + } + return err } // adjustDrawings updates the existing two cell anchor pictures and charts // object when inserting or deleting rows or columns. func (a *xlsxCellAnchorPos) adjustDrawings(dir adjustDirection, num, offset int, editAs string) error { - if a.From == nil || a.To == nil || editAs == "absolute" { + if (a.From == nil && (a.To == nil || a.Ext == nil)) || editAs == "absolute" { return nil } ok, err := a.From.adjustDrawings(dir, num, offset, editAs) if err != nil { return err } - return a.To.adjustDrawings(dir, num, offset, editAs, ok || editAs == "") + if a.To != nil { + return a.To.adjustDrawings(dir, num, offset, ok || editAs == "") + } + return err } // adjustDrawings updates the pictures and charts object when inserting or // deleting rows or columns. -func (f *File) adjustDrawings(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { +func (f *File) adjustDrawings(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset int) error { if ws.Drawing == nil { return nil } @@ -1128,12 +1134,17 @@ func (f *File) adjustDrawings(ws *xlsxWorksheet, sheet string, dir adjustDirecti return err } } + for _, anchor := range wsDr.OneCellAnchor { + if err = anchorCb(anchor); err != nil { + return err + } + } return nil } // adjustDefinedNames updates the cell reference of the defined names when // inserting or deleting rows or columns. -func (f *File) adjustDefinedNames(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { +func (f *File) adjustDefinedNames(sheet string, dir adjustDirection, num, offset int) error { wb, err := f.workbookReader() if err != nil { return err diff --git a/adjust_test.go b/adjust_test.go index 0acc8bf2eb..07ccaa5e12 100644 --- a/adjust_test.go +++ b/adjust_test.go @@ -1206,7 +1206,7 @@ func TestAdjustDrawings(t *testing.T) { assert.NoError(t, f.InsertRows("Sheet1", 15, 1)) cells, err := f.GetPictureCells("Sheet1") assert.NoError(t, err) - assert.Equal(t, []string{"D3", "D13", "B21"}, cells) + assert.Equal(t, []string{"D3", "B21", "D13"}, cells) wb := filepath.Join("test", "TestAdjustDrawings.xlsx") assert.NoError(t, f.SaveAs(wb)) @@ -1215,7 +1215,7 @@ func TestAdjustDrawings(t *testing.T) { assert.NoError(t, f.RemoveRow("Sheet1", 1)) cells, err = f.GetPictureCells("Sheet1") assert.NoError(t, err) - assert.Equal(t, []string{"C2", "C12", "B21"}, cells) + assert.Equal(t, []string{"C2", "B21", "C12"}, cells) // Test adjust existing pictures on inserting columns and rows f, err = OpenFile(wb) @@ -1227,7 +1227,7 @@ func TestAdjustDrawings(t *testing.T) { assert.NoError(t, f.InsertRows("Sheet1", 16, 1)) cells, err = f.GetPictureCells("Sheet1") assert.NoError(t, err) - assert.Equal(t, []string{"F4", "F15", "B21"}, cells) + assert.Equal(t, []string{"F4", "B21", "F15"}, cells) // Test adjust drawings with unsupported charset f, err = OpenFile(wb) @@ -1267,6 +1267,11 @@ func TestAdjustDrawings(t *testing.T) { assert.NoError(t, err) f.Pkg.Store("xl/drawings/drawing1.xml", []byte(xml.Header+`00001010`)) assert.NoError(t, f.InsertCols("Sheet1", "A", 1)) + + f, err = OpenFile(wb) + assert.NoError(t, err) + f.Pkg.Store("xl/drawings/drawing1.xml", []byte(xml.Header+fmt.Sprintf(`%d0`, MaxColumns))) + assert.EqualError(t, f.InsertCols("Sheet1", "A", 1), "the column number must be greater than or equal to 1 and less than or equal to 16384") } func TestAdjustDefinedNames(t *testing.T) { @@ -1330,5 +1335,5 @@ func TestAdjustDefinedNames(t *testing.T) { f = NewFile() f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) - assert.EqualError(t, f.adjustDefinedNames(nil, "Sheet1", columns, 0, 0, 1), "XML syntax error on line 1: invalid UTF-8") + assert.EqualError(t, f.adjustDefinedNames("Sheet1", columns, 0, 0), "XML syntax error on line 1: invalid UTF-8") } diff --git a/chart_test.go b/chart_test.go index 42e2a65764..a0a9a505d9 100644 --- a/chart_test.go +++ b/chart_test.go @@ -93,8 +93,8 @@ func TestChartSize(t *testing.T) { t.FailNow() } - if !assert.Equal(t, 14, anchor.To.Col, "Expected 'to' column 14") || - !assert.Equal(t, 29, anchor.To.Row, "Expected 'to' row 29") { + if !assert.Equal(t, 11, anchor.To.Col, "Expected 'to' column 11") || + !assert.Equal(t, 27, anchor.To.Row, "Expected 'to' row 27") { t.FailNow() } diff --git a/col.go b/col.go index 6608048a83..3be7e33ca7 100644 --- a/col.go +++ b/col.go @@ -14,7 +14,6 @@ package excelize import ( "bytes" "encoding/xml" - "math" "strconv" "strings" @@ -23,10 +22,10 @@ import ( // Define the default cell size and EMU unit of measurement. const ( - defaultColWidth float64 = 9.140625 - defaultColWidthPixels float64 = 64 - defaultRowHeight float64 = 15 - defaultRowHeightPixels float64 = 20 + defaultColWidth float64 = 10.5 + defaultColWidthPixels float64 = 84.0 + defaultRowHeight float64 = 15.6 + defaultRowHeightPixels float64 = 20.8 EMU int = 9525 ) @@ -621,40 +620,35 @@ func flatCols(col xlsxCol, cols []xlsxCol, replacer func(fc, c xlsxCol) xlsxCol) // // width # Width of object frame. // height # Height of object frame. -func (f *File) positionObjectPixels(sheet string, col, row, x1, y1, width, height int) (int, int, int, int, int, int) { +func (f *File) positionObjectPixels(sheet string, col, row, width, height int, opts *GraphicOptions) (int, int, int, int, int, int, int, int) { colIdx, rowIdx := col-1, row-1 - // Adjust start column for offsets that are greater than the col width. - for x1 >= f.getColWidth(sheet, colIdx+1) { - colIdx++ - x1 -= f.getColWidth(sheet, colIdx) - } - - // Adjust start row for offsets that are greater than the row height. - for y1 >= f.getRowHeight(sheet, rowIdx+1) { - rowIdx++ - y1 -= f.getRowHeight(sheet, rowIdx) - } - // Initialized end cell to the same as the start cell. colEnd, rowEnd := colIdx, rowIdx + x1, y1, x2, y2 := opts.OffsetX, opts.OffsetY, width, height + if opts.Positioning == "" || opts.Positioning == "twoCell" { + // Using a twoCellAnchor, the maximum possible offset is limited by the + // "from" cell dimensions. If these were to be exceeded the "toPoint" would + // be calculated incorrectly, since the requested "fromPoint" is not possible + + x1 = min(x1, f.getColWidth(sheet, col)) + y1 = min(y1, f.getRowHeight(sheet, row)) + + x2 += x1 + y2 += y1 + // Subtract the underlying cell widths to find end cell of the object. + for x2 >= f.getColWidth(sheet, colEnd+1) { + colEnd++ + x2 -= f.getColWidth(sheet, colEnd) + } - width += x1 - height += y1 - - // Subtract the underlying cell widths to find end cell of the object. - for width >= f.getColWidth(sheet, colEnd+1) { - colEnd++ - width -= f.getColWidth(sheet, colEnd) - } - - // Subtract the underlying cell heights to find end cell of the object. - for height >= f.getRowHeight(sheet, rowEnd+1) { - rowEnd++ - height -= f.getRowHeight(sheet, rowEnd) + // Subtract the underlying cell heights to find end cell of the object. + for y2 >= f.getRowHeight(sheet, rowEnd+1) { + rowEnd++ + y2 -= f.getRowHeight(sheet, rowEnd) + } } - // The end vertices are whatever is left from the width and height. - return colIdx, rowIdx, colEnd, rowEnd, width, height + return colIdx, rowIdx, colEnd, rowEnd, x1, y1, x2, y2 } // getColWidth provides a function to get column width in pixels by given @@ -664,13 +658,14 @@ func (f *File) getColWidth(sheet string, col int) int { ws.mu.Lock() defer ws.mu.Unlock() if ws.Cols != nil { - var width float64 + width := -1.0 for _, v := range ws.Cols.Col { if v.Min <= col && col <= v.Max && v.Width != nil { width = *v.Width + break } } - if width != 0 { + if width != -1.0 { return int(convertColWidthToPixels(width)) } } @@ -800,16 +795,15 @@ func (f *File) RemoveCol(sheet, col string) error { // 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 + var maxDigitWidth float64 = 8 if width == 0 { return pixels } if width < 1 { pixels = (width * 12) + 0.5 - return math.Ceil(pixels) + return float64(int(pixels)) } - pixels = (width*maxDigitWidth + 0.5) + padding - return math.Ceil(pixels) + pixels = (width*maxDigitWidth + 0.5) + return float64(int(pixels)) } diff --git a/col_test.go b/col_test.go index 2e7aeb80c7..0019587434 100644 --- a/col_test.go +++ b/col_test.go @@ -396,7 +396,7 @@ func TestColWidth(t *testing.T) { width, err = f.GetColWidth("Sheet1", "A") assert.NoError(t, err) assert.Equal(t, 10.0, width) - assert.Equal(t, 76, f.getColWidth("Sheet1", 1)) + assert.Equal(t, 80, f.getColWidth("Sheet1", 1)) // Test set and get column width with illegal cell reference width, err = f.GetColWidth("Sheet1", "*") diff --git a/drawing.go b/drawing.go index c029fdf7d3..b156fedc32 100644 --- a/drawing.go +++ b/drawing.go @@ -1392,7 +1392,7 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI } width = int(float64(width) * opts.ScaleX) height = int(float64(height) * opts.ScaleY) - colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, opts.OffsetX, opts.OffsetY, width, height) + colStart, rowStart, colEnd, rowEnd, x1, y1, x2, y2 := f.positionObjectPixels(sheet, col, row, width, height, opts) content, cNvPrID, err := f.drawingParser(drawingXML) if err != nil { return err @@ -1401,9 +1401,9 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI twoCellAnchor.EditAs = opts.Positioning from := xlsxFrom{} from.Col = colStart - from.ColOff = opts.OffsetX * EMU + from.ColOff = x1 * EMU from.Row = rowStart - from.RowOff = opts.OffsetY * EMU + from.RowOff = y1 * EMU to := xlsxTo{} to.Col = colEnd to.ColOff = x2 * EMU diff --git a/picture.go b/picture.go index de0d555870..3be2e23dd3 100644 --- a/picture.go +++ b/picture.go @@ -366,25 +366,29 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, ext string, rID, hyper width = int(float64(width) * opts.ScaleX) height = int(float64(height) * opts.ScaleY) } - colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, opts.OffsetX, opts.OffsetY, width, height) + colStart, rowStart, colEnd, rowEnd, x1, y1, x2, y2 := f.positionObjectPixels(sheet, col, row, width, height, opts) content, cNvPrID, err := f.drawingParser(drawingXML) if err != nil { return err } - twoCellAnchor := xdrCellAnchor{} - twoCellAnchor.EditAs = opts.Positioning + cellAnchor := xdrCellAnchor{} + cellAnchor.EditAs = opts.Positioning from := xlsxFrom{} from.Col = colStart - from.ColOff = opts.OffsetX * EMU + from.ColOff = x1 * EMU from.Row = rowStart - from.RowOff = opts.OffsetY * EMU - to := xlsxTo{} - to.Col = colEnd - to.ColOff = x2 * EMU - to.Row = rowEnd - to.RowOff = y2 * EMU - twoCellAnchor.From = &from - twoCellAnchor.To = &to + from.RowOff = y1 * EMU + cellAnchor.From = &from + + if opts.Positioning != "oneCell" { + to := xlsxTo{} + to.Col = colEnd + to.ColOff = x2 * EMU + to.Row = rowEnd + to.RowOff = y2 * EMU + cellAnchor.To = &to + } + pic := xlsxPic{} pic.NvPicPr.CNvPicPr.PicLocks.NoChangeAspect = opts.LockAspectRatio pic.NvPicPr.CNvPr.ID = cNvPrID @@ -413,14 +417,29 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, ext string, rID, hyper } pic.SpPr.PrstGeom.Prst = "rect" - twoCellAnchor.Pic = &pic - twoCellAnchor.ClientData = &xdrClientData{ + if opts.Positioning == "oneCell" { + cx := x2 * EMU + cy := y2 * EMU + cellAnchor.Ext = &aExt{ + Cx: cx, + Cy: cy, + } + pic.SpPr.Xfrm.Ext.Cx = cx + pic.SpPr.Xfrm.Ext.Cy = cy + } + + cellAnchor.Pic = &pic + cellAnchor.ClientData = &xdrClientData{ FLocksWithSheet: *opts.Locked, FPrintsWithSheet: *opts.PrintObject, } content.mu.Lock() defer content.mu.Unlock() - content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor) + if opts.Positioning == "oneCell" { + content.OneCellAnchor = append(content.OneCellAnchor, &cellAnchor) + } else { + content.TwoCellAnchor = append(content.TwoCellAnchor, &cellAnchor) + } f.Drawings.Store(drawingXML, content) return err } @@ -753,18 +772,15 @@ func (f *File) drawingResize(sheet, cell string, width, height float64, opts *Gr cellHeight += f.getRowHeight(sheet, row) } } - if float64(cellWidth) < width { - asp := float64(cellWidth) / width - width, height = float64(cellWidth), height*asp - } - if float64(cellHeight) < height { - asp := float64(cellHeight) / height - height, width = float64(cellHeight), width*asp + if float64(cellWidth) < width || float64(cellHeight) < height { + aspWidth := float64(cellWidth) / width + aspHeight := float64(cellHeight) / height + asp := min(aspWidth, aspHeight) + width, height = width*asp, height*asp } if opts.AutoFitIgnoreAspect { width, height = float64(cellWidth), float64(cellHeight) } - width, height = width-float64(opts.OffsetX), height-float64(opts.OffsetY) w, h = int(width*opts.ScaleX), int(height*opts.ScaleY) return } diff --git a/picture_test.go b/picture_test.go index c0c9075583..38cd5df2dc 100644 --- a/picture_test.go +++ b/picture_test.go @@ -42,6 +42,16 @@ func TestAddPicture(t *testing.T) { assert.NoError(t, f.AddPicture("Sheet1", "F21", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{OffsetX: 10, OffsetY: 10, Hyperlink: "https://github.com/xuri/excelize", HyperlinkType: "External", Positioning: "oneCell"})) + // Test add pictures to single cell with offsets + assert.NoError(t, f.AddPicture("Sheet2", "K22", filepath.Join("test", "images", "excel.jpg"), + &GraphicOptions{Positioning: "oneCell"})) + assert.NoError(t, f.AddPicture("Sheet2", "K22", filepath.Join("test", "images", "excel.jpg"), + &GraphicOptions{OffsetX: 200, Positioning: "oneCell"})) + assert.NoError(t, f.AddPicture("Sheet2", "K22", filepath.Join("test", "images", "excel.jpg"), + &GraphicOptions{OffsetX: 400, Positioning: "oneCell"})) + assert.NoError(t, f.AddPicture("Sheet2", "K22", filepath.Join("test", "images", "excel.jpg"), + &GraphicOptions{OffsetX: 600, Positioning: "oneCell"})) + file, err := os.ReadFile(filepath.Join("test", "images", "excel.png")) assert.NoError(t, err) @@ -83,7 +93,7 @@ func TestAddPicture(t *testing.T) { // Test get picture cells cells, err := f.GetPictureCells("Sheet1") assert.NoError(t, err) - assert.Equal(t, []string{"F21", "A30", "B30", "C30", "Q1", "Q8", "Q15", "Q22", "Q28"}, cells) + assert.Equal(t, []string{"A30", "B30", "C30", "Q1", "Q8", "Q15", "Q22", "Q28", "F21"}, cells) assert.NoError(t, f.Close()) f, err = OpenFile(filepath.Join("test", "TestAddPicture1.xlsx")) @@ -92,7 +102,7 @@ func TestAddPicture(t *testing.T) { f.Drawings.Delete(path) cells, err = f.GetPictureCells("Sheet1") assert.NoError(t, err) - assert.Equal(t, []string{"F21", "A30", "B30", "C30", "Q1", "Q8", "Q15", "Q22", "Q28"}, cells) + assert.Equal(t, []string{"A30", "B30", "C30", "Q1", "Q8", "Q15", "Q22", "Q28", "F21"}, cells) // Test get picture cells with unsupported charset f.Drawings.Delete(path) f.Pkg.Store(path, MacintoshCyrillicCharset) diff --git a/rows.go b/rows.go index 436a5d6abf..be255b17ea 100644 --- a/rows.go +++ b/rows.go @@ -393,17 +393,22 @@ func (f *File) getRowHeight(sheet string, row int) int { ws, _ := f.workSheetReader(sheet) ws.mu.Lock() defer ws.mu.Unlock() + height := -1.0 for i := range ws.SheetData.Row { v := &ws.SheetData.Row[i] if v.R == row && v.Ht != nil { - return int(convertRowHeightToPixels(*v.Ht)) + height = *v.Ht + break } } + if height != -1.0 { + return int(convertRowHeightToPixels(height)) + } if ws.SheetFormatPr != nil && ws.SheetFormatPr.DefaultRowHeight > 0 { return int(convertRowHeightToPixels(ws.SheetFormatPr.DefaultRowHeight)) } // Optimization for when the row heights haven't changed. - return int(defaultRowHeightPixels) + return int(math.Round(defaultRowHeightPixels)) } // GetRowHeight provides a function to get row height by given worksheet name @@ -947,5 +952,5 @@ func convertRowHeightToPixels(height float64) float64 { if height == 0 { return 0 } - return math.Ceil(4.0 / 3.4 * height) + return float64(int(height*4.0/3.0 + 0.5)) } diff --git a/rows_test.go b/rows_test.go index 01b20a0fcf..8848bdd78f 100644 --- a/rows_test.go +++ b/rows_test.go @@ -204,7 +204,7 @@ func TestRowHeight(t *testing.T) { assert.NoError(t, f.SetCellValue("Sheet2", "A2", true)) height, err = f.GetRowHeight("Sheet2", 1) assert.NoError(t, err) - assert.Equal(t, 15.0, height) + assert.Equal(t, 15.6, height) err = f.SaveAs(filepath.Join("test", "TestRowHeight.xlsx")) if !assert.NoError(t, err) { diff --git a/shape.go b/shape.go index 1bbf6964d6..b0228748a7 100644 --- a/shape.go +++ b/shape.go @@ -331,7 +331,7 @@ func (f *File) twoCellAnchorShape(sheet, drawingXML, cell string, width, height } w := int(float64(width) * format.ScaleX) h := int(float64(height) * format.ScaleY) - colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, fromCol, fromRow, format.OffsetX, format.OffsetY, w, h) + colStart, rowStart, colEnd, rowEnd, x1, y1, x2, y2 := f.positionObjectPixels(sheet, fromCol, fromRow, w, h, &format) content, cNvPrID, err := f.drawingParser(drawingXML) if err != nil { return content, nil, cNvPrID, err @@ -340,9 +340,9 @@ func (f *File) twoCellAnchorShape(sheet, drawingXML, cell string, width, height twoCellAnchor.EditAs = format.Positioning from := xlsxFrom{} from.Col = colStart - from.ColOff = format.OffsetX * EMU + from.ColOff = x1 * EMU from.Row = rowStart - from.RowOff = format.OffsetY * EMU + from.RowOff = y1 * EMU to := xlsxTo{} to.Col = colEnd to.ColOff = x2 * EMU diff --git a/vml.go b/vml.go index 7ea3e22a68..751f4eb525 100644 --- a/vml.go +++ b/vml.go @@ -845,7 +845,7 @@ func (f *File) addDrawingVML(sheetID int, drawingVML string, opts *vmlOptions) e leftOffset, vmlID = 0, 201 style = "position:absolute;73.5pt;width:108pt;height:59.25pt;z-index:1;mso-wrap-style:tight" } - colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(opts.sheet, col, row, opts.Format.OffsetX, opts.Format.OffsetY, int(opts.FormControl.Width), int(opts.FormControl.Height)) + colStart, rowStart, colEnd, rowEnd, _, _, x2, y2 := f.positionObjectPixels(opts.sheet, col, row, int(opts.FormControl.Width), int(opts.FormControl.Height), &opts.Format) anchor := fmt.Sprintf("%d, %d, %d, 0, %d, %d, %d, %d", colStart, leftOffset, rowStart, colEnd, x2, rowEnd, y2) if vml == nil { vml = &vmlDrawing{