Need a way to scale a font to fit a rectangle
Semi-pseudo code:
public Font scaleFont(
String text, Rectangle rect, Graphics g, Font font) {
float fontSize = 20.0f;
font = g.getFont().deriveFont(fontSize);
int width = g.getFontMetrics(font).stringWidth(text);
fontSize = (rect.width / width ) * fontSize;
return g.getFont().deriveFont(fontSize);
}
A derivation that iterates:
/**
* Adjusts the given {@link Font}/{@link String} size such that it fits
* within the bounds of the given {@link Rectangle}.
*
* @param label Contains the text and font to scale.
* @param dst The bounds for fitting the string.
* @param graphics The context for rendering the string.
* @return A new {@link Font} instance that is guaranteed to write the given
* string within the bounds of the given {@link Rectangle}.
*/
public Font scaleFont(
final JLabel label, final Rectangle dst, final Graphics graphics ) {
assert label != null;
assert dst != null;
assert graphics != null;
final var font = label.getFont();
final var text = label.getText();
final var frc = ((Graphics2D) graphics).getFontRenderContext();
final var dstWidthPx = dst.getWidth();
final var dstHeightPx = dst.getHeight();
var minSizePt = 1f;
var maxSizePt = 1000f;
var scaledFont = font;
float scaledPt = scaledFont.getSize();
while( maxSizePt - minSizePt > 1f ) {
scaledFont = scaledFont.deriveFont( scaledPt );
final var layout = new TextLayout( text, scaledFont, frc );
final var fontWidthPx = layout.getVisibleAdvance();
final var metrics = scaledFont.getLineMetrics( text, frc );
final var fontHeightPx = metrics.getHeight();
if( (fontWidthPx > dstWidthPx) || (fontHeightPx > dstHeightPx) ) {
maxSizePt = scaledPt;
}
else {
minSizePt = scaledPt;
}
scaledPt = (minSizePt + maxSizePt) / 2;
}
return scaledFont.deriveFont( (float) Math.floor( scaledPt ) );
}
Imagine you want to add a label to a component that has rectangular bounds r
such that the label completely fills the component's area. One could write:
final Font DEFAULT_FONT = new Font( "DejaVu Sans", BOLD, 12 );
final Color COLOUR_LABEL = new Color( 33, 33, 33 );
// TODO: Return a valid container component instance.
final var r = getComponent().getBounds();
final var graphics = getComponent().getGraphics();
final int width = (int) r.getWidth();
final int height = (int) r.getHeight();
final var label = new JLabel( text );
label.setFont( DEFAULT_FONT );
label.setSize( width, height );
label.setForeground( COLOUR_LABEL );
final var scaledFont = scaleFont( label, r, graphics );
label.setFont( scaledFont );
You could use interpolation search:
public static Font scaleFont(String text, Rectangle rect, Graphics g, Font pFont) {
float min=0.1f;
float max=72f;
float size=18.0f;
Font font=pFont;
while(max - min <= 0.1) {
font = g.getFont().deriveFont(size);
FontMetrics fm = g.getFontMetrics(font);
int width = fm.stringWidth(text);
if (width == rect.width) {
return font;
} else {
if (width < rect.width) {
min = size;
} else {
max = size;
}
size = Math.min(max, Math.max(min, size * (float)rect.width / (float)width));
}
}
return font;
}
Change all width variables to float instead of int for better result.
public static Font scaleFontToFit(String text, int width, Graphics g, Font pFont)
{
float fontSize = pFont.getSize();
float fWidth = g.getFontMetrics(pFont).stringWidth(text);
if(fWidth <= width)
return pFont;
fontSize = ((float)width / fWidth) * fontSize;
return pFont.deriveFont(fontSize);
}