This implemented TextLayout.getCaretInfo(). The impl is backed by the
tests in the Harmony testsuite.

2006-11-27  Roman Kennke  <[EMAIL PROTECTED]>

        * java/awt/font/TextLayout.java
        (Run.font): New field.
        (Run.location): New field.
        (Run.Run): Initialize font.
        (font): Removed field. This is moved into Run as the actual font
        is something run-specific.
        (TextLayout(String,Font,FontRenderContext)): Set font on the
        single runs. Layout the runs here.
        (TextLayout(TextLayout,int,int)): Copy over the run fonts.
        (findRunAtIndex): New helper method.
        (getCaretInfo): Implemented.
        (layoutRuns): New helper method.
        (toString): Don't put font in output string.

/Roman

Index: java/awt/font/TextLayout.java
===================================================================
RCS file: /cvsroot/classpath/classpath/java/awt/font/TextLayout.java,v
retrieving revision 1.19
diff -u -1 -5 -r1.19 TextLayout.java
--- java/awt/font/TextLayout.java	22 Nov 2006 16:05:38 -0000	1.19
+++ java/awt/font/TextLayout.java	27 Nov 2006 11:16:24 -0000
@@ -57,72 +57,82 @@
  * @author Sven de Marothy
  */
 public final class TextLayout implements Cloneable
 {
   /**
    * Holds the layout data that belongs to one run of characters.
    */
   private class Run
   {
     /**
      * The actual glyph vector.
      */
     GlyphVector glyphVector;
 
     /**
+     * The font for this text run.
+     */
+    Font font;
+
+    /**
      * The start of the run.
      */
     int runStart;
 
     /**
      * The end of the run.
      */
     int runEnd;
 
     /**
+     * The layout location of the beginning of the run.
+     */
+    float location;
+
+    /**
      * Initializes the Run instance.
      *
      * @param gv the glyph vector
      * @param start the start index of the run
      * @param end the end index of the run
      */
-    Run(GlyphVector gv, int start, int end)
+    Run(GlyphVector gv, Font f, int start, int end)
     {
       glyphVector = gv;
+      font = f;
       runStart = start;
       runEnd = end;
     }
 
     /**
      * Returns <code>true</code> when this run is left to right,
      * <code>false</code> otherwise.
      *
      * @return <code>true</code> when this run is left to right,
      *         <code>false</code> otherwise
      */
     boolean isLeftToRight()
     {
       return (glyphVector.getLayoutFlags() & GlyphVector.FLAG_RUN_RTL) == 0;
     }
   }
 
   /**
    * The laid out character runs.
    */
   private Run[] runs;
 
-  private Font font;
   private FontRenderContext frc;
   private char[] string;
   private int offset;
   private int length;
   private Rectangle2D boundsCache;
   private LineMetrics lm;
 
   /**
    * The total advance of this text layout. This is cache for maximum
    * performance.
    */
   private float totalAdvance = -1F;
   
   /**
    * The cached natural bounds.
@@ -167,133 +177,133 @@
    * The cached hashCode.
    */
   private int hash;
 
   /**
    * The default caret policy.
    */
   public static final TextLayout.CaretPolicy DEFAULT_CARET_POLICY =
     new CaretPolicy();
 
   /**
    * Constructs a TextLayout.
    */
   public TextLayout (String str, Font font, FontRenderContext frc) 
   {
-    this.font = font;
     this.frc = frc;
     string = str.toCharArray();
     offset = 0;
     length = this.string.length;
     lm = font.getLineMetrics(this.string, offset, length, frc);
 
     // Get base direction and whitespace info
     getStringProperties();
 
     if (Bidi.requiresBidi(string, offset, offset + length))
       {
 	bidi = new Bidi(str, leftToRight ? Bidi.DIRECTION_LEFT_TO_RIGHT
                                          : Bidi.DIRECTION_RIGHT_TO_LEFT );
 	int rc = bidi.getRunCount();
 	byte[] table = new byte[ rc ];
 	for(int i = 0; i < table.length; i++)
 	  table[i] = (byte)bidi.getRunLevel(i);
 
         runs = new Run[rc];
 	for(int i = 0; i < rc; i++)
 	  {
 	    int start = bidi.getRunStart(i);
 	    int end = bidi.getRunLimit(i);
 	    if(start != end) // no empty runs.
 	      {
 	        GlyphVector gv = font.layoutGlyphVector(frc,
                                                         string, start, end,
                            ((table[i] & 1) == 0) ? Font.LAYOUT_LEFT_TO_RIGHT
                                                  : Font.LAYOUT_RIGHT_TO_LEFT );
-                runs[i] = new Run(gv, start, end);
+                runs[i] = new Run(gv, font, start, end);
               }
 	  }
 	Bidi.reorderVisually( table, 0, runs, 0, runs.length );
         // Clean up null runs.
         ArrayList cleaned = new ArrayList(rc);
         for (int i = 0; i < rc; i++)
           {
             if (runs[i] != null)
               cleaned.add(runs[i]);
           }
         runs = new Run[cleaned.size()];
         runs = (Run[]) cleaned.toArray(runs);
       }
     else
       {
         GlyphVector gv = font.layoutGlyphVector( frc, string, offset, length,
                                      leftToRight ? Font.LAYOUT_LEFT_TO_RIGHT
                                                  : Font.LAYOUT_RIGHT_TO_LEFT );
-        Run run = new Run(gv, 0, length);
+        Run run = new Run(gv, font, 0, length);
 	runs = new Run[]{ run };
       }
     setCharIndices();
     setupMappings();
+    layoutRuns();
   }
 
   public TextLayout (String string, Map attributes, FontRenderContext frc)  
   {
     this( string, new Font( attributes ), frc );
   }
 
   public TextLayout (AttributedCharacterIterator text, FontRenderContext frc)
   {
     // FIXME: Very rudimentary.
     this(getText(text), getFont(text), frc);
   }
 
   /**
    * Package-private constructor to make a textlayout from an existing one.
    * This is used by TextMeasurer for returning sub-layouts, and it 
    * saves a lot of time in not having to relayout the text.
    */
   TextLayout(TextLayout t, int startIndex, int endIndex)
   {
-    font = t.font;
     frc = t.frc;
     boundsCache = null;
     lm = t.lm;
     leftToRight = t.leftToRight;
 
     if( endIndex > t.getCharacterCount() )
       endIndex = t.getCharacterCount();
     string = t.string;
     offset = startIndex + offset;
     length = endIndex - startIndex;
 
     int startingRun = t.charIndices[startIndex][0];
     int nRuns = 1 + t.charIndices[endIndex - 1][0] - startingRun;
 
     runs = new Run[nRuns];
     for( int i = 0; i < nRuns; i++ )
       {
         Run run = t.runs[i + startingRun];
 	GlyphVector gv = run.glyphVector;
+        Font font = run.font;
 	// Copy only the relevant parts of the first and last runs.
 	int beginGlyphIndex = (i > 0) ? 0 : t.charIndices[startIndex][1];
 	int numEntries = ( i < nRuns - 1) ? gv.getNumGlyphs() : 
 	  1 + t.charIndices[endIndex - 1][1] - beginGlyphIndex;
 	
 	int[] codes = gv.getGlyphCodes(beginGlyphIndex, numEntries, null);
         gv = font.createGlyphVector(frc, codes);
-        runs[i] = new Run(gv, run.runStart - startIndex,
-                         run.runEnd - startIndex);
+        runs[i] = new Run(gv, font, run.runStart - startIndex,
+                          run.runEnd - startIndex);
       }
     runs[nRuns - 1].runEnd = endIndex - 1;
 
     setCharIndices();
     setupMappings();
     determineWhiteSpace();
   }
 
   private void setCharIndices()
   {
     charIndices = new int[ getCharacterCount() ][2];
     int i = 0;
     int currentChar = 0;
     for(int run = 0; run < runs.length; run++)
       {
@@ -532,33 +542,74 @@
   }
 
   public Rectangle2D getBounds()
   {
     if( boundsCache == null )
       boundsCache = getOutline(new AffineTransform()).getBounds();
     return boundsCache;
   }
 
   public float[] getCaretInfo (TextHitInfo hit)
   {
     return getCaretInfo(hit, getNaturalBounds());
   }
 
   public float[] getCaretInfo (TextHitInfo hit, Rectangle2D bounds)
-    throws NotImplementedException
   {
-    throw new Error ("not implemented");
+    float[] info = new float[2];
+    int index = hit.getCharIndex();
+    boolean leading = hit.isLeadingEdge();
+    // For the boundary cases we return the boundary runs.
+    Run run;
+    
+    if (index >= length)
+      {
+        info[0] = getAdvance();
+        info[1] = 0;
+      }
+    else
+      {
+        if (index < 0)
+          {
+            run = runs[0];
+            index = 0;
+            leading = true;
+          }
+        else
+          run = findRunAtIndex(index);
+
+        int glyphIndex = index - run.runStart;
+        Shape glyphBounds = run.glyphVector.getGlyphLogicalBounds(glyphIndex);
+        Rectangle2D glyphRect = glyphBounds.getBounds2D();
+        if (isVertical())
+          {
+            if (leading)
+              info[0] = (float) glyphRect.getMinY();
+            else
+              info[0] = (float) glyphRect.getMaxY();
+          }
+        else
+          {
+            if (leading)
+              info[0] = (float) glyphRect.getMinX();
+            else
+              info[0] = (float) glyphRect.getMaxX();
+          }
+        info[0] += run.location;
+        info[1] = run.font.getItalicAngle();
+      }
+    return info;
   }
 
   public Shape getCaretShape (TextHitInfo hit)
   {
     return getCaretShape( hit, getBounds() );
   }
 
   public Shape getCaretShape (TextHitInfo hit, Rectangle2D bounds)
     throws NotImplementedException
   {
     throw new Error ("not implemented");
   }
 
   public Shape[] getCaretShapes (int offset)
   {
@@ -1098,31 +1149,31 @@
   public int hashCode ()
   {
     // This is implemented in sync to equals().
     if (hash == 0 && runs.length > 0)
       {
         hash = runs.length;
         for (int i = 0; i < runs.length; i++)
           hash ^= runs[i].glyphVector.hashCode();
       }
     return hash;
   }
 
   public String toString ()
   {
     return "TextLayout [string:"+ new String(string, offset, length)
-    +", Font:"+font+" Rendercontext:"+
+    +" Rendercontext:"+
       frc+"]";
   }
 
   /**
    * Returns the natural bounds of that text layout. This is made up
    * of the ascent plus descent and the text advance.
    *
    * @return the natural bounds of that text layout
    */
   private Rectangle2D getNaturalBounds()
   {
     if (naturalBounds == null)
       naturalBounds = new Rectangle2D.Float(0.0F, -getAscent(), getAdvance(),
                                             getAscent() + getDescent());
     return naturalBounds;
@@ -1169,30 +1220,66 @@
         int logical = visualToLogical[index];
         boolean leading = isCharacterLTR(logical); // LTR.
         hit = leading ? TextHitInfo.leading(logical)
                       : TextHitInfo.trailing(logical);
       }
     return hit;
   }
 
   private boolean isCharacterLTR(int index)
   {
     byte level = getCharacterLevel(index);
     return (level & 1) == 0;
   }
 
   /**
+   * Finds the run that holds the specified (logical) character index. This
+   * returns <code>null</code> when the index is not inside the range.
+   *
+   * @param index the index of the character to find
+   *
+   * @return the run that holds the specified character
+   */
+  private Run findRunAtIndex(int index)
+  {
+    Run found = null;
+    // TODO: Can we do better than linear searching here?
+    for (int i = 0; i < runs.length && found == null; i++)
+      {
+        Run run = runs[i];
+        if (run.runStart <= index && run.runEnd > index)
+          found = run;
+      }
+    return found;
+  }
+
+  /**
+   * Computes the layout locations for each run.
+   */
+  private void layoutRuns()
+  {
+    float loc = 0.0F;
+    float lastWidth = 0.0F;
+    for (int i = 0; i < runs.length - 1; i++)
+      {
+        runs[i].location = loc;
+        Rectangle2D bounds = runs[i].glyphVector.getLogicalBounds();
+        loc += isVertical() ? bounds.getHeight() : bounds.getWidth();
+      }
+  }
+
+  /**
    * Inner class describing a caret policy
    */
   public static class CaretPolicy
   {
     public CaretPolicy()
     {
     }
 
     public TextHitInfo getStrongCaret(TextHitInfo hit1,
 				      TextHitInfo hit2,
 				      TextLayout layout)
     {
       byte l1 = layout.getCharacterLevel(hit1.getCharIndex());
       byte l2 = layout.getCharacterLevel(hit2.getCharIndex());
       TextHitInfo strong;

Reply via email to