Skip to content

Instantly share code, notes, and snippets.

@b1ghawk
Last active August 20, 2020 02:44
Show Gist options
  • Save b1ghawk/bc6b552b7d909e9992cd488ffa25e695 to your computer and use it in GitHub Desktop.
Save b1ghawk/bc6b552b7d909e9992cd488ffa25e695 to your computer and use it in GitHub Desktop.
Apache POI patch : a more precise autoColumnWidth (with support for auto-wrap text)
import lombok.Data;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.SheetUtil;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.helpers.ColumnHelper;
import java.awt.font.FontRenderContext;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.text.AttributedString;
/**
* 对POI-autoSizeColumn修正自动列宽不准确的BUG,并添加新特性
* 1: 支持中英文及数字列宽的精准计算
* 2: 添加自动换行情况下的自动列宽(同时可以混合非自动换行的单元格)
*
* @author b1ghawk
*/
public class POIUtil {
private static final FontRenderContext fontRenderContext = new FontRenderContext(null, true, true);
private static final char defaultChar = '0';
private static final double fontHeightMultiple = 2.0;
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 void setDebugEvaluationOutputForNextEval(boolean value) { }
public void evaluateAll() { }
public int evaluateFormulaCell(Cell cell) {
return cell.getCachedFormulaResultType();
}
};
public static void autoSizeColumn(Sheet sheet, int column, boolean useMergedCells) {
double width = getColumnWidth(sheet, column, useMergedCells);
if (width != -1) {
width *= 256;
int maxColumnWidth = 255 * 256;
if (width > maxColumnWidth) {
width = maxColumnWidth;
}
sheet.setColumnWidth(column, (int) (width));
if (sheet instanceof XSSFSheet) {
ColumnHelper columnHelper = ((XSSFSheet) sheet).getColumnHelper();
if (columnHelper != null) {
columnHelper.setColBestFit(column, true);
}
}
}
}
private 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);
double defaultCharWidth = layout.getAdvance();
int maxColumnWidth = sheet.getColumnWidth(column);
MinColumnWidthState minState = new MinColumnWidthState(maxColumnWidth);
double width = -1;
for (Row row : sheet) {
Cell cell = row.getCell(column);
if (cell == null) {
continue;
}
double cellWidth = getCellWidth(cell, defaultCharWidth, maxColumnWidth, minState, formatter, useMergedCells);
width = Math.max(width, cellWidth);
}
if (!minState.isCancel() && minState.isAllGreaterThanMax()) {
width = minState.getMinimumWidth();
}
return width;
}
private static double getCellWidth(Cell cell, double defaultCharWidth, int maxColumnWidth, MinColumnWidthState minState, DataFormatter formatter, boolean useMergedCells) {
Sheet sheet = cell.getSheet();
Workbook wb = sheet.getWorkbook();
Row row = cell.getRow();
int column = cell.getColumnIndex();
// hacking text-wrap
boolean wrapText = false;
CellStyle cellStyle = cell.getCellStyle();
if (cellStyle != null) {
wrapText = cellStyle.getWrapText();
}
// hacking end.
int colspan = 1;
for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
CellRangeAddress region = sheet.getMergedRegion(i);
if (SheetUtil.containsCell(region, row.getRowNum(), column)) {
if (!useMergedCells) {
return -1;
}
cell = row.getCell(region.getFirstColumn());
colspan = 1 + region.getLastColumn() - region.getFirstColumn();
}
}
CellStyle style = cell.getCellStyle();
int cellType = cell.getCellType();
if (cellType == Cell.CELL_TYPE_FORMULA) cellType = cell.getCachedFormulaResultType();
Font font = wb.getFontAt(style.getFontIndex());
AttributedString str;
double width = -1;
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) {
}
width = getProperWidth(cell, defaultCharWidth, maxColumnWidth, minState, wrapText, colspan, style, str, width);
}
} else {
String sval = null;
if (cellType == Cell.CELL_TYPE_NUMERIC) {
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());
width = getProperWidth(cell, defaultCharWidth, maxColumnWidth, minState, wrapText, colspan, style, str, width);
}
}
return width;
}
private static double getProperWidth(Cell cell, double defaultCharWidth, int maxColumnWidth, MinColumnWidthState minState, boolean wrapText, int colspan, CellStyle style, AttributedString str, double width) {
TextLayout layout;
layout = new TextLayout(str.getIterator(), fontRenderContext);
if (style.getRotation() != 0) {
AffineTransform trans = new AffineTransform();
trans.concatenate(AffineTransform.getRotateInstance(style.getRotation() * 2.0 * Math.PI / 360.0));
trans.concatenate(
AffineTransform.getScaleInstance(1, fontHeightMultiple)
);
double v = ((layout.getOutline(trans).getBounds().getWidth() / colspan) / defaultCharWidth) + cell.getCellStyle().getIndention();
minState.apply(v);
if (wrapText && v * 256 > maxColumnWidth) {
v = maxColumnWidth * 1.0 / 256;
} else {
minState.setCancel(true);
}
width = Math.max(width, v);
} else {
double v = ((layout.getBounds().getWidth() / colspan) / defaultCharWidth) + cell.getCellStyle().getIndention();
minState.apply(v);
if (wrapText && v * 256 > maxColumnWidth) {
v = maxColumnWidth * 1.0 / 256;
} else {
minState.setCancel(true);
}
width = Math.max(width, v);
}
return width;
}
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);
}
@Data
private static class MinColumnWidthState {
private int maxColumnWidth;
private boolean cancel = false;
private boolean allGreaterThanMax = true;
private double minimumWidth;
public MinColumnWidthState(int maxColumnWidth) {
this.maxColumnWidth = maxColumnWidth;
this.minimumWidth = maxColumnWidth;
}
public void apply(double columnWidth) {
double w = columnWidth * 256;
if (allGreaterThanMax && w < maxColumnWidth) allGreaterThanMax = false;
minimumWidth = Math.min(minimumWidth, columnWidth);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment