git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1033005 13f79535-47bb-0310-9956-ffa450edef68tags/REL_3_8_BETA1
<section><title>Adjust column width to fit the contents</title> | <section><title>Adjust column width to fit the contents</title> | ||||
<source> | <source> | ||||
Sheet sheet = workbook.getSheetAt(0); | 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> | </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> | <warning> | ||||
To calculate column width HSSFSheet.autoSizeColumn uses Java2D classes | To calculate column width HSSFSheet.autoSizeColumn uses Java2D classes | ||||
that throw exception if graphical environment is not available. In case if graphical environment | that throw exception if graphical environment is not available. In case if graphical environment |
<changes> | <changes> | ||||
<release version="3.8-beta1" date="2010-??-??"> | <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">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> | <action dev="poi-developers" type="fix">50119 - avoid NPE when XSSFReader comes across chart sheets</action> | ||||
</release> | </release> |
import org.apache.poi.ss.util.CellRangeAddress; | import org.apache.poi.ss.util.CellRangeAddress; | ||||
import org.apache.poi.ss.util.CellReference; | import org.apache.poi.ss.util.CellReference; | ||||
import org.apache.poi.ss.util.SSCellRange; | 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.POILogFactory; | ||||
import org.apache.poi.util.POILogger; | import org.apache.poi.util.POILogger; | ||||
} | } | ||||
//only shift if the region outside the shifted rows is not merged too | //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.setFirstRow(merged.getFirstRow()+n); | ||||
merged.setLastRow(merged.getLastRow()+n); | merged.setLastRow(merged.getLastRow()+n); | ||||
//have to remove/add it back | //have to remove/add it back | ||||
this.addMergedRegion(region); | 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. | * Shifts rows between startRow and endRow n number of rows. | ||||
* @param useMergedCells whether to use the contents of merged cells when calculating the width of the column | * @param useMergedCells whether to use the contents of merged cells when calculating the width of the column | ||||
*/ | */ | ||||
public void autoSizeColumn(int column, boolean useMergedCells) { | 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)); | |||||
} | } | ||||
/** | /** |
/* ==================================================================== | |||||
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; | |||||
} | |||||
} |
import org.apache.poi.ss.usermodel.Header; | import org.apache.poi.ss.usermodel.Header; | ||||
import org.apache.poi.ss.usermodel.Row; | import org.apache.poi.ss.usermodel.Row; | ||||
import org.apache.poi.ss.usermodel.Sheet; | 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.HexDump; | ||||
import org.apache.poi.util.Internal; | import org.apache.poi.util.Internal; | ||||
import org.apache.poi.util.POILogFactory; | import org.apache.poi.util.POILogFactory; | ||||
* @param useMergedCells whether to use the contents of merged cells when calculating the width of the column | * @param useMergedCells whether to use the contents of merged cells when calculating the width of the column | ||||
*/ | */ | ||||
public void autoSizeColumn(int column, boolean useMergedCells) { | public void autoSizeColumn(int column, boolean useMergedCells) { | ||||
double width = ColumnHelper.getColumnWidth(this, column, useMergedCells); | |||||
double width = SheetUtil.getColumnWidth(this, column, useMergedCells); | |||||
if(width != -1){ | if(width != -1){ | ||||
columnHelper.setColBestFit(column, true); | columnHelper.setColBestFit(column, true); | ||||
columnHelper.setCustomWidth(column, true); | columnHelper.setCustomWidth(column, true); |
} | } | ||||
return -1; | 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; | |||||
} | |||||
} | } |
/* ==================================================================== | |||||
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); | |||||
} | |||||
} |
/* ==================================================================== | |||||
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); | |||||
} | |||||
} |
/* ==================================================================== | |||||
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); | |||||
} | |||||
} |