Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8212743

Graphics.drawString() renders certain unicode chars incorrectly, when the font is scaled

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: P4 P4
    • tbd
    • 9, 10, 11
    • client-libs
    • None
    • java --version
      openjdk 11.0.1 2018-10-16
      OpenJDK Runtime Environment 18.9 (build 11.0.1+13)
      OpenJDK 64-Bit Server VM 18.9 (build 11.0.1+13, mixed mode)

       

      macOS 10.14.0

    • 2d
    • os_x

      When drawing a large version of ♫ (\u266B) with g.drawString() the font changes: the connecting beam points down, instead of up and the glyph is suddenly smaller. FontMetrics still reflect the width and height of what a properly scaled glyph would have looked like. This last fact makes this especially problematic, as the glyphs cannot be positioned properly anymore.

      Demo Code:
      ==========

      import javax.swing.*;
      import java.awt.*;
      import java.awt.geom.Rectangle2D;
      import java.awt.image.BufferedImage;

      public class UnicodeFontMetricsIssues {

          public static void main(final String[] args) {
              final String note = "\u266B";
              final String xNoteY = "\u266BY";
              final String xY = "XY";

              final JFrame frame = new JFrame();
              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 noteIcon = new TextIcon(note);
              final TextIcon xNoteYIcon = new TextIcon(xNoteY);
              final TextIcon xYIcon = new TextIcon(xY);
              iconPanel.add(noteIcon);
              iconPanel.add(xNoteYIcon);
              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;
                  noteIcon.setScaleFactor(scaleFactor);
                  xNoteYIcon.setScaleFactor(scaleFactor);
                  xYIcon.setScaleFactor(scaleFactor);
                  scaleFactorLabel.setText("" + scaleFactor);
                  frame.invalidate();
              });

              SwingUtilities.invokeLater(() -> {
                  frame.setSize(500, 800);
                  frame.setVisible(true);
              });
          }

          private static class TextIcon extends JLabel {

              private final String text;

              public TextIcon(final String text) {
                  this.text = text;
                  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);

                  final float originalSize = g2d.getFont().getSize2D();
                  g2d.setFont(g2d.getFont().deriveFont(originalSize * scaleFactor));

                  final FontMetrics metrics = g2d.getFontMetrics();

                  // fill whole background
                  g2d.setColor(Color.GREEN);
                  g2d.fillRect(0, 0, width, height);

                  // draw string bounds as RED background
                  final Rectangle2D rect = metrics.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, metrics.getAscent());
                  g2d.dispose();
                  return new ImageIcon(image);
              }
          }
      }


      The demo code lets you change the size of the font, showing a scale factor. The RED background shows the bounding box for the string.

      The issue is illustrated in the attached screenshots and movie.

        1. large.png
          446 kB
          Hendrik Schreiber
        2. small.png
          437 kB
          Hendrik Schreiber
        3. unicode_scale.mov
          1.17 MB
          Hendrik Schreiber

            prr Philip Race
            hschreiber Hendrik Schreiber
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated: