This implements a GlyphPainter that uses the TextLayout class for more
efficient and precise text rendering. This noticably improves rendering
performance for styled text and HTML.

2006-12-04  Roman Kennke  <[EMAIL PROTECTED]>

        * javax/swing/text/GlyphView.java
        (J2DGlyphPainter): New inner class.
        (checkPainter): For Java2D capable environments create
        a J2DGlyphPainter.

/Roman

Index: javax/swing/text/GlyphView.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/GlyphView.java,v
retrieving revision 1.27
diff -u -1 -5 -r1.27 GlyphView.java
--- javax/swing/text/GlyphView.java	20 Nov 2006 11:18:04 -0000	1.27
+++ javax/swing/text/GlyphView.java	4 Dec 2006 20:25:31 -0000
@@ -26,38 +26,45 @@
 As a special exception, the copyright holders of this library give you
 permission to link this library with independent modules to produce an
 executable, regardless of the license terms of these independent
 modules, and to copy and distribute the resulting executable under
 terms of your choice, provided that you also meet, for each linked
 independent module, the terms and conditions of the license of that
 module.  An independent module is a module which is not derived from
 or based on this library.  If you modify this library, you may extend
 this exception to your version of the library, but you are not
 obligated to do so.  If you do not wish to do so, delete this
 exception statement from your version. */
 
 
 package javax.swing.text;
 
+import gnu.classpath.SystemProperties;
+
 import java.awt.Color;
 import java.awt.Container;
 import java.awt.Font;
 import java.awt.FontMetrics;
 import java.awt.Graphics;
+import java.awt.Graphics2D;
 import java.awt.Rectangle;
 import java.awt.Shape;
 import java.awt.Toolkit;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextHitInfo;
+import java.awt.font.TextLayout;
+import java.awt.geom.Rectangle2D;
 
 import javax.swing.SwingConstants;
 import javax.swing.event.DocumentEvent;
 import javax.swing.text.Position.Bias;
 
 /**
  * Renders a run of styled text. This [EMAIL PROTECTED] View} subclass paints the
  * characters of the <code>Element</code> it is responsible for using
  * the style information from that <code>Element</code>.
  *
  * @author Roman Kennke ([EMAIL PROTECTED])
  */
 public class GlyphView extends View implements TabableView, Cloneable
 {
 
@@ -236,30 +243,182 @@
      * default behaviour is to return itself.
      *
      * @param v the glyph view for which to create a painter
      * @param p0 the start offset of the rendered area
      * @param p1 the end offset of the rendered area
      *
      * @return a painter that can be used to render the specified glyph view
      */
     public GlyphPainter getPainter(GlyphView v, int p0, int p1)
     {
       return this;
     }
   }
 
   /**
+   * A GlyphPainter implementation based on TextLayout. This should give
+   * better performance in Java2D environments.
+   */
+  private static class J2DGlyphPainter
+    extends GlyphPainter
+  {
+
+    /**
+     * The text layout.
+     */
+    TextLayout textLayout;
+
+    /**
+     * Creates a new J2DGlyphPainter.
+     *
+     * @param str the string
+     * @param font the font
+     * @param frc the font render context
+     */
+    J2DGlyphPainter(String str, Font font, FontRenderContext frc)
+    {
+      textLayout = new TextLayout(str, font, frc);
+    }
+
+    /**
+     * Returns null so that GlyphView.checkPainter() creates a new instance.
+     */
+    public GlyphPainter getPainter(GlyphView v, int p0, int p1)
+    {
+      return null;
+    }
+
+    /**
+     * Delegates to the text layout.
+     */
+    public float getAscent(GlyphView v)
+    {
+      return textLayout.getAscent();
+    }
+
+    /**
+     * Delegates to the text layout.
+     */
+    public int getBoundedPosition(GlyphView v, int p0, float x, float len)
+    {
+      int pos;
+      TextHitInfo hit = textLayout.hitTestChar(len, 0);
+      if (hit.getCharIndex() == -1 && ! textLayout.isLeftToRight())
+        pos = v.getEndOffset();
+      else
+        {
+          pos = hit.isLeadingEdge() ? hit.getInsertionIndex()
+                                    : hit.getInsertionIndex() - 1;
+          pos += v.getStartOffset();
+        }
+      return pos;
+    }
+
+    /**
+     * Delegates to the text layout.
+     */
+    public float getDescent(GlyphView v)
+    {
+      return textLayout.getDescent();
+    }
+
+    /**
+     * Delegates to the text layout.
+     */
+    public float getHeight(GlyphView view)
+    {
+      return textLayout.getAscent() + textLayout.getDescent()
+             + textLayout.getLeading();
+    }
+
+    /**
+     * Delegates to the text layout.
+     */
+    public float getSpan(GlyphView v, int p0, int p1, TabExpander te, float x)
+    {
+      float span;
+      if (p0 == v.getStartOffset() && p1 == v.getEndOffset())
+        span = textLayout.getAdvance();
+      else
+        {
+          int start = v.getStartOffset();
+          int i0 = p0 - start;
+          int i1 = p1 - start;
+          TextHitInfo hit0 = TextHitInfo.afterOffset(i0);
+          TextHitInfo hit1 = TextHitInfo.afterOffset(i1);
+          float x0 = textLayout.getCaretInfo(hit0)[0];
+          float x1 = textLayout.getCaretInfo(hit1)[0];
+          span = Math.abs(x1 - x0);
+        }
+      return span;
+    }
+
+    /**
+     * Delegates to the text layout.
+     */
+    public Shape modelToView(GlyphView v, int pos, Bias b, Shape a)
+      throws BadLocationException
+    {
+      int offs = pos - v.getStartOffset();
+      // Create copy here to protect original shape.
+      Rectangle2D bounds = a.getBounds2D();
+      TextHitInfo hit =
+        b == Position.Bias.Forward ? TextHitInfo.afterOffset(offs)
+                                   : TextHitInfo.beforeOffset(offs);
+      float[] loc = textLayout.getCaretInfo(hit);
+      bounds.setRect(bounds.getX() + loc[0], bounds.getY(), 1,
+                     bounds.getHeight());
+      return bounds;
+    }
+
+    /**
+     * Delegates to the text layout.
+     */
+    public void paint(GlyphView view, Graphics g, Shape a, int p0, int p1)
+    {
+      // Can't paint this with plain graphics.
+      if (g instanceof Graphics2D)
+        {
+          Graphics2D g2d = (Graphics2D) g;
+          Rectangle2D b = a instanceof Rectangle2D ? (Rectangle2D) a
+                                                   : a.getBounds2D();
+          float x = (float) b.getX();
+          float y = (float) b.getY() + textLayout.getAscent()
+                    + textLayout.getLeading();
+          // TODO: Try if clipping makes things faster for narrow views.
+          textLayout.draw(g2d, x, y);
+        }
+    }
+
+    /**
+     * Delegates to the text layout.
+     */
+    public int viewToModel(GlyphView v, float x, float y, Shape a,
+                           Bias[] biasRet)
+    {
+      Rectangle2D bounds = a instanceof Rectangle2D ? (Rectangle2D) a
+                                                    : a.getBounds2D();
+      TextHitInfo hit = textLayout.hitTestChar(x - (float) bounds.getX(), 0);
+      int pos = hit.getInsertionIndex();
+      biasRet[0] = hit.isLeadingEdge() ? Position.Bias.Forward
+                                       : Position.Bias.Backward;
+      return pos + v.getStartOffset();
+    }
+    
+  }
+
+  /**
    * The default <code>GlyphPainter</code> used in <code>GlyphView</code>.
    */
   static class DefaultGlyphPainter extends GlyphPainter
   {
     FontMetrics fontMetrics;
 
     /**
      * Returns the full height of the rendered text.
      *
      * @return the full height of the rendered text
      */
     public float getHeight(GlyphView view)
     {
       updateFontMetrics(view);
       float height = fontMetrics.getHeight();
@@ -520,31 +679,45 @@
    *
    * @param painter the glyph painter to be used for this glyph view
    */
   public void setGlyphPainter(GlyphPainter painter)
   {
     glyphPainter = painter;
   }
 
   /**
    * Checks if a <code>GlyphPainer</code> is installed. If this is not the
    * case, a default painter is installed.
    */
   protected void checkPainter()
   {
     if (glyphPainter == null)
-      glyphPainter = new DefaultGlyphPainter();
+      {
+        if ("true".equals(
+                 SystemProperties.getProperty("gnu.javax.swing.noGraphics2D")))
+          {
+            glyphPainter = new DefaultGlyphPainter();
+          }
+        else
+          {
+            Segment s = getText(getStartOffset(), getEndOffset());
+            glyphPainter = new J2DGlyphPainter(s.toString(), getFont(),
+                                               new FontRenderContext(null,
+                                                                     false,
+                                                                     false));
+          }
+      }
   }
 
   /**
    * Renders the <code>Element</code> that is associated with this
    * <code>View</code>.
    *
    * @param g the <code>Graphics</code> context to render to
    * @param a the allocated region for the <code>Element</code>
    */
   public void paint(Graphics g, Shape a)
   {
     checkPainter();
     int p0 = getStartOffset();
     int p1 = getEndOffset();
 

Reply via email to