git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1033005 13f79535-47bb-0310-9956-ffa450edef68tags/REL_3_8_BETA1
@@ -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 |
@@ -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> |
@@ -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)); | |||
} | |||
/** |
@@ -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; | |||
} | |||
} |
@@ -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); |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |