How to make rotated text look good with Java2D

java-2d, render, rotation, text

Solution

I once had a similar problem, and solved it by drawing the text with high precision to an image, then drawing the rotated image.

Here's the code:

import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;

import javax.swing.*;

public class RotatedText extends JPanel {
    String message = "How does this text look?";

    public RotatedText() {
        this.setPreferredSize(new Dimension(640, 280));
    }

    public BufferedImage createStringImage(Graphics g, String s) {
        int w = g.getFontMetrics().stringWidth(s) + 5;
        int h = g.getFontMetrics().getHeight();

        BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
        Graphics2D imageGraphics = image.createGraphics();
        imageGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        imageGraphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        imageGraphics.setColor(Color.BLACK);
        imageGraphics.setFont(g.getFont());
        imageGraphics.drawString(s, 0, h - g.getFontMetrics().getDescent());
        imageGraphics.dispose();

        return image;
    }

    private void drawString(Graphics g, String s, int tx, int ty, double theta, double rotx, double roty) {
        AffineTransform aff = AffineTransform.getRotateInstance(theta, rotx, roty);
        aff.translate(tx, ty);

        Graphics2D g2D = ((Graphics2D) g);
        g2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        g2D.drawImage(createStringImage(g, s), aff, this);
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        g.setFont(new Font("MyriadPro", Font.BOLD, 20));

        drawString(g, message, 80, 20, 0, 0, 0);
        drawString(g, message, 80, 80, -Math.toRadians(7.0), -10, 80);
        drawString(g, message, 60, 260, -Math.toRadians(30.0), -40, 80);

        g.setFont(new Font("MyriadPro", Font.BOLD, 12));

        drawString(g, message, 380, 20, 0, 0, 0);
        drawString(g, message, 380, 120, -Math.toRadians(7.0), -10, 80);
        drawString(g, message, 320, 400, -Math.toRadians(30.0), -40, 80);
    }

    private void display() {
        JFrame f = new JFrame("RotateTest");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);
        f.pack();
        f.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new RotatedText().display();
            }
        });
    }
}

Problem

My question is not about how to rotate text with Java2D; I know how to do that. What I don't know is how to make the rotated text "look good." For example, if you create a text box in PowerPoint and rotate it, the text appears sharp and clear no matter the rotation angle. However, text drawn with `g2D.drawString()` looks okay at 0 or 90 degrees but not so good at other angles. Is there a way to manipulate the text to clean or sharpen it up? If so, then if someone could point me to where look to learn how to do this I would be so thankful. Below is a little program that illustrates what I'm talking about. The bigger font isn't too bad when rotated but still doesn't look very professional. The smaller font when rotated is terrible. ``` import java.awt.*; import java.awt.geom.*; import javax.swing.*; public class RotateTest extends JPanel { String message = "How does this text look?"; public RotateTest() { this.setPreferredSize(new Dimension(640, 280)); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2D = (Graphics2D) g; g2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); g2D.setFont(new Font("MyriadPro", Font.BOLD, 20)); g2D.drawString(message, 80, 20); AffineTransform orig = g2D.getTransform(); double angle = Math.toRadians(7.0); g2D.rotate(-angle, -10, 80); g2D.drawString(message, 80, 80); g2D.setTransform(orig); angle = Math.toRadians(30.0); g2D.rotate(-angle, -40, 80); g2D.drawString(message, 60, 260); g2D.setTransform(orig); g2D.setFont(new Font("MyriadPro", Font.BOLD, 12)); g2D.drawString(message, 380, 20); angle = Math.toRadians(7.0); g2D.rotate(-angle, -10, 80); g2D.drawString(message, 380, 120); g2D.setTransform(orig); angle = Math.toRadians(30.0); g2D.rotate(-angle, -40, 80); g2D.drawString(message, 320, 400); g2D.setTransform(orig); } private void display() { JFrame f = new JFrame("RotateTest"); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.add(this); f.pack(); f.setVisible(true); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { new RotateTest().display(); } }); } } ```

Original source