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;