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

[macos] Overlapping glyphs with harfbuzz + scaled AAT fonts

    Details

    • Type: Bug
    • Status: Open
    • Priority: P4
    • Resolution: Unresolved
    • Affects Version/s: 9, 10, 11
    • Fix Version/s: tbd
    • Component/s: client-libs
    • Labels:
    • Environment:

      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

    • Subcomponent:
      2d
    • OS:
      os_x

      Description

      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

        Attachments

          Issue Links

            Activity

              People

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

                Dates

                • Created:
                  Updated: