JDK-4650997 : java.awt.font.TextLayout.getBounds() returns a wrong result for rotated font
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 1.4.0
  • Priority: P2
  • Status: Resolved
  • Resolution: Fixed
  • OS: solaris_8
  • CPU: sparc
  • Submitted: 2002-03-11
  • Updated: 2013-11-01
  • Resolved: 2002-09-16
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
1.4.2 mantisFixed
Related Reports
Relates :  
Description
The java.awt.font.TextLayout.getBounds() method return a completely wrong
result for a rotated font (other than zero, -PI) with j2se-1.4.0. The same
code works with j2se-1.3.1

Versions:
SunOS suncity 5.8 Generic_108528-13 sun4u sparc SUNW,Sun-Blade-1000
 
% java -version
java version "1.3.1"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.1-b24)
Java HotSpot(TM) Client VM (build 1.3.1-b24, mixed mode)

suncity: /home/mkh
% /usr/j2sdk1.4.0/bin/java -version
java version "1.4.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0-b92)
Java HotSpot(TM) Client VM (build 1.4.0-b92, mixed mode)

Here are two examples, first j2se1.4, followed by j2se1.3.

% /usr/j2sdk1.4.0/bin/java FontFlip
Angle: -0.0
getAscent: 23.0
getAdvance: 195.0
getBaseline: 0
getBounds:
java.awt.geom.Rectangle2D$Float[x=2.1875,y=-17.46875,w=190.39062,h=22.515625]
getDescent: 7.0
getLeading: 1.0
getVisibleAdvance: 194.0
.
Angle: -0.7853981633974483
getAscent: 16.104034
getAdvance: 137.883
getBaseline: 0
getBounds:
java.awt.geom.Rectangle2D$Float[x=-6.800354,y=-1135.0493,w=142.96808,h=1135.0493]
getDescent: 5.0
getLeading: 1.0
getVisibleAdvance: 137.883
.


% /usr/j2se/bin/java FontFlip      
Angle: -0.0
getAscent: 21.0
getAdvance: 195.0
getBaseline: 0
getBounds: java.awt.geom.Rectangle2D$Float[x=2.0,y=-18.0,w=191.0,h=24.0]
getDescent: 6.0
getLeading: 1.0
getVisibleAdvance: 195.0
.
Angle: -0.7853981633974483
getAscent: 16.0
getAdvance: 146.0
getBaseline: 0
getBounds: java.awt.geom.Rectangle2D$Float[x=-7.0,y=-133.0,w=152.0,h=133.0]
getDescent: 4.0
getLeading: 1.0
getVisibleAdvance: 146.0
.

Here is the test program (a single file):

% cat FontFlip.java

import java.awt.*;
import java.awt.event.*;
import java.text.*;
import java.awt.font.*;
import java.awt.image.*;
import java.awt.geom.*;
import java.util.*;

import javax.swing.*;

public class FontFlip extends JComponent{
  int counter = 0;
  // Text we render
  String text = ".This is a STRINg.";

  // The following transform performs a flip along the x axis
  AffineTransform flip;

  // Rendering context parameters
  Font font = new Font("Default", Font.PLAIN, 24);
  Font flippedFont;

  public FontFlip(){
    setPreferredSize(new Dimension(700,700));
  }

  public void paint(Graphics _g){
    Graphics2D g = (Graphics2D)_g;
    Dimension d = getSize();
    counter++;
    int x = d.width/2;
    int y = d.height/2;
    FontRenderContext frc = g.getFontRenderContext();

    for (int i = 0; i < 8; i++) {
        double angle = -Math.PI/4.0*i;
        flip = AffineTransform.getRotateInstance(angle);
        flippedFont = font.deriveFont(flip);
        TextLayout tl = new TextLayout(text, flippedFont, frc);
        Rectangle2D bb = tl.getBounds();
        g.setPaint(Color.black);
        tl.draw(g, x, y);
        g.setPaint(Color.red);
        g.drawRect(x + (int) bb.getX(), y + (int) bb.getY(),
            (int) bb.getWidth(), (int) bb.getHeight());

        if (counter == 1) {
            System.out.println("Angle: "+angle);
            System.out.println("getAscent: "+tl.getAscent());
            System.out.println("getAdvance: "+tl.getAdvance());
            System.out.println("getBaseline: "+tl.getBaseline());
            System.out.println("getBounds: "+tl.getBounds());
            System.out.println("getDescent: "+tl.getDescent());
            System.out.println("getLeading: "+tl.getLeading());
            System.out.println("getVisibleAdvance: "+tl.getVisibleAdvance());
            System.out.println(".");
        } else if (i == 0) {
            System.out.println("Paint, counter="+counter);
        }
    }

  }

  public static void main(String args[]){
    new SnippetFrame(new FontFlip());
  }
}

class SnippetFrame extends JFrame{
  /**
   * @param cmp component to add to the frame's content pane
   */
  public SnippetFrame(JComponent cmp){
    super();

    getContentPane().add(cmp);

    // Set white background as a default
    getContentPane().setBackground(Color.white);

    // Pack to fit cmp's preferred size
    pack();

    // Add a listener to close the application when
    // user closes the frame.
    addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent evt){
        System.exit(0);
      }
    });

    setVisible(true);
  }
}



Notice also that the bounds are correct so long as the text is horizontal (even if it is rotated upside down). The text is correctly rotated -- it's the bounds that are wrong.

A slight modification to the example shows that 1.4 also suffers a huge 
performance degradation over 1.3, even though a feature of 1.4 is supposed to be 
improved graphics 2D performance. On the Windows2000 platform the attached 
example runs in about 3.3 seconds under 1.3.1, but it takes about 20 seconds 
under 1.4 (it does not matter whether or not the bounding box is drawn). 

Here is the example that shows the performance problem:

<<TL.java>>
import java.awt.*;
import java.awt.event.*;
import java.text.*;
import java.awt.font.*;
import java.awt.image.*;
import java.awt.geom.*;
import java.util.*;

import javax.swing.*;

public class TL extends JComponent{
  int number = 360;
  boolean print = false;
  boolean box = true;
  int counter = 0;
  // Text we render
  String text = ".This is a STRINg.";

  // The following transform performs a flip along the x axis
  AffineTransform flip;

  // Rendering context parameters
  Font font = new Font("Default", Font.PLAIN, 24);
  Font flippedFont;

  public TL(){
    setPreferredSize(new Dimension(700,700));
  }

  public void paint(Graphics _g){
    long start = System.currentTimeMillis();
    Graphics2D g = (Graphics2D)_g;
    Dimension d = getSize();
    counter++;
    int x = d.width/2;
    int y = d.height/2;
    FontRenderContext frc = g.getFontRenderContext();

    for (int i = 0; i < number; i++) {
	double angle = -Math.PI*2.0/number*i;
	flip = AffineTransform.getRotateInstance(angle);
	flippedFont = font.deriveFont(flip);
	TextLayout tl = new TextLayout(text, flippedFont, frc);
	Rectangle2D bb = tl.getBounds();
	g.setPaint(Color.black);
	tl.draw(g, x, y);
	g.setPaint(Color.red);
	if (box) {
	  g.drawRect(x + (int) bb.getX(), y + (int) bb.getY(),
	      (int) bb.getWidth(), (int) bb.getHeight());
	}

	if (print && counter == 1) {
	    System.out.println("Angle: "+angle);
	    System.out.println("getAscent: "+tl.getAscent());
	    System.out.println("getAdvance: "+tl.getAdvance());
	    System.out.println("getBaseline: "+tl.getBaseline());
	    System.out.println("getBounds: "+tl.getBounds());
	    System.out.println("getDescent: "+tl.getDescent());
	    System.out.println("getLeading: "+tl.getLeading());
	    System.out.println("getVisibleAdvance: "+tl.getVisibleAdvance());
	    System.out.println(".");
	} else if (print && i == 0) {
	    System.out.println("Paint, counter="+counter);
	}
    }
    System.out.println("Time="+(System.currentTimeMillis()-start));
  }

  public static void main(String args[]){
    new SnippetFrame(new TL());
  }
}

class SnippetFrame extends JFrame{
  /**
   * @param cmp component to add to the frame's content pane
   */
  public SnippetFrame(JComponent cmp){
    super();

    getContentPane().add(cmp);

    // Set white background as a default
    getContentPane().setBackground(Color.white);

    // Pack to fit cmp's preferred size
    pack();

    // Add a listener to close the application when
    // user closes the frame.
    addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent evt){
	System.exit(0);
      }
    });

    setVisible(true);
  }
}

Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: mantis mantis-b02 FIXED IN: mantis mantis-b02 INTEGRATED IN: mantis mantis-b02
14-06-2004

EVALUATION java.awt.font.* belongs to 2D. ###@###.### 2002-03-11 The change in behavior in TextLayout is a bug due to the introduction of a fast path for 'simple text' in JDK1.4. This was not tested against rotated fonts. The performance degradation is due to tweaking of the glyph and strike cache sizes to reduce memory footprint. The large number of different strikes (360) is overflowing the strike cache. If you run with 45 different rotations you'll see that JDK 1.4 is actually faster. We're looking into improving the strike cache behavior, we optimized for the more common case of regular text. ###@###.### 2002-03-21 I created a new bug, 4656562, for the performance issue. ###@###.### 2002-03-21
21-03-2002