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

Graphics2D.drawString produces different text widths, but the same height, when rotated 180 degrees

XMLWordPrintable

    • x86_64
    • linux

      FULL PRODUCT VERSION :
      openjdk version "1.8.0_151"


      ADDITIONAL OS VERSION INFORMATION :
      Linux ... 4.13.0-21-generic #24-Ubuntu SMP Mon Dec 18 17:29:16 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux


      EXTRA RELEVANT SYSTEM CONFIGURATION :
      Running System-76's ubuntu-based window system Pop-OS, but I do not believe that would make any difference.

      A DESCRIPTION OF THE PROBLEM :
      Import the following packages:

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

      1. create a buffered image and graphics context:
            BufferedImage bi =
                  new BufferedImage(800, 600, BufferedImage.TYPE_INT_RGB);
           Graphics2D g2d = bi.createGraphics();

      2. draw a string:
          String text = "example string\u00b9\u2074"
          g2d.drawString(text, 250, 250);

      3. rotate by 180 degrees about a point just below the end of the drawn string:
          
              FontMetrics fm = g2d.getFontMetrics();
              Rectangle2D bounds = fm.getStringBounds(text, g2d);
              double width1 = bounds.getWidth();
              double height1 = bounds.getHeight();
              double x = 250 + bounds.getWidth();
              double y = 250 + bounds.getHeight();
              g2d.rotate(Math.PI, x, y);
              g2d.drawString(text, (int)Math.round(x), (int)Math.round(y));

      The string will print with different widths depending on whether it is right side up or upside down. The heights, however, will be the same so the upside-down text will seem to be "squished" together.

      The effect depends on the font size. The larger the font size, the less noticeable the discrepancy.
      The same thing happens with vertical text. The text is squished when the coordinate (X or Y) for the end of the text is lower than for the start.

      The effect is also less if you print a postscript file, and even less for SVG (using the Apache Batik SVG library). For SVG files, I can measure the effect, but it is too small to discern by merely looking at an image.

      My guess is that the AWT code rounds up from user-space coordinates to the nearest pixel coordinate. This works when successive characters have increasing X or Y coordinates, but creates squished text if successive characters have decreasing X or Y coordinates. Unfortunately, the errors in both cases accumulate as the text gets longer.





      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      I've enclosed a t est program named FontBug.java
      To reproduce the problem, run the following comands:

             javac FontBug.java
             java FontBug

      An output file named FontBug.png will be produced. To see what happens if the font size is changed, add the font size as an argument. For example,

           java FontBug 64


      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      width ratio = 1.0

      ACTUAL -
      width ratio = 0.84

      (numbers will change with the font size, but will never be exactly 1.0. In addition, an output file FontBug.png will be produced that will allow one to see the problem visually.)

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      import java.io.*;
      import java.awt.*;
      import java.awt.geom.*;
      import java.awt.image.*;
      import javax.imageio.*;

      /*
       * Reproduces a bug in which the rotating text 180 degrees
       * changes its width (but not its height). The program
       * creates an output file named FontBug.png showing that
       * the text differs in size depending on direction: the
       * height of the font is the same but the spacing along
       * the baseline is different in the two cases.kl
       *
       * The ratio between widths gets closer to 1.0 as the
       * font size increases. Rotation by just 90 degrees clockwise
       * did not result in a change in width, so it appears that
       * the behavior depends on whether the X or Y coordinate for
       * the last character in a string is larger or smaller than the
       * coordinate for the first character.
       *
       * Combining these two observations suggests that round-off
       * errors in positioning each character are biased in one
       * direction: the ratio for the test string is roughly what
       * one would expect if one rounded up whenever a coordinate
       * did not match that for a pixel.
       *
       * If one uses Batik to generate an SVG file, the ratio is
       * very close to 1.0 in all cases, which is consistent with
       * the hypothesis as SVG images are supposed to be scalable.
       */
      public class FontBug {

          public static void main(String[] argv) throws Exception {

      BufferedImage bi =
      new BufferedImage(800, 600, BufferedImage.TYPE_INT_RGB);

      Graphics2D g2d = bi.createGraphics();

      if (argv.length > 0) {
      int fontsize = Integer.parseInt(argv[0]);
      g2d.setFont(new Font("SansSerif", Font.PLAIN, fontsize));
      }
      String text = "example string\u00b9\u2074";
      // Compare a string in the normal orientation running left to
      // right to one that is upside down running from right to
      // left. A width ratio of 1.0 means the two strings have the
      // same length. A ratio that is less than 1.0 means that the
      // upside-down string pointing right to left is shorter.
      FontMetrics fm = g2d.getFontMetrics();
      Rectangle2D bounds = fm.getStringBounds(text, g2d);
      g2d.drawString(text, 250, 250);
      double width1 = bounds.getWidth();
      double height1 = bounds.getHeight();
      double x = 250 + bounds.getWidth();
      double y = 250 + bounds.getHeight();
      g2d.rotate(Math.PI, x, y);
      g2d.drawString(text, (int)Math.round(x), (int)Math.round(y));
      fm = g2d.getFontMetrics();
      bounds = fm.getStringBounds(text, g2d);
      double width2 = bounds.getWidth();
      double height2 = bounds.getHeight();
      System.out.println("width ratio = " + (width2/width1));
      if (Math.abs(height1-height2) > 1.e-5) {
      System.out.format("height1 = %g, height2 = %g\n",
      height1, height2);
      }
      // Rotate 90 degrees and see if the dimensions for a string
      // whose end has a higher Y coordinate than its start has the
      // same width as the one in the previous case oriented so that
      // its end has a higher X coordinate than its start. The
      // strings pointing in the opposite direction are compared as
      // well.
      g2d = bi.createGraphics();
      if (argv.length > 0) {
      int fontsize = Integer.parseInt(argv[0]);
      g2d.setFont(new Font("SansSerif", Font.PLAIN, fontsize));
      }
      g2d.rotate(Math.PI/2.0, 250, 300);
      g2d.drawString(text, 250, 300);
      fm = g2d.getFontMetrics();
      bounds = fm.getStringBounds(text, g2d);
      double width3 =bounds.getWidth();
      double height3 = bounds.getHeight();
      g2d.rotate(Math.PI, 250, 300);
      fm = g2d.getFontMetrics();
      bounds = fm.getStringBounds(text, g2d);
      double width4 =bounds.getWidth();
      double height4 = bounds.getHeight();

      if (Math.abs(width1 - width3) > 1.e-5) {
      System.out.println("width1 and width3 differ");
      }

      if (Math.abs(width2 - width4) > 1.e-5) {
      System.out.println("width2 and width4 differ");
      }
      if (Math.abs(height3-height4) > 1.e-5) {
      System.out.format("height3 = %g, height4 = %g\n",
      height3, height4);
      }
      if (Math.abs(height1-height1) > 1.e-5) {
      System.out.format("height1 = %g, height3 = %g\n",
      height1, height3);
      }


      File output = new File("FontBug.png");
      ImageIO.write(bi, "png", output);
          }
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      I can document the problem. I've set the serverity oat "No Impact" because the text merely looks bad and my code measures sizes after adding the rotation, so I can align text properly. This may not be true for others, particularly if they are sensitive to how the fonts look.

        1. FontBug.java
          4 kB
        2. FontBug01.png
          2 kB
        3. FontBug02.png
          5 kB

            Unassigned Unassigned
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated: