]> source.dussan.org Git - poi.git/commitdiff
use cached formula result when autosizing sheet columns, see Bugzilla 50211
authorYegor Kozlov <yegor@apache.org>
Tue, 9 Nov 2010 15:04:55 +0000 (15:04 +0000)
committerYegor Kozlov <yegor@apache.org>
Tue, 9 Nov 2010 15:04:55 +0000 (15:04 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1033005 13f79535-47bb-0310-9956-ffa450edef68

src/documentation/content/xdocs/spreadsheet/quick-guide.xml
src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java
src/java/org/apache/poi/ss/util/SheetUtil.java [new file with mode: 0755]
src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java
src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/ColumnHelper.java
src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheetAutosizeColumn.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheetAutosizeColumn.java [new file with mode: 0644]
src/testcases/org/apache/poi/ss/usermodel/BaseTestSheetAutosizeColumn.java [new file with mode: 0644]

index acd08d906317fed8b6dda06803aeb2a7cb2aa85b..806ba435553dbbdd6f87e51b5fc19a51459040ce 100644 (file)
@@ -1371,9 +1371,14 @@ Examples:
      <section><title>Adjust column width to fit the contents</title>
         <source>
     Sheet sheet = workbook.getSheetAt(0);
-    sheet.autoSizeColumn((short)0); //adjust width of the first column
-    sheet.autoSizeColumn((short)1); //adjust width of the second column
+    sheet.autoSizeColumn(0); //adjust width of the first column
+    sheet.autoSizeColumn(1); //adjust width of the second column
         </source>
+    <p>
+      Note, that Sheet#autoSizeColumn() does not evaluate formula cells, 
+      the width of formula cells is calculated based on the cached formula result.
+      If your workbook has many formulas then it is a good idea to evaluate them before auto-sizing.
+    </p>
         <warning>
     To calculate column width HSSFSheet.autoSizeColumn uses Java2D classes
     that throw exception if graphical environment is not available. In case if graphical environment
index d7d2b62c71dc96da5625365e49faeaaa6d6b5b90..deb2890c7b974451dd07e562ad968cb55cfc72c3 100644 (file)
@@ -34,6 +34,8 @@
 
     <changes>
         <release version="3.8-beta1" date="2010-??-??">
+           <action dev="poi-developers" type="fix">49761 - Tolerate Double.NaN when reading .xls files</action>
+           <action dev="poi-developers" type="fix">50211 - Use cached formula result when auto-sizing formula cells</action>
            <action dev="poi-developers" type="fix">50118 - OLE2 does allow a directory with an empty name, so support this in POIFS</action>
            <action dev="poi-developers" type="fix">50119 - avoid NPE when XSSFReader comes across chart sheets</action>
         </release>
index 283032ee4da17331e0679d287e8037d97b8b36ed..96166d6a4c2c9c26fbf7bf86b842c74ee322f461 100644 (file)
@@ -54,6 +54,7 @@ import org.apache.poi.ss.usermodel.Row;
 import org.apache.poi.ss.util.CellRangeAddress;
 import org.apache.poi.ss.util.CellReference;
 import org.apache.poi.ss.util.SSCellRange;
+import org.apache.poi.ss.util.SheetUtil;
 import org.apache.poi.util.POILogFactory;
 import org.apache.poi.util.POILogger;
 
@@ -1146,7 +1147,8 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet {
              }
 
              //only shift if the region outside the shifted rows is not merged too
-             if (!containsCell(merged, startRow-1, 0) && !containsCell(merged, endRow+1, 0)){
+             if (!SheetUtil.containsCell(merged, startRow-1, 0) &&
+                 !SheetUtil.containsCell(merged, endRow+1, 0)){
                  merged.setFirstRow(merged.getFirstRow()+n);
                  merged.setLastRow(merged.getLastRow()+n);
                  //have to remove/add it back
@@ -1164,14 +1166,6 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet {
             this.addMergedRegion(region);
         }
     }
-    private static boolean containsCell(CellRangeAddress cr, int rowIx, int colIx) {
-        if (cr.getFirstRow() <= rowIx && cr.getLastRow() >= rowIx
-                && cr.getFirstColumn() <= colIx && cr.getLastColumn() >= colIx)
-        {
-            return true;
-        }
-        return false;
-    }
 
     /**
      * Shifts rows between startRow and endRow n number of rows.
@@ -1741,153 +1735,8 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet {
      * @param useMergedCells whether to use the contents of merged cells when calculating the width of the column
      */
     public void autoSizeColumn(int column, boolean useMergedCells) {
-        AttributedString str;
-        TextLayout layout;
-        /**
-         * Excel measures columns in units of 1/256th of a character width
-         * but the docs say nothing about what particular character is used.
-         * '0' looks to be a good choice.
-         */
-        char defaultChar = '0';
-
-        /**
-         * This is the multiple that the font height is scaled by when determining the
-         * boundary of rotated text.
-         */
-        double fontHeightMultiple = 2.0;
-
-        FontRenderContext frc = new FontRenderContext(null, true, true);
-
-        HSSFWorkbook wb = HSSFWorkbook.create(_book); // TODO - is it important to not use _workbook?
-        HSSFDataFormatter formatter = new HSSFDataFormatter();
-        HSSFFont defaultFont = wb.getFontAt((short) 0);
-
-        str = new AttributedString("" + defaultChar);
-        copyAttributes(defaultFont, str, 0, 1);
-        layout = new TextLayout(str.getIterator(), frc);
-        int defaultCharWidth = (int)layout.getAdvance();
-
-        double width = -1;
-        rows:
-        for (Iterator<Row> it = rowIterator(); it.hasNext();) {
-            HSSFRow row = (HSSFRow) it.next();
-            HSSFCell cell = row.getCell(column);
-
-            if (cell == null) {
-                continue;
-            }
-
-            int colspan = 1;
-            for (int i = 0 ; i < getNumMergedRegions(); i++) {
-                CellRangeAddress region = getMergedRegion(i);
-                if (containsCell(region, row.getRowNum(), column)) {
-                    if (!useMergedCells) {
-                        // If we're not using merged cells, skip this one and move on to the next.
-                        continue rows;
-                    }
-                    cell = row.getCell(region.getFirstColumn());
-                    colspan = 1 + region.getLastColumn() - region.getFirstColumn();
-                }
-            }
-
-            HSSFCellStyle style = cell.getCellStyle();
-            int cellType = cell.getCellType();
-            if(cellType == HSSFCell.CELL_TYPE_FORMULA) cellType = cell.getCachedFormulaResultType();
-
-            HSSFFont font = wb.getFontAt(style.getFontIndex());
-
-            if (cellType == HSSFCell.CELL_TYPE_STRING) {
-                HSSFRichTextString rt = cell.getRichStringCellValue();
-                String[] lines = rt.getString().split("\\n");
-                for (int i = 0; i < lines.length; i++) {
-                    String txt = lines[i] + defaultChar;
-                    str = new AttributedString(txt);
-                    copyAttributes(font, str, 0, txt.length());
-
-                    if (rt.numFormattingRuns() > 0) {
-                        for (int j = 0; j < lines[i].length(); j++) {
-                            int idx = rt.getFontAtIndex(j);
-                            if (idx != 0) {
-                                HSSFFont fnt = wb.getFontAt((short) idx);
-                                copyAttributes(fnt, str, j, j + 1);
-                            }
-                        }
-                    }
-
-                    layout = new TextLayout(str.getIterator(), frc);
-                    if(style.getRotation() != 0){
-                        /*
-                         * Transform the text using a scale so that it's height is increased by a multiple of the leading,
-                         * and then rotate the text before computing the bounds. The scale results in some whitespace around
-                         * the unrotated top and bottom of the text that normally wouldn't be present if unscaled, but
-                         * is added by the standard Excel autosize.
-                         */
-                        AffineTransform trans = new AffineTransform();
-                        trans.concatenate(AffineTransform.getRotateInstance(style.getRotation()*2.0*Math.PI/360.0));
-                        trans.concatenate(
-                        AffineTransform.getScaleInstance(1, fontHeightMultiple)
-                        );
-                        width = Math.max(width, ((layout.getOutline(trans).getBounds().getWidth() / colspan) / defaultCharWidth) + cell.getCellStyle().getIndention());
-                    } else {
-                        width = Math.max(width, ((layout.getBounds().getWidth() / colspan) / defaultCharWidth) + cell.getCellStyle().getIndention());
-                    }
-                }
-            } else {
-                String sval = null;
-                if (cellType == HSSFCell.CELL_TYPE_NUMERIC) {
-                    // Try to get it formatted to look the same as excel
-                    try {
-                        sval = formatter.formatCellValue(cell);
-                    } catch (Exception e) {
-                        sval = "" + cell.getNumericCellValue();
-                    }
-                } else if (cellType == HSSFCell.CELL_TYPE_BOOLEAN) {
-                    sval = String.valueOf(cell.getBooleanCellValue());
-                }
-                if(sval != null) {
-                    String txt = sval + defaultChar;
-                    str = new AttributedString(txt);
-                    copyAttributes(font, str, 0, txt.length());
-
-                    layout = new TextLayout(str.getIterator(), frc);
-                    if(style.getRotation() != 0){
-                        /*
-                         * Transform the text using a scale so that it's height is increased by a multiple of the leading,
-                         * and then rotate the text before computing the bounds. The scale results in some whitespace around
-                         * the unrotated top and bottom of the text that normally wouldn't be present if unscaled, but
-                         * is added by the standard Excel autosize.
-                         */
-                        AffineTransform trans = new AffineTransform();
-                        trans.concatenate(AffineTransform.getRotateInstance(style.getRotation()*2.0*Math.PI/360.0));
-                        trans.concatenate(
-                        AffineTransform.getScaleInstance(1, fontHeightMultiple)
-                        );
-                        width = Math.max(width, ((layout.getOutline(trans).getBounds().getWidth() / colspan) / defaultCharWidth) + cell.getCellStyle().getIndention());
-                    } else {
-                        width = Math.max(width, ((layout.getBounds().getWidth() / colspan) / defaultCharWidth) + cell.getCellStyle().getIndention());
-                    }
-                }
-            }
-
-        }
-        if (width != -1) {
-            width *= 256;
-            if (width > Short.MAX_VALUE) { //width can be bigger that Short.MAX_VALUE!
-                width = Short.MAX_VALUE;
-            }
-            _sheet.setColumnWidth(column, (short) (width));
-        }
-    }
-
-    /**
-     * Copy text attributes from the supplied HSSFFont to Java2D AttributedString
-     */
-    private void copyAttributes(HSSFFont font, AttributedString str, int startIdx, int endIdx) {
-        str.addAttribute(TextAttribute.FAMILY, font.getFontName(), startIdx, endIdx);
-        str.addAttribute(TextAttribute.SIZE, new Float(font.getFontHeightInPoints()));
-        if (font.getBoldweight() == HSSFFont.BOLDWEIGHT_BOLD) str.addAttribute(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD, startIdx, endIdx);
-        if (font.getItalic() ) str.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE, startIdx, endIdx);
-        if (font.getUnderline() == HSSFFont.U_SINGLE ) str.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON, startIdx, endIdx);
+        double width = SheetUtil.getColumnWidth(this, column, useMergedCells);
+        if(width != -1) setColumnWidth(column, (int) (256*width));
     }
 
     /**
diff --git a/src/java/org/apache/poi/ss/util/SheetUtil.java b/src/java/org/apache/poi/ss/util/SheetUtil.java
new file mode 100755 (executable)
index 0000000..e114e0d
--- /dev/null
@@ -0,0 +1,219 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.util;
+
+import org.apache.poi.ss.usermodel.*;
+
+import java.text.AttributedString;
+import java.awt.font.TextLayout;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextAttribute;
+import java.awt.geom.AffineTransform;
+
+
+/**
+ * Helper methods for when working with Usermodel sheets
+ *
+ * @author Yegor Kozlov
+ */
+public class SheetUtil {
+
+    /**
+     * Excel measures columns in units of 1/256th of a character width
+     * but the docs say nothing about what particular character is used.
+     * '0' looks to be a good choice.
+     */
+    private static final char defaultChar = '0';
+
+    /**
+     * This is the multiple that the font height is scaled by when determining the
+     * boundary of rotated text.
+     */
+    private static final double fontHeightMultiple = 2.0;
+
+    /**
+     *  Dummy formula evaluator that does nothing.
+     *  YK: The only reason of having this class is that
+     *  {@link org.apache.poi.ss.usermodel.DataFormatter#formatCellValue(org.apache.poi.ss.usermodel.Cell)}
+     *  returns formula string for formula cells. Dummy evaluator makes it to format the cached formula result.
+     *
+     *  See Bugzilla #50021 
+     */
+    private static final FormulaEvaluator dummyEvaluator = new FormulaEvaluator(){
+        public void clearAllCachedResultValues(){}
+        public void notifySetFormula(Cell cell) {}
+        public void notifyDeleteCell(Cell cell) {}
+        public void notifyUpdateCell(Cell cell) {}
+        public CellValue evaluate(Cell cell) {return null;  }
+        public Cell evaluateInCell(Cell cell) { return null; }
+
+        public int evaluateFormulaCell(Cell cell) {
+            return cell.getCachedFormulaResultType();
+        }
+
+    };
+
+    /**
+     * drawing context to measure text
+     */
+    private static final FontRenderContext fontRenderContext = new FontRenderContext(null, true, true);
+
+    /**
+     * Compute width of a column and return the result
+     *
+     * @param sheet the sheet to calculate
+     * @param column    0-based index of the column
+     * @param useMergedCells    whether to use merged cells
+     * @return  the width in pixels
+     */
+    public static double getColumnWidth(Sheet sheet, int column, boolean useMergedCells){
+        AttributedString str;
+        TextLayout layout;
+
+        Workbook wb = sheet.getWorkbook();
+        DataFormatter formatter = new DataFormatter();
+        Font defaultFont = wb.getFontAt((short) 0);
+
+        str = new AttributedString(String.valueOf(defaultChar));
+        copyAttributes(defaultFont, str, 0, 1);
+        layout = new TextLayout(str.getIterator(), fontRenderContext);
+        int defaultCharWidth = (int)layout.getAdvance();
+
+        double width = -1;
+        rows:
+        for (Row row : sheet) {
+            Cell cell = row.getCell(column);
+
+            if (cell == null) {
+                continue;
+            }
+
+            int colspan = 1;
+            for (int i = 0 ; i < sheet.getNumMergedRegions(); i++) {
+                CellRangeAddress region = sheet.getMergedRegion(i);
+                if (containsCell(region, row.getRowNum(), column)) {
+                    if (!useMergedCells) {
+                        // If we're not using merged cells, skip this one and move on to the next.
+                        continue rows;
+                    }
+                    cell = row.getCell(region.getFirstColumn());
+                    colspan = 1 + region.getLastColumn() - region.getFirstColumn();
+                }
+            }
+
+            CellStyle style = cell.getCellStyle();
+            int cellType = cell.getCellType();
+
+            // for formula cells we compute the cell width for the cached formula result
+            if(cellType == Cell.CELL_TYPE_FORMULA) cellType = cell.getCachedFormulaResultType();
+
+            Font font = wb.getFontAt(style.getFontIndex());
+
+            if (cellType == Cell.CELL_TYPE_STRING) {
+                RichTextString rt = cell.getRichStringCellValue();
+                String[] lines = rt.getString().split("\\n");
+                for (int i = 0; i < lines.length; i++) {
+                    String txt = lines[i] + defaultChar;
+
+                    str = new AttributedString(txt);
+                    copyAttributes(font, str, 0, txt.length());
+
+                    if (rt.numFormattingRuns() > 0) {
+                        // TODO: support rich text fragments
+                    }
+
+                    layout = new TextLayout(str.getIterator(), fontRenderContext);
+                    if(style.getRotation() != 0){
+                        /*
+                         * Transform the text using a scale so that it's height is increased by a multiple of the leading,
+                         * and then rotate the text before computing the bounds. The scale results in some whitespace around
+                         * the unrotated top and bottom of the text that normally wouldn't be present if unscaled, but
+                         * is added by the standard Excel autosize.
+                         */
+                        AffineTransform trans = new AffineTransform();
+                        trans.concatenate(AffineTransform.getRotateInstance(style.getRotation()*2.0*Math.PI/360.0));
+                        trans.concatenate(
+                        AffineTransform.getScaleInstance(1, fontHeightMultiple)
+                        );
+                        width = Math.max(width, ((layout.getOutline(trans).getBounds().getWidth() / colspan) / defaultCharWidth) + cell.getCellStyle().getIndention());
+                    } else {
+                        width = Math.max(width, ((layout.getBounds().getWidth() / colspan) / defaultCharWidth) + cell.getCellStyle().getIndention());
+                    }
+                }
+            } else {
+                String sval = null;
+                if (cellType == Cell.CELL_TYPE_NUMERIC) {
+                    // Try to get it formatted to look the same as excel
+                    try {
+                        sval = formatter.formatCellValue(cell, dummyEvaluator);
+                    } catch (Exception e) {
+                        sval = String.valueOf(cell.getNumericCellValue());
+                    }
+                } else if (cellType == Cell.CELL_TYPE_BOOLEAN) {
+                    sval = String.valueOf(cell.getBooleanCellValue()).toUpperCase();
+                }
+                if(sval != null) {
+                    String txt = sval + defaultChar;
+                    str = new AttributedString(txt);
+                    copyAttributes(font, str, 0, txt.length());
+
+                    layout = new TextLayout(str.getIterator(), fontRenderContext);
+                    if(style.getRotation() != 0){
+                        /*
+                         * Transform the text using a scale so that it's height is increased by a multiple of the leading,
+                         * and then rotate the text before computing the bounds. The scale results in some whitespace around
+                         * the unrotated top and bottom of the text that normally wouldn't be present if unscaled, but
+                         * is added by the standard Excel autosize.
+                         */
+                        AffineTransform trans = new AffineTransform();
+                        trans.concatenate(AffineTransform.getRotateInstance(style.getRotation()*2.0*Math.PI/360.0));
+                        trans.concatenate(
+                        AffineTransform.getScaleInstance(1, fontHeightMultiple)
+                        );
+                        width = Math.max(width, ((layout.getOutline(trans).getBounds().getWidth() / colspan) / defaultCharWidth) + cell.getCellStyle().getIndention());
+                    } else {
+                        width = Math.max(width, ((layout.getBounds().getWidth() / colspan) / defaultCharWidth) + cell.getCellStyle().getIndention());
+                    }
+                }
+            }
+
+        }
+        return width;        
+    }
+
+    /**
+     * Copy text attributes from the supplied Font to Java2D AttributedString
+     */
+    private static void copyAttributes(Font font, AttributedString str, int startIdx, int endIdx) {
+        str.addAttribute(TextAttribute.FAMILY, font.getFontName(), startIdx, endIdx);
+        str.addAttribute(TextAttribute.SIZE, (float)font.getFontHeightInPoints());
+        if (font.getBoldweight() == Font.BOLDWEIGHT_BOLD) str.addAttribute(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD, startIdx, endIdx);
+        if (font.getItalic() ) str.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE, startIdx, endIdx);
+        if (font.getUnderline() == Font.U_SINGLE ) str.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON, startIdx, endIdx);
+    }
+
+    public static boolean containsCell(CellRangeAddress cr, int rowIx, int colIx) {
+        if (cr.getFirstRow() <= rowIx && cr.getLastRow() >= rowIx
+                && cr.getFirstColumn() <= colIx && cr.getLastColumn() >= colIx)
+        {
+            return true;
+        }
+        return false;
+    }
+
+}
\ No newline at end of file
index 2acf93fdb62d22906eea997e67e62c35c670c889..f3d24232a5b18d0d15b94b17cce78ad098e53e05 100644 (file)
@@ -49,10 +49,7 @@ import org.apache.poi.ss.usermodel.Footer;
 import org.apache.poi.ss.usermodel.Header;
 import org.apache.poi.ss.usermodel.Row;
 import org.apache.poi.ss.usermodel.Sheet;
-import org.apache.poi.ss.util.CellRangeAddress;
-import org.apache.poi.ss.util.CellRangeAddressList;
-import org.apache.poi.ss.util.CellReference;
-import org.apache.poi.ss.util.SSCellRange;
+import org.apache.poi.ss.util.*;
 import org.apache.poi.util.HexDump;
 import org.apache.poi.util.Internal;
 import org.apache.poi.util.POILogFactory;
@@ -334,7 +331,7 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
      * @param useMergedCells whether to use the contents of merged cells when calculating the width of the column
      */
     public void autoSizeColumn(int column, boolean useMergedCells) {
-        double width = ColumnHelper.getColumnWidth(this, column, useMergedCells);
+        double width = SheetUtil.getColumnWidth(this, column, useMergedCells);
         if(width != -1){
             columnHelper.setColBestFit(column, true);
             columnHelper.setCustomWidth(column, true);
index f465078acb466c12e745bd9af5d294dc1d8cc94c..91a217068acd3a2bfb3c4c088aaf3174510fe4c5 100644 (file)
@@ -299,169 +299,4 @@ public class ColumnHelper {
            }
            return -1;
        }
-
-    public static double getColumnWidth(XSSFSheet sheet, int column, boolean useMergedCells){
-        AttributedString str;
-        TextLayout layout;
-        /**
-         * Excel measures columns in units of 1/256th of a character width
-         * but the docs say nothing about what particular character is used.
-         * '0' looks to be a good choice.
-         */
-        char defaultChar = '0';
-
-        /**
-         * This is the multiple that the font height is scaled by when determining the
-         * boundary of rotated text.
-         */
-        double fontHeightMultiple = 2.0;
-
-        FontRenderContext frc = new FontRenderContext(null, true, true);
-
-        XSSFWorkbook wb = sheet.getWorkbook();
-        XSSFFont defaultFont = wb.getFontAt((short) 0);
-
-        str = new AttributedString("" + defaultChar);
-        copyAttributes(defaultFont, str, 0, 1);
-        layout = new TextLayout(str.getIterator(), frc);
-        int defaultCharWidth = (int)layout.getAdvance();
-
-        double width = -1;
-        rows:
-        for (Iterator it = sheet.rowIterator(); it.hasNext();) {
-            XSSFRow row = (XSSFRow) it.next();
-            XSSFCell cell = row.getCell(column);
-
-            if (cell == null) {
-                continue;
-            }
-
-            int colspan = 1;
-            for (int i = 0 ; i < sheet.getNumMergedRegions(); i++) {
-                CellRangeAddress region = sheet.getMergedRegion(i);
-                if (containsCell(region, row.getRowNum(), column)) {
-                    if (!useMergedCells) {
-                        // If we're not using merged cells, skip this one and move on to the next.
-                        continue rows;
-                    }
-                    cell = row.getCell(region.getFirstColumn());
-                    colspan = 1 + region.getLastColumn() - region.getFirstColumn();
-                }
-            }
-
-            XSSFCellStyle style = cell.getCellStyle();
-            int cellType = cell.getCellType();
-            if(cellType == XSSFCell.CELL_TYPE_FORMULA) cellType = cell.getCachedFormulaResultType();
-            XSSFFont font = wb.getFontAt(style.getFontIndex());
-
-            if (cellType == XSSFCell.CELL_TYPE_STRING) {
-                XSSFRichTextString rt = cell.getRichStringCellValue();
-                String[] lines = rt.getString().split("\\n");
-                for (int i = 0; i < lines.length; i++) {
-                    String txt = lines[i] + defaultChar;
-                    str = new AttributedString(txt);
-                    copyAttributes(font, str, 0, txt.length());
-
-                    if (rt.numFormattingRuns() > 0) {
-                        int pos = 0;
-                        for (int j = 0; j < rt.numFormattingRuns(); j++) {
-                            XSSFFont fnt = rt.getFontOfFormattingRun(j);
-                            if (fnt != null) {
-                                int len = rt.getLengthOfFormattingRun(j);
-                                if(len > 0) { //ignore degenerate zero-length runs
-                                    copyAttributes(fnt, str, pos, pos + len);
-                                    pos += len;
-                                }
-                            }
-                        }
-                    }
-
-                    layout = new TextLayout(str.getIterator(), frc);
-                    if(style.getRotation() != 0){
-                        /*
-                         * Transform the text using a scale so that it's height is increased by a multiple of the leading,
-                         * and then rotate the text before computing the bounds. The scale results in some whitespace around
-                         * the unrotated top and bottom of the text that normally wouldn't be present if unscaled, but
-                         * is added by the standard Excel autosize.
-                         */
-                        AffineTransform trans = new AffineTransform();
-                        trans.concatenate(AffineTransform.getRotateInstance(style.getRotation()*2.0*Math.PI/360.0));
-                        trans.concatenate(
-                        AffineTransform.getScaleInstance(1, fontHeightMultiple)
-                        );
-                        width = Math.max(width, ((layout.getOutline(trans).getBounds().getWidth() / colspan) / defaultCharWidth) + cell.getCellStyle().getIndention());
-                    } else {
-                        width = Math.max(width, ((layout.getBounds().getWidth() / colspan) / defaultCharWidth) + cell.getCellStyle().getIndention());
-                    }
-                }
-            } else {
-                String sval = null;
-                if (cellType == XSSFCell.CELL_TYPE_NUMERIC) {
-                    String dfmt = style.getDataFormatString();
-                    String format = dfmt == null ? null : dfmt.replaceAll("\"", "");
-                    double value = cell.getNumericCellValue();
-                    try {
-                        NumberFormat fmt;
-                        if ("General".equals(format))
-                            sval = "" + value;
-                        else
-                        {
-                            fmt = new DecimalFormat(format);
-                            sval = fmt.format(value);
-                        }
-                    } catch (Exception e) {
-                        sval = "" + value;
-                    }
-                } else if (cellType == XSSFCell.CELL_TYPE_BOOLEAN) {
-                    sval = String.valueOf(cell.getBooleanCellValue());
-                }
-                if(sval != null) {
-                    String txt = sval + defaultChar;
-                    str = new AttributedString(txt);
-                    copyAttributes(font, str, 0, txt.length());
-
-                    layout = new TextLayout(str.getIterator(), frc);
-                    if(style.getRotation() != 0){
-                        /*
-                         * Transform the text using a scale so that it's height is increased by a multiple of the leading,
-                         * and then rotate the text before computing the bounds. The scale results in some whitespace around
-                         * the unrotated top and bottom of the text that normally wouldn't be present if unscaled, but
-                         * is added by the standard Excel autosize.
-                         */
-                        AffineTransform trans = new AffineTransform();
-                        trans.concatenate(AffineTransform.getRotateInstance(style.getRotation()*2.0*Math.PI/360.0));
-                        trans.concatenate(
-                        AffineTransform.getScaleInstance(1, fontHeightMultiple)
-                        );
-                        width = Math.max(width, ((layout.getOutline(trans).getBounds().getWidth() / colspan) / defaultCharWidth) + cell.getCellStyle().getIndention());
-                    } else {
-                        width = Math.max(width, ((layout.getBounds().getWidth() / colspan) / defaultCharWidth) + cell.getCellStyle().getIndention());
-                    }
-                }
-            }
-
-        }
-        return width;
-    }
-
-    /**
-     * Copy text attributes from the supplied HSSFFont to Java2D AttributedString
-     */
-    private static void copyAttributes(XSSFFont font, AttributedString str, int startIdx, int endIdx) {
-        str.addAttribute(TextAttribute.FAMILY, font.getFontName(), startIdx, endIdx);
-        str.addAttribute(TextAttribute.SIZE, new Float(font.getFontHeightInPoints()));
-        if (font.getBoldweight() == XSSFFont.BOLDWEIGHT_BOLD) str.addAttribute(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD, startIdx, endIdx);
-        if (font.getItalic() ) str.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE, startIdx, endIdx);
-        if (font.getUnderline() == XSSFFont.U_SINGLE ) str.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON, startIdx, endIdx);
-    }
-
-    private static boolean containsCell(CellRangeAddress cr, int rowIx, int colIx) {
-        if (cr.getFirstRow() <= rowIx && cr.getLastRow() >= rowIx
-                && cr.getFirstColumn() <= colIx && cr.getLastColumn() >= colIx)
-        {
-            return true;
-        }
-        return false;
-    }
-
 }
diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheetAutosizeColumn.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheetAutosizeColumn.java
new file mode 100644 (file)
index 0000000..031aadf
--- /dev/null
@@ -0,0 +1,31 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.usermodel;
+
+import org.apache.poi.ss.usermodel.BaseTestSheetAutosizeColumn;
+import org.apache.poi.xssf.XSSFITestDataProvider;
+
+/**
+ * @author Yegor Kozlov
+ */
+public final class TestXSSFSheetAutosizeColumn extends BaseTestSheetAutosizeColumn {
+
+    public TestXSSFSheetAutosizeColumn(){
+        super(XSSFITestDataProvider.instance);
+    }
+}
\ No newline at end of file
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheetAutosizeColumn.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheetAutosizeColumn.java
new file mode 100644 (file)
index 0000000..b3571b0
--- /dev/null
@@ -0,0 +1,53 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+
+import junit.framework.AssertionFailedError;
+
+import org.apache.poi.ddf.EscherDgRecord;
+import org.apache.poi.hssf.HSSFITestDataProvider;
+import org.apache.poi.hssf.HSSFTestDataSamples;
+import org.apache.poi.hssf.model.DrawingManager2;
+import org.apache.poi.hssf.model.InternalWorkbook;
+import org.apache.poi.hssf.model.InternalSheet;
+import org.apache.poi.hssf.record.*;
+import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.hssf.record.formula.Area3DPtg;
+import org.apache.poi.hssf.record.aggregates.WorksheetProtectionBlock;
+import org.apache.poi.hssf.usermodel.RecordInspector.RecordCollector;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.ss.util.CellRangeAddressList;
+import org.apache.poi.util.TempFile;
+
+/**
+ * Test auto-sizing columns in HSSF
+ *
+ * @author Yegor Kozlov
+ */
+public final class TestHSSFSheetAutosizeColumn extends BaseTestSheetAutosizeColumn {
+
+    public TestHSSFSheetAutosizeColumn() {
+        super(HSSFITestDataProvider.instance);
+    }
+
+}
\ No newline at end of file
diff --git a/src/testcases/org/apache/poi/ss/usermodel/BaseTestSheetAutosizeColumn.java b/src/testcases/org/apache/poi/ss/usermodel/BaseTestSheetAutosizeColumn.java
new file mode 100644 (file)
index 0000000..5adc9ee
--- /dev/null
@@ -0,0 +1,255 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.usermodel;
+
+import junit.framework.TestCase;
+import org.apache.poi.ss.ITestDataProvider;
+import org.apache.poi.ss.util.CellRangeAddress;
+
+import java.util.Calendar;
+
+/**
+ * Common superclass for testing automatic sizing of sheet columns
+ *
+ * @author Yegor Kozlov
+ */
+public abstract class BaseTestSheetAutosizeColumn extends TestCase {
+
+    private final ITestDataProvider _testDataProvider;
+
+    protected BaseTestSheetAutosizeColumn(ITestDataProvider testDataProvider) {
+        _testDataProvider = testDataProvider;
+    }
+
+    // TODO should we have this stuff in the FormulaEvaluator?
+    private void evaluateWorkbook(Workbook workbook){
+        FormulaEvaluator eval = workbook.getCreationHelper().createFormulaEvaluator();
+        for(int i=0; i < workbook.getNumberOfSheets(); i++) {
+            Sheet sheet = workbook.getSheetAt(i);
+            for (Row r : sheet) {
+                for (Cell c : r) {
+                    if (c.getCellType() == Cell.CELL_TYPE_FORMULA){
+                        eval.evaluateFormulaCell(c);
+                    }
+                }
+            }
+        }
+    }
+
+    public void testNumericCells(){
+        Workbook workbook = _testDataProvider.createWorkbook();
+        DataFormat df = workbook.getCreationHelper().createDataFormat();
+        Sheet sheet = workbook.createSheet();
+
+        Row row = sheet.createRow(0);
+        row.createCell(0).setCellValue(0); // getCachedFormulaResult() returns 0 for not evaluated formula cells
+        row.createCell(1).setCellValue(10);
+        row.createCell(2).setCellValue("10");
+        row.createCell(3).setCellFormula("(A1+B1)*1.0"); // a formula that returns '10'
+
+        Cell cell4 = row.createCell(4);       // numeric cell with a custom style
+        CellStyle style4 = workbook.createCellStyle();
+        style4.setDataFormat(df.getFormat("0.0000"));
+        cell4.setCellStyle(style4);
+        cell4.setCellValue(10); // formatted as '10.0000'
+
+        row.createCell(5).setCellValue("10.0000");
+
+        // autosize not-evaluated cells, formula cells are sized as if the result is 0
+        for (int i = 0; i < 6; i++) sheet.autoSizeColumn(i);
+
+        assertTrue(sheet.getColumnWidth(0) < sheet.getColumnWidth(1));  // width of '0' is less then width of '10'
+        assertEquals(sheet.getColumnWidth(1), sheet.getColumnWidth(2)); // 10 and '10' should be sized equally
+        assertEquals(sheet.getColumnWidth(3), sheet.getColumnWidth(0)); // formula result is unknown, the width is calculated  for '0'
+        assertEquals(sheet.getColumnWidth(4), sheet.getColumnWidth(5)); // 10.0000 and '10.0000'
+
+        // evaluate formulas and re-autosize
+        evaluateWorkbook(workbook);
+
+        for (int i = 0; i < 6; i++) sheet.autoSizeColumn(i);
+
+        assertTrue(sheet.getColumnWidth(0) < sheet.getColumnWidth(1));  // width of '0' is less then width of '10'
+        assertEquals(sheet.getColumnWidth(1), sheet.getColumnWidth(2)); // columns 1, 2 and 3 should have the same width
+        assertEquals(sheet.getColumnWidth(2), sheet.getColumnWidth(3)); // columns 1, 2 and 3 should have the same width
+        assertEquals(sheet.getColumnWidth(4), sheet.getColumnWidth(5)); // 10.0000 and '10.0000'
+    }
+
+    public void testBooleanCells(){
+        Workbook workbook = _testDataProvider.createWorkbook();
+        Sheet sheet = workbook.createSheet();
+
+        Row row = sheet.createRow(0);
+        row.createCell(0).setCellValue(0); // getCachedFormulaResult() returns 0 for not evaluated formula cells
+        row.createCell(1).setCellValue(true);
+        row.createCell(2).setCellValue("TRUE");
+        row.createCell(3).setCellFormula("1 > 0"); // a formula that returns true
+
+        // autosize not-evaluated cells, formula cells are sized as if the result is 0
+        for (int i = 0; i < 4; i++) sheet.autoSizeColumn(i);
+
+        assertTrue(sheet.getColumnWidth(1) > sheet.getColumnWidth(0));  // 'true' is wider than '0'
+        assertEquals(sheet.getColumnWidth(1), sheet.getColumnWidth(2));  // 10 and '10' should be sized equally
+        assertEquals(sheet.getColumnWidth(3), sheet.getColumnWidth(0));  // formula result is unknown, the width is calculated  for '0'
+
+        // evaluate formulas and re-autosize
+        evaluateWorkbook(workbook);
+
+        for (int i = 0; i < 4; i++) sheet.autoSizeColumn(i);
+
+        assertTrue(sheet.getColumnWidth(1) > sheet.getColumnWidth(0));  // 'true' is wider than '0'
+        assertEquals(sheet.getColumnWidth(1), sheet.getColumnWidth(2));  // columns 1, 2 and 3 should have the same width
+        assertEquals(sheet.getColumnWidth(2), sheet.getColumnWidth(3));  // columns 1, 2 and 3 should have the same width
+    }
+
+    public void testDateCells(){
+        Workbook workbook = _testDataProvider.createWorkbook();
+        Sheet sheet = workbook.createSheet();
+        DataFormat df = workbook.getCreationHelper().createDataFormat();
+
+        CellStyle style1 = workbook.createCellStyle();
+        style1.setDataFormat(df.getFormat("m"));
+
+        CellStyle style3 = workbook.createCellStyle();
+        style3.setDataFormat(df.getFormat("mmm"));
+
+        CellStyle style5 = workbook.createCellStyle(); //rotated text
+        style5.setDataFormat(df.getFormat("mmm/dd/yyyy"));
+
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(2010, 0, 1); // Jan 1 2010
+
+        Row row = sheet.createRow(0);
+        row.createCell(0).setCellValue(DateUtil.getJavaDate(0));   //default date
+
+        Cell cell1 = row.createCell(1);
+        cell1.setCellValue(calendar);
+        cell1.setCellStyle(style1);
+        row.createCell(2).setCellValue("1"); // column 1 should be sized as '1'
+
+        Cell cell3 = row.createCell(3);
+        cell3.setCellValue(calendar);
+        cell3.setCellStyle(style3);
+        row.createCell(4).setCellValue("Jan");
+
+        Cell cell5 = row.createCell(5);
+        cell5.setCellValue(calendar);
+        cell5.setCellStyle(style5);
+        row.createCell(6).setCellValue("Jan/01/2010");
+
+        Cell cell7 = row.createCell(7);
+        cell7.setCellFormula("DATE(2010,1,1)");
+        cell7.setCellStyle(style3); // should be sized as 'Jan'
+
+        // autosize not-evaluated cells, formula cells are sized as if the result is 0
+        for (int i = 0; i < 8; i++) sheet.autoSizeColumn(i);
+
+        assertEquals(sheet.getColumnWidth(2), sheet.getColumnWidth(1)); // date formatted as 'm'
+        assertTrue(sheet.getColumnWidth(3) > sheet.getColumnWidth(1));  // 'mmm' is wider than 'm'
+        assertEquals(sheet.getColumnWidth(4), sheet.getColumnWidth(3)); // date formatted as 'mmm'
+        assertTrue(sheet.getColumnWidth(5) > sheet.getColumnWidth(3));  // 'mmm/dd/yyyy' is wider than 'mmm'
+        assertEquals(sheet.getColumnWidth(6), sheet.getColumnWidth(5)); // date formatted as 'mmm/dd/yyyy'
+
+        // YK: width of not-evaluated formulas that return data is not determined
+        // POI seems to conevert '0' to Excel date which is the beginng of the Excel's date system
+
+        // evaluate formulas and re-autosize
+        evaluateWorkbook(workbook);
+
+        for (int i = 0; i < 8; i++) sheet.autoSizeColumn(i);
+
+        assertEquals(sheet.getColumnWidth(2), sheet.getColumnWidth(1)); // date formatted as 'm'
+        assertTrue(sheet.getColumnWidth(3) > sheet.getColumnWidth(1));  // 'mmm' is wider than 'm'
+        assertEquals(sheet.getColumnWidth(4), sheet.getColumnWidth(3)); // date formatted as 'mmm'
+        assertTrue(sheet.getColumnWidth(5) > sheet.getColumnWidth(3));  // 'mmm/dd/yyyy' is wider than 'mmm'
+        assertEquals(sheet.getColumnWidth(6), sheet.getColumnWidth(5)); // date formatted as 'mmm/dd/yyyy'
+        assertEquals(sheet.getColumnWidth(4), sheet.getColumnWidth(7)); // date formula formatted as 'mmm'
+    }
+
+    public void testStringCells(){
+        Workbook workbook = _testDataProvider.createWorkbook();
+        Sheet sheet = workbook.createSheet();
+        Row row = sheet.createRow(0);
+
+        Font defaultFont = workbook.getFontAt((short)0);
+
+        CellStyle style1 = workbook.createCellStyle();
+        Font font1 = workbook.createFont();
+        font1.setFontHeight((short)(2*defaultFont.getFontHeight()));
+        style1.setFont(font1);
+
+        row.createCell(0).setCellValue("x");
+        row.createCell(1).setCellValue("xxxx");
+        row.createCell(2).setCellValue("xxxxxxxxxxxx");
+        row.createCell(3).setCellValue("Apache\nSoftware Foundation"); // the text is splitted into two lines
+        row.createCell(4).setCellValue("Software Foundation");
+
+        Cell cell5 = row.createCell(5);
+        cell5.setCellValue("Software Foundation");
+        cell5.setCellStyle(style1); // same as in column 4 but the font is twice larger than the default font
+
+        for (int i = 0; i < 10; i++) sheet.autoSizeColumn(i);
+
+        assertTrue(2*sheet.getColumnWidth(0) < sheet.getColumnWidth(1)); // width is roughly proportional to the number of characters
+        assertTrue(2*sheet.getColumnWidth(1) < sheet.getColumnWidth(2));
+        assertEquals(sheet.getColumnWidth(4), sheet.getColumnWidth(3));
+        assertTrue(sheet.getColumnWidth(5) > sheet.getColumnWidth(4)); //larger font results in a wider column width
+    }
+
+    public void testRotatedText(){
+        Workbook workbook = _testDataProvider.createWorkbook();
+        Sheet sheet = workbook.createSheet();
+        Row row = sheet.createRow(0);
+
+        CellStyle style1 = workbook.createCellStyle();
+        style1.setRotation((short)90);
+
+        Cell cell0 = row.createCell(0);
+        cell0.setCellValue("Apache Software Foundation");
+        cell0.setCellStyle(style1);
+
+        Cell cell1 = row.createCell(1);
+        cell1.setCellValue("Apache Software Foundation");
+
+        for (int i = 0; i < 2; i++) sheet.autoSizeColumn(i);
+
+        int w0 = sheet.getColumnWidth(0);
+        int w1 = sheet.getColumnWidth(1);
+
+        assertTrue(w0*5 < w1); // rotated text occupies at least five times less horizontal space than normal text
+    }
+
+    public void testMergedCells(){
+        Workbook workbook = _testDataProvider.createWorkbook();
+        Sheet sheet = workbook.createSheet();
+
+        Row row = sheet.createRow(0);
+        sheet.addMergedRegion(CellRangeAddress.valueOf("A1:B1"));
+
+        Cell cell0 = row.createCell(0);
+        cell0.setCellValue("Apache Software Foundation");
+
+        int defaulWidth = sheet.getColumnWidth(0);
+        sheet.autoSizeColumn(0);
+        // column is unchanged if merged regions are ignored (Excel like behavior)
+        assertEquals(defaulWidth, sheet.getColumnWidth(0));
+
+        sheet.autoSizeColumn(0, true);
+        assertTrue(sheet.getColumnWidth(0) > defaulWidth);
+    }
+
+}
\ No newline at end of file