Scaling a font with
deriveFont(AffineTransform.getScaleInstance(scaleFactor, scaleFactor))
fails, once the scaleFactor crosses a certain threshold. Glyphs are then drawn on top of each other, not after one another (see attached screenshot).
Demo Code:
==========
import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
public class FontScalingIssues {
public static void main(final String[] args) {
createFrame(true);
createFrame(false);
}
private static void createFrame(final boolean scaleWithTransform) {
final String xY = "XY";
final JFrame frame = new JFrame("scaleWithTransform: " + scaleWithTransform);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new BorderLayout());
final JPanel iconPanel = new JPanel(new FlowLayout());
frame.getContentPane().add(iconPanel, BorderLayout.CENTER);
final TextIcon xYIcon = new TextIcon(xY, scaleWithTransform);
iconPanel.add(xYIcon);
final JSlider slider = new JSlider(1, 2000, 1);
slider.setSnapToTicks(false);
final JPanel sliderPanel = new JPanel(new FlowLayout());
sliderPanel.add(slider);
final JLabel scaleFactorLabel = new JLabel("1.0");
sliderPanel.add(scaleFactorLabel);
frame.getContentPane().add(sliderPanel, BorderLayout.SOUTH);
slider.addChangeListener(e -> {
final float scaleFactor = slider.getValue()/100f;
xYIcon.setScaleFactor(scaleFactor);
scaleFactorLabel.setText("" + scaleFactor);
frame.invalidate();
});
SwingUtilities.invokeLater(() -> {
final int y = scaleWithTransform ? 100 : 500;
frame.setBounds(100, y, 500, 300);
frame.setVisible(true);
});
}
private static class TextIcon extends JLabel {
private final String text;
private final boolean scaleWithTransform;
public TextIcon(final String text, final boolean scaleWithTransform) {
this.text = text;
this.scaleWithTransform = scaleWithTransform;
setIcon(createIconWithText(1f));
}
public void setScaleFactor(final float scaleFactor) {
setIcon(createIconWithText(scaleFactor));
}
private ImageIcon createIconWithText(final float scaleFactor) {
final int width = 400;
final int height = 200;
final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
final Graphics2D g2d = (Graphics2D)image.getGraphics();
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
if (scaleWithTransform) {
g2d.setFont(g2d.getFont().deriveFont(AffineTransform.getScaleInstance(scaleFactor, scaleFactor)));
} else {
final float originalSize = g2d.getFont().getSize2D();
g2d.setFont(g2d.getFont().deriveFont(originalSize * scaleFactor));
}
final FontMetrics newMetrics = g2d.getFontMetrics();
// fill whole background
g2d.setColor(Color.GREEN);
g2d.fillRect(0, 0, width, height);
// draw string bounds as RED background
final Rectangle2D rect = newMetrics.getStringBounds(text, g2d);
g2d.setColor(Color.RED);
g2d.fillRect((width - (int) rect.getWidth()) / 2, 0, (int)rect.getWidth(), (int)rect.getHeight());
// draw string
g2d.setColor(Color.BLACK);
g2d.drawString(text, (width - (int) rect.getWidth()) / 2, newMetrics.getAscent());
g2d.dispose();
return new ImageIcon(image);
}
}
}
The code opens two JFrames that allow to scale some characters using a slider. The scaling method differs. Frame 1 scales with an AffineTransform, frame 2 uses deriveFont(newSize). Once you scale beyond a certain value, the AffineTransform version draws glyphs on top of each other. Despite this, the bounding box (drawn in RED) stays correct.
The bug may be connected to https://bugs.openjdk.java.net/browse/JDK-8212743