United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
JDK-4650997 : java.awt.font.TextLayout.getBounds() returns a wrong result for rotated font

Details
Type:
Bug
Submit Date:
2002-03-11
Status:
Resolved
Updated Date:
2003-04-12
Project Name:
JDK
Resolved Date:
2002-09-16
Component:
client-libs
OS:
solaris_8
Sub-Component:
2d
CPU:
sparc
Priority:
P2
Resolution:
Fixed
Affected Versions:
1.4.0
Fixed Versions:
1.4.2 (mantis)

Related Reports
Relates:

Sub Tasks

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
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
                                     
2002-03-21
CONVERTED DATA

BugTraq+ Release Management Values

COMMIT TO FIX:
mantis
mantis-b02

FIXED IN:
mantis
mantis-b02

INTEGRATED IN:
mantis
mantis-b02


                                     
2004-06-14



Hardware and Software, Engineered to Work Together