diff --git a/col.go b/col.go index b93087738c..a34e942302 100644 --- a/col.go +++ b/col.go @@ -17,6 +17,7 @@ import ( "math" "strconv" "strings" + "unicode/utf8" "github.com/mohae/deepcopy" ) @@ -479,9 +480,15 @@ func (f *File) SetColWidth(sheet, startCol, endCol string, width float64) error if err != nil { return err } + + return f.setColWidth(sheet, min, max, width) +} + +func (f *File) setColWidth(sheet string, min, max int, width float64) error { if width > MaxColumnWidth { return ErrColumnWidth } + ws, err := f.workSheetReader(sheet) if err != nil { return err @@ -512,6 +519,81 @@ func (f *File) SetColWidth(sheet, startCol, endCol string, width float64) error return err } +// AutoFitColWidth provides a function to autofit columns according to +// their text content with default font size and font. +// Note: this only works on the column with cells which not contains +// formula cell and style with a number format. +// +// For example set column of column H on Sheet1: +// +// err = f.AutoFitColWidth("Sheet1", "H") +// +// Set style of columns C:F on Sheet1: +// +// err = f.AutoFitColWidth("Sheet1", "C:F") +func (f *File) AutoFitColWidth(sheetName, columns string) error { + startColIdx, endColIdx, err := f.parseColRange(columns) + if err != nil { + return err + } + + cols, err := f.Cols(sheetName) + if err != nil { + return err + } + + colIdx := 1 + for cols.Next() { + if colIdx >= startColIdx && colIdx <= endColIdx { + rowCells, _ := cols.Rows() + var max int + for i := range rowCells { + rowCell := rowCells[i] + + // Single Byte Character Set(SBCS) is 1 holds + // Multi-Byte Character System(MBCS) is 2 holds + var cellLenSBCS, cellLenMBCS int + for ii := range rowCell { + if rowCell[ii] < 0x80 { + cellLenSBCS++ + } + } + + runeLen := utf8.RuneCountInString(rowCell) + cellLenMBCS = runeLen - cellLenSBCS + + cellWidth := cellLenSBCS + cellLenMBCS*2 + if cellWidth > max { + max = cellWidth + } + } + + // The ratio of 1.123 is the best approximation I tried my best to + // find. + actualMax := float64(max) * 1.123 + + if actualMax < defaultColWidth { + actualMax = defaultColWidth + } else if actualMax >= MaxColumnWidth { + actualMax = MaxColumnWidth + } + + if err := f.setColWidth(sheetName, colIdx, colIdx, actualMax); err != nil { + return err + } + } + + // fast go away. + if colIdx == endColIdx { + break + } + + colIdx++ + } + + return nil +} + // flatCols provides a method for the column's operation functions to flatten // and check the worksheet columns. func flatCols(col xlsxCol, cols []xlsxCol, replacer func(fc, c xlsxCol) xlsxCol) []xlsxCol { diff --git a/col_test.go b/col_test.go index 0ed1906166..3977e5a287 100644 --- a/col_test.go +++ b/col_test.go @@ -2,7 +2,9 @@ package excelize import ( "path/filepath" + "strings" "testing" + "unicode/utf8" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -360,6 +362,49 @@ func TestColWidth(t *testing.T) { convertRowHeightToPixels(0) } +func TestAutoFitColWidth(t *testing.T) { + getCellLen := func(s string) int { + var cellLenSBCS, cellLenMBCS int + for i := range s { + if s[i] < 0x80 { + cellLenSBCS++ + } + } + + runeLen := utf8.RuneCountInString(s) + cellLenMBCS = runeLen - cellLenSBCS + + return cellLenSBCS + cellLenMBCS*2 + } + + for _, c := range []string{"", "A", "a", "a你好", "你好", "あなた"} { + f := NewFile() + for i := 1; i <= 10; i++ { + colN, err := ColumnNumberToName(i) + assert.NoError(t, err) + + v := strings.Repeat(c, i) + assert.NoError(t, f.SetCellValue("Sheet1", colN+"1", v)) + + assert.NoError(t, f.AutoFitColWidth("Sheet1", colN)) + + got, err := f.GetColWidth("Sheet1", colN) + assert.CallerInfo() + assert.NoError(t, err) + + actualLen := float64(getCellLen(v)) * 1.123 + if actualLen < defaultColWidth { + assert.Equal(t, defaultColWidth, got) + } else if actualLen >= MaxColumnWidth { + assert.Equal(t, float64(MaxColumnWidth), got) + } else { + assert.Equal(t, actualLen, got) + } + } + assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAutoColWidth.xlsx"))) + } +} + func TestGetColStyle(t *testing.T) { f := NewFile() styleID, err := f.GetColStyle("Sheet1", "A")