Last active
August 20, 2020 02:44
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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