diff --git a/src/documentation/content/xdocs/spreadsheet/quick-guide.xml b/src/documentation/content/xdocs/spreadsheet/quick-guide.xml index acd08d906..806ba4355 100644 --- a/src/documentation/content/xdocs/spreadsheet/quick-guide.xml +++ b/src/documentation/content/xdocs/spreadsheet/quick-guide.xml @@ -1371,9 +1371,14 @@ Examples:
Adjust column width to fit the contents 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 +

+ 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. +

To calculate column width HSSFSheet.autoSizeColumn uses Java2D classes that throw exception if graphical environment is not available. In case if graphical environment diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index d7d2b62c7..deb2890c7 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,8 @@ + 49761 - Tolerate Double.NaN when reading .xls files + 50211 - Use cached formula result when auto-sizing formula cells 50118 - OLE2 does allow a directory with an empty name, so support this in POIFS 50119 - avoid NPE when XSSFReader comes across chart sheets diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java index 283032ee4..96166d6a4 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java @@ -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 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 index 000000000..e114e0d8f --- /dev/null +++ b/src/java/org/apache/poi/ss/util/SheetUtil.java @@ -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 diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java index 2acf93fdb..f3d24232a 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java @@ -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); diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/ColumnHelper.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/ColumnHelper.java index f465078ac..91a217068 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/ColumnHelper.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/ColumnHelper.java @@ -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 index 000000000..031aadfc3 --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheetAutosizeColumn.java @@ -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 index 000000000..b3571b0c0 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheetAutosizeColumn.java @@ -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 index 000000000..5adc9ee78 --- /dev/null +++ b/src/testcases/org/apache/poi/ss/usermodel/BaseTestSheetAutosizeColumn.java @@ -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