JDK-8212743 : Graphics.drawString() renders certain unicode chars incorrectly, when the font is scaled
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 9,10,11
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: os_x
  • Submitted: 2018-10-22
  • Updated: 2019-02-25
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Relates :  
Description
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.
Comments
Thanks for letting me know.
25-02-2019

[~hschreiber] We do not have resources for this bug fix now, but we will try to address it later
22-02-2019

I have noticed that the target version has been moved from 13 to TBD. May I ask why?
22-02-2019

NOTE: This P4 bug will be retargeted to "13" in case this issue is still Unresolved by Mon, Dec 10 3am Pacific Time Mon Dec 10 (Pre Integration Testing deadline) to meet JDK 12 RDP1 (Dec 13th) milestone [1]. No more P4-P5 in JDK 12 beyond RDP1 in accordance with RDP1 definition [2]. [1] http://mail.openjdk.java.net/pipermail/jdk-dev/2018-September/001984.html [2] http://openjdk.java.net/jeps/3
04-12-2018

This differs from JDK-8212744 insofar as it occurs on JDK 9 whether ICU or harfbuzz is used. However the other workaround - setting Arial as the font instead of the default font - seems to solve it, so it is also likely an AAT code path issue.
22-10-2018