Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Merging with latest development version
  • Loading branch information
jtulach committed Sep 25, 2025
commit f79024a04b8dcee8436d92fac881dcfa2b7d099d
209 changes: 103 additions & 106 deletions poi/src/main/java/org/apache/poi/ss/util/SheetUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,106 +93,12 @@ public void evaluateAll() {}
public CellType evaluateFormulaCell(Cell cell) { return cell.getCachedFormulaResultType(); }
};

/**
* Helper utilities to use <b>only</b> when dealing when java.desktop module is available.
* By moving these definitions into its own class, we prevent {@link NoClassDefFoundError}
* to happen <em>too early</em>. The rest of SheetUtil is resolved sooner,
* but WithJavaDesktop only when first accessed - e.g. when really java.desktop module
* is needed.
*/
private static final class WithJavaDesktop {
/**
* drawing context to measure text
*/
private static final FontRenderContext fontRenderContext = new FontRenderContext(null, true, true);

/**
* Copy text attributes from the supplied Font to Java2D AttributedString
*/
private static void copyAttributes(Font font, AttributedString str, @SuppressWarnings("SameParameterValue") int startIdx, int endIdx) {
str.addAttribute(TextAttribute.FAMILY, font.getFontName(), startIdx, endIdx);
str.addAttribute(TextAttribute.SIZE, (float)font.getFontHeightInPoints());
if (font.getBold()) 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);
}

/**
* Calculate the best-fit width for a cell
* If a merged cell spans multiple columns, evenly distribute the column width among those columns
*
* @param defaultCharWidth the width of a character using the default font in a workbook
* @param colspan the number of columns that is spanned by the cell (1 if the cell is not part of a merged region)
* @param style the cell style, which contains text rotation and indention information needed to compute the cell width
* @param minWidth the minimum best-fit width. This algorithm will only return values greater than or equal to the minimum width.
* @param str the text contained in the cell
* @return the best fit cell width
*/
private static double getCellWidth(int defaultCharWidth, int colspan,
CellStyle style, double minWidth, AttributedString str) {
TextLayout layout = new TextLayout(str.getIterator(), WithJavaDesktop.fontRenderContext);
final Rectangle2D bounds;
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)
);
bounds = layout.getOutline(trans).getBounds();
} else {
bounds = layout.getBounds();
}
// frameWidth accounts for leading spaces which is excluded from bounds.getWidth()
final double frameWidth = bounds.getX() + bounds.getWidth();
return Math.max(minWidth, ((frameWidth / colspan) / defaultCharWidth) + style.getIndention());
}

private static int getDefaultCharWidthFromLayout(Font defaultFont, AttributedString str) {
WithJavaDesktop.copyAttributes(defaultFont, str, 0, 1);
TextLayout layout = new TextLayout(str.getIterator(), WithJavaDesktop.fontRenderContext);
return (int) layout.getAdvance();
}

private static boolean canComputeColumnWidthFromLayout(Font font) {
// not sure what is the best value sample-here, only "1" did not work on some platforms...
AttributedString str = new AttributedString("1w");
WithJavaDesktop.copyAttributes(font, str, 0, "1w".length());

TextLayout layout = new TextLayout(str.getIterator(), WithJavaDesktop.fontRenderContext);
return (layout.getBounds().getWidth() > 0);
}

}

/**
* A system property which can be enabled to not fail when the
* font-system is not available on the current machine.
* Since POI 5.4.0, this flag is enabled by default.
*/
private static final boolean ignoreMissingFontSystem;
static {
final String propValue = System.getProperty("org.apache.poi.ss.ignoreMissingFontSystem");
boolean ignore;
if (propValue != null) {
ignore = Boolean.parseBoolean(propValue);
} else {
try {
Class.forName("java.awt.font.TextAttribute");
ignore = false;
} catch (ClassNotFoundException ex) {
// if there is no java.desktop module then ignoreMissingFontSystem
// as the system has clearly been configured without a font system
ignore = true;
}
}
ignoreMissingFontSystem = ignore;
}
private static boolean ignoreMissingFontSystem = initIgnoreMissingFontSystemFlag();

/**
* Which default char-width to use if the font-system is unavailable.
Expand Down Expand Up @@ -335,6 +241,80 @@ public static double getCellWidth(Cell cell, float defaultCharWidth, DataFormatt
return width;
}

private static final class WithJavaDesktop {
/**
* drawing context to measure text
*/
private static FontRenderContext fontRenderContext = new FontRenderContext(null, true, true);

/**
* Calculate the best-fit width for a cell
* If a merged cell spans multiple columns, evenly distribute the column width among those columns
*
* @param defaultCharWidth the width of a character using the default font in a workbook
* @param colspan the number of columns that is spanned by the cell (1 if the cell is not part of a merged region)
* @param style the cell style, which contains text rotation and indention information needed to compute the cell width
* @param minWidth the minimum best-fit width. This algorithm will only return values greater than or equal to the minimum width.
* @param str the text contained in the cell
* @return the best fit cell width
*/
private static double getCellWidth(float defaultCharWidth, final int colspan,
final CellStyle style, final double minWidth, final AttributedString str) {
TextLayout layout;
try {
layout = new TextLayout(str.getIterator(), fontRenderContext);
} catch (Throwable t) {
if (shouldIgnoreMissingFontSystem(t)) {
return FAILOVER_FUNCTION.apply(defaultCharWidth, colspan, style, minWidth, str);
}
throw t;
}
final Rectangle2D bounds;
if (style.getRotation() != 0) {
/*
* Transform the text using a scale so that its 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)
);
bounds = layout.getOutline(trans).getBounds();
} else {
bounds = layout.getBounds();
}
// frameWidth accounts for leading spaces which is excluded from bounds.getWidth()
final double frameWidth = bounds.getX() + bounds.getWidth();
return Math.max(minWidth, ((frameWidth / colspan) / defaultCharWidth) + style.getIndention());
}

private static float getDefaultCharWidthAsFloat(AttributedString str) {
TextLayout layout = new TextLayout(str.getIterator(), fontRenderContext);
return layout.getAdvance();
}

/**
* Copy text attributes from the supplied Font to Java2D AttributedString
*/
private static void copyAttributes(Font font, AttributedString str, @SuppressWarnings("SameParameterValue") int startIdx, int endIdx) {
str.addAttribute(TextAttribute.FAMILY, font.getFontName(), startIdx, endIdx);
str.addAttribute(TextAttribute.SIZE, (float)font.getFontHeightInPoints());
if (font.getBold()) 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);
}

private static boolean canComputeColumnWidth(Font font, AttributedString str) {
copyAttributes(font, str, 0, "1w".length());

TextLayout layout = new TextLayout(str.getIterator(), fontRenderContext);
return (layout.getBounds().getWidth() > 0);
}

}

/**
* Compute width of a column and return the result.
Expand Down Expand Up @@ -386,13 +366,27 @@ public static double getColumnWidth(Sheet sheet, int column, boolean useMergedCe
*/
@Internal
public static int getDefaultCharWidth(final Workbook wb) {
return Math.round(getDefaultCharWidthAsFloat(wb));
}

/**
* Get default character width using the Workbook's default font. Note that this can
* fail if your OS does not have the right fonts installed.
*
* @param wb the workbook to get the default character width from
* @return default character width in pixels (as a float)
* @since POI 5.2.5
*/
@Internal
public static float getDefaultCharWidthAsFloat(final Workbook wb) {
Font defaultFont = wb.getFontAt( 0);

AttributedString str = new AttributedString(String.valueOf(defaultChar));
try {
Font defaultFont = wb.getFontAt( 0);
AttributedString str = new AttributedString(String.valueOf(defaultChar));
return WithJavaDesktop.getDefaultCharWidthFromLayout(defaultFont, str);
} catch (UnsatisfiedLinkError | NoClassDefFoundError | InternalError e) {
if (ignoreMissingFontSystem) {
WithJavaDesktop.copyAttributes(defaultFont, str, 0, 1);
return WithJavaDesktop.getDefaultCharWidthAsFloat(str);
} catch (Throwable t) {
if (shouldIgnoreMissingFontSystem(t)) {
return DEFAULT_CHAR_WIDTH;
}
throw t;
Expand Down Expand Up @@ -485,16 +479,19 @@ private static double getColumnWidthForRow(
* @return true if computing the size for this Font will succeed, false otherwise
*/
public static boolean canComputeColumnWidth(Font font) {
// not sure what is the best value sample-here, only "1" did not work on some platforms...
AttributedString str = new AttributedString("1w");
try {
return WithJavaDesktop.canComputeColumnWidthFromLayout(font);
} catch (UnsatisfiedLinkError | NoClassDefFoundError | InternalError e) {
if (ignoreMissingFontSystem) {
return WithJavaDesktop.canComputeColumnWidth(font, str);
} catch (Throwable t) {
if (shouldIgnoreMissingFontSystem(t)) {
return false;
}
throw e;
throw t;
}
}


/**
* Return the cell, without taking account of merged regions.
* <p>
Expand Down Expand Up @@ -565,11 +562,11 @@ protected static void setIgnoreMissingFontSystem(boolean value) {
}

protected static FontRenderContext getFontRenderContext() {
return fontRenderContext;
return WithJavaDesktop.fontRenderContext;
}

protected static void setFontRenderContext(FontRenderContext fontRenderContext) {
SheetUtil.fontRenderContext = fontRenderContext;
WithJavaDesktop.fontRenderContext = fontRenderContext;
}

private static boolean initIgnoreMissingFontSystemFlag() {
Expand Down
You are viewing a condensed version of this merge commit. You can view the full changes here.