Merged gcj-eclipse branch to trunk.
[official-gcc.git] / libjava / classpath / java / awt / font / TextLayout.java
blobdc0e537eba95b006dddf0c857ec2d8587f47cc63
1 /* TextLayout.java --
2 Copyright (C) 2006 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package java.awt.font;
41 import java.awt.Font;
42 import java.awt.Graphics2D;
43 import java.awt.Shape;
44 import java.awt.geom.AffineTransform;
45 import java.awt.geom.Line2D;
46 import java.awt.geom.Rectangle2D;
47 import java.awt.geom.GeneralPath;
48 import java.awt.geom.Point2D;
49 import java.text.CharacterIterator;
50 import java.text.AttributedCharacterIterator;
51 import java.text.Bidi;
52 import java.util.ArrayList;
53 import java.util.Map;
55 /**
56 * @author Sven de Marothy
58 public final class TextLayout implements Cloneable
60 /**
61 * Holds the layout data that belongs to one run of characters.
63 private class Run
65 /**
66 * The actual glyph vector.
68 GlyphVector glyphVector;
70 /**
71 * The font for this text run.
73 Font font;
75 /**
76 * The start of the run.
78 int runStart;
80 /**
81 * The end of the run.
83 int runEnd;
85 /**
86 * The layout location of the beginning of the run.
88 float location;
90 /**
91 * Initializes the Run instance.
93 * @param gv the glyph vector
94 * @param start the start index of the run
95 * @param end the end index of the run
97 Run(GlyphVector gv, Font f, int start, int end)
99 glyphVector = gv;
100 font = f;
101 runStart = start;
102 runEnd = end;
106 * Returns <code>true</code> when this run is left to right,
107 * <code>false</code> otherwise.
109 * @return <code>true</code> when this run is left to right,
110 * <code>false</code> otherwise
112 boolean isLeftToRight()
114 return (glyphVector.getLayoutFlags() & GlyphVector.FLAG_RUN_RTL) == 0;
119 * The laid out character runs.
121 private Run[] runs;
123 private FontRenderContext frc;
124 private char[] string;
125 private int offset;
126 private int length;
127 private Rectangle2D boundsCache;
128 private LineMetrics lm;
131 * The total advance of this text layout. This is cache for maximum
132 * performance.
134 private float totalAdvance = -1F;
137 * The cached natural bounds.
139 private Rectangle2D naturalBounds;
142 * Character indices.
143 * Fixt index is the glyphvector, second index is the (first) glyph.
145 private int[][] charIndices;
148 * Base directionality, determined from the first char.
150 private boolean leftToRight;
153 * Whether this layout contains whitespace or not.
155 private boolean hasWhitespace = false;
158 * The {@link Bidi} object that is used for reordering and by
159 * {@link #getCharacterLevel(int)}.
161 private Bidi bidi;
164 * Mpas the logical position of each individual character in the original
165 * string to its visual position.
167 private int[] logicalToVisual;
170 * Maps visual positions of a character to its logical position
171 * in the original string.
173 private int[] visualToLogical;
176 * The cached hashCode.
178 private int hash;
181 * The default caret policy.
183 public static final TextLayout.CaretPolicy DEFAULT_CARET_POLICY =
184 new CaretPolicy();
187 * Constructs a TextLayout.
189 public TextLayout (String str, Font font, FontRenderContext frc)
191 this.frc = frc;
192 string = str.toCharArray();
193 offset = 0;
194 length = this.string.length;
195 lm = font.getLineMetrics(this.string, offset, length, frc);
197 // Get base direction and whitespace info
198 getStringProperties();
200 if (Bidi.requiresBidi(string, offset, offset + length))
202 bidi = new Bidi(str, leftToRight ? Bidi.DIRECTION_LEFT_TO_RIGHT
203 : Bidi.DIRECTION_RIGHT_TO_LEFT );
204 int rc = bidi.getRunCount();
205 byte[] table = new byte[ rc ];
206 for(int i = 0; i < table.length; i++)
207 table[i] = (byte)bidi.getRunLevel(i);
209 runs = new Run[rc];
210 for(int i = 0; i < rc; i++)
212 int start = bidi.getRunStart(i);
213 int end = bidi.getRunLimit(i);
214 if(start != end) // no empty runs.
216 GlyphVector gv = font.layoutGlyphVector(frc,
217 string, start, end,
218 ((table[i] & 1) == 0) ? Font.LAYOUT_LEFT_TO_RIGHT
219 : Font.LAYOUT_RIGHT_TO_LEFT );
220 runs[i] = new Run(gv, font, start, end);
223 Bidi.reorderVisually( table, 0, runs, 0, runs.length );
224 // Clean up null runs.
225 ArrayList cleaned = new ArrayList(rc);
226 for (int i = 0; i < rc; i++)
228 if (runs[i] != null)
229 cleaned.add(runs[i]);
231 runs = new Run[cleaned.size()];
232 runs = (Run[]) cleaned.toArray(runs);
234 else
236 GlyphVector gv = font.layoutGlyphVector( frc, string, offset, length,
237 leftToRight ? Font.LAYOUT_LEFT_TO_RIGHT
238 : Font.LAYOUT_RIGHT_TO_LEFT );
239 Run run = new Run(gv, font, 0, length);
240 runs = new Run[]{ run };
242 setCharIndices();
243 setupMappings();
244 layoutRuns();
247 public TextLayout (String string,
248 Map<? extends AttributedCharacterIterator.Attribute, ?> attributes,
249 FontRenderContext frc)
251 this( string, new Font( attributes ), frc );
254 public TextLayout (AttributedCharacterIterator text, FontRenderContext frc)
256 // FIXME: Very rudimentary.
257 this(getText(text), getFont(text), frc);
261 * Package-private constructor to make a textlayout from an existing one.
262 * This is used by TextMeasurer for returning sub-layouts, and it
263 * saves a lot of time in not having to relayout the text.
265 TextLayout(TextLayout t, int startIndex, int endIndex)
267 frc = t.frc;
268 boundsCache = null;
269 lm = t.lm;
270 leftToRight = t.leftToRight;
272 if( endIndex > t.getCharacterCount() )
273 endIndex = t.getCharacterCount();
274 string = t.string;
275 offset = startIndex + offset;
276 length = endIndex - startIndex;
278 int startingRun = t.charIndices[startIndex][0];
279 int nRuns = 1 + t.charIndices[endIndex - 1][0] - startingRun;
281 runs = new Run[nRuns];
282 for( int i = 0; i < nRuns; i++ )
284 Run run = t.runs[i + startingRun];
285 GlyphVector gv = run.glyphVector;
286 Font font = run.font;
287 // Copy only the relevant parts of the first and last runs.
288 int beginGlyphIndex = (i > 0) ? 0 : t.charIndices[startIndex][1];
289 int numEntries = ( i < nRuns - 1) ? gv.getNumGlyphs() :
290 1 + t.charIndices[endIndex - 1][1] - beginGlyphIndex;
292 int[] codes = gv.getGlyphCodes(beginGlyphIndex, numEntries, null);
293 gv = font.createGlyphVector(frc, codes);
294 runs[i] = new Run(gv, font, run.runStart - startIndex,
295 run.runEnd - startIndex);
297 runs[nRuns - 1].runEnd = endIndex - 1;
299 setCharIndices();
300 setupMappings();
301 determineWhiteSpace();
302 layoutRuns();
305 private void setCharIndices()
307 charIndices = new int[ getCharacterCount() ][2];
308 int i = 0;
309 int currentChar = 0;
310 for(int run = 0; run < runs.length; run++)
312 currentChar = -1;
313 Run current = runs[run];
314 GlyphVector gv = current.glyphVector;
315 for( int gi = 0; gi < gv.getNumGlyphs(); gi++)
317 if( gv.getGlyphCharIndex( gi ) != currentChar )
319 charIndices[ i ][0] = run;
320 charIndices[ i ][1] = gi;
321 currentChar = gv.getGlyphCharIndex( gi );
322 i++;
329 * Initializes the logicalToVisual and visualToLogial maps.
331 private void setupMappings()
333 int numChars = getCharacterCount();
334 logicalToVisual = new int[numChars];
335 visualToLogical = new int[numChars];
336 int lIndex = 0;
337 int vIndex = 0;
338 // We scan the runs in visual order and set the mappings accordingly.
339 for (int i = 0; i < runs.length; i++)
341 Run run = runs[i];
342 if (run.isLeftToRight())
344 for (lIndex = run.runStart; lIndex < run.runEnd; lIndex++)
346 logicalToVisual[lIndex] = vIndex;
347 visualToLogical[vIndex] = lIndex;
348 vIndex++;
351 else
353 for (lIndex = run.runEnd - 1; lIndex >= run.runStart; lIndex--)
355 logicalToVisual[lIndex] = vIndex;
356 visualToLogical[vIndex] = lIndex;
357 vIndex++;
363 private static String getText(AttributedCharacterIterator iter)
365 StringBuffer sb = new StringBuffer();
366 int idx = iter.getIndex();
367 for(char c = iter.first(); c != CharacterIterator.DONE; c = iter.next())
368 sb.append(c);
369 iter.setIndex( idx );
370 return sb.toString();
373 private static Font getFont(AttributedCharacterIterator iter)
375 Font f = (Font)iter.getAttribute(TextAttribute.FONT);
376 if( f == null )
378 int size;
379 Float i = (Float)iter.getAttribute(TextAttribute.SIZE);
380 if( i != null )
381 size = (int)i.floatValue();
382 else
383 size = 14;
384 f = new Font("Dialog", Font.PLAIN, size );
386 return f;
390 * Scan the character run for the first strongly directional character,
391 * which in turn defines the base directionality of the whole layout.
393 private void getStringProperties()
395 boolean gotDirection = false;
396 int i = offset;
397 int endOffs = offset + length;
398 leftToRight = true;
399 while( i < endOffs && !gotDirection )
400 switch( Character.getDirectionality(string[i++]) )
402 case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
403 case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
404 case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
405 gotDirection = true;
406 break;
408 case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
409 case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
410 case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
411 case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
412 leftToRight = false;
413 gotDirection = true;
414 break;
416 determineWhiteSpace();
419 private void determineWhiteSpace()
421 // Determine if there's whitespace in the thing.
422 // Ignore trailing chars.
423 int i = offset + length - 1;
424 hasWhitespace = false;
425 while( i >= offset && Character.isWhitespace( string[i] ) )
426 i--;
427 // Check the remaining chars
428 while( i >= offset )
429 if( Character.isWhitespace( string[i--] ) )
430 hasWhitespace = true;
433 protected Object clone ()
435 return new TextLayout( this, 0, length);
438 public void draw (Graphics2D g2, float x, float y)
440 for(int i = 0; i < runs.length; i++)
442 Run run = runs[i];
443 GlyphVector gv = run.glyphVector;
444 g2.drawGlyphVector(gv, x, y);
445 Rectangle2D r = gv.getLogicalBounds();
446 x += r.getWidth();
450 public boolean equals (Object obj)
452 if( !( obj instanceof TextLayout) )
453 return false;
455 return equals( (TextLayout) obj );
458 public boolean equals (TextLayout tl)
460 if( runs.length != tl.runs.length )
461 return false;
462 // Compare all glyph vectors.
463 for( int i = 0; i < runs.length; i++ )
464 if( !runs[i].equals( tl.runs[i] ) )
465 return false;
466 return true;
469 public float getAdvance ()
471 if (totalAdvance == -1F)
473 totalAdvance = 0f;
474 for(int i = 0; i < runs.length; i++)
476 Run run = runs[i];
477 GlyphVector gv = run.glyphVector;
478 totalAdvance += gv.getLogicalBounds().getWidth();
481 return totalAdvance;
484 public float getAscent ()
486 return lm.getAscent();
489 public byte getBaseline ()
491 return (byte)lm.getBaselineIndex();
494 public float[] getBaselineOffsets ()
496 return lm.getBaselineOffsets();
499 public Shape getBlackBoxBounds (int firstEndpoint, int secondEndpoint)
501 if( secondEndpoint - firstEndpoint <= 0 )
502 return new Rectangle2D.Float(); // Hmm?
504 if( firstEndpoint < 0 || secondEndpoint > getCharacterCount())
505 return new Rectangle2D.Float();
507 GeneralPath gp = new GeneralPath();
509 int ri = charIndices[ firstEndpoint ][0];
510 int gi = charIndices[ firstEndpoint ][1];
512 double advance = 0;
514 for( int i = 0; i < ri; i++ )
516 Run run = runs[i];
517 GlyphVector gv = run.glyphVector;
518 advance += gv.getLogicalBounds().getWidth();
521 for( int i = ri; i <= charIndices[ secondEndpoint - 1 ][0]; i++ )
523 Run run = runs[i];
524 GlyphVector gv = run.glyphVector;
525 int dg;
526 if( i == charIndices[ secondEndpoint - 1 ][0] )
527 dg = charIndices[ secondEndpoint - 1][1];
528 else
529 dg = gv.getNumGlyphs() - 1;
531 for( int j = 0; j <= dg; j++ )
533 Rectangle2D r2 = (gv.getGlyphVisualBounds( j )).
534 getBounds2D();
535 Point2D p = gv.getGlyphPosition( j );
536 r2.setRect( advance + r2.getX(), r2.getY(),
537 r2.getWidth(), r2.getHeight() );
538 gp.append(r2, false);
541 advance += gv.getLogicalBounds().getWidth();
543 return gp;
546 public Rectangle2D getBounds()
548 if( boundsCache == null )
549 boundsCache = getOutline(new AffineTransform()).getBounds();
550 return boundsCache;
553 public float[] getCaretInfo (TextHitInfo hit)
555 return getCaretInfo(hit, getNaturalBounds());
558 public float[] getCaretInfo (TextHitInfo hit, Rectangle2D bounds)
560 float[] info = new float[2];
561 int index = hit.getCharIndex();
562 boolean leading = hit.isLeadingEdge();
563 // For the boundary cases we return the boundary runs.
564 Run run;
566 if (index >= length)
568 info[0] = getAdvance();
569 info[1] = 0;
571 else
573 if (index < 0)
575 run = runs[0];
576 index = 0;
577 leading = true;
579 else
580 run = findRunAtIndex(index);
582 int glyphIndex = index - run.runStart;
583 Shape glyphBounds = run.glyphVector.getGlyphLogicalBounds(glyphIndex);
584 Rectangle2D glyphRect = glyphBounds.getBounds2D();
585 if (isVertical())
587 if (leading)
588 info[0] = (float) glyphRect.getMinY();
589 else
590 info[0] = (float) glyphRect.getMaxY();
592 else
594 if (leading)
595 info[0] = (float) glyphRect.getMinX();
596 else
597 info[0] = (float) glyphRect.getMaxX();
599 info[0] += run.location;
600 info[1] = run.font.getItalicAngle();
602 return info;
605 public Shape getCaretShape(TextHitInfo hit)
607 return getCaretShape(hit, getBounds());
610 public Shape getCaretShape(TextHitInfo hit, Rectangle2D bounds)
612 // TODO: Handle vertical shapes somehow.
613 float[] info = getCaretInfo(hit);
614 float x1 = info[0];
615 float y1 = (float) bounds.getMinY();
616 float x2 = info[0];
617 float y2 = (float) bounds.getMaxY();
618 if (info[1] != 0)
620 // Shift x1 and x2 according to the slope.
621 x1 -= y1 * info[1];
622 x2 -= y2 * info[1];
624 GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD, 2);
625 path.moveTo(x1, y1);
626 path.lineTo(x2, y2);
627 return path;
630 public Shape[] getCaretShapes(int offset)
632 return getCaretShapes(offset, getNaturalBounds());
635 public Shape[] getCaretShapes(int offset, Rectangle2D bounds)
637 return getCaretShapes(offset, bounds, DEFAULT_CARET_POLICY);
640 public Shape[] getCaretShapes(int offset, Rectangle2D bounds,
641 CaretPolicy policy)
643 // The RI returns a 2-size array even when there's only one
644 // shape in it.
645 Shape[] carets = new Shape[2];
646 TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
647 int caretHit1 = hitToCaret(hit1);
648 TextHitInfo hit2 = hit1.getOtherHit();
649 int caretHit2 = hitToCaret(hit2);
650 if (caretHit1 == caretHit2)
652 carets[0] = getCaretShape(hit1);
653 carets[1] = null; // The RI returns null in this seldom case.
655 else
657 Shape caret1 = getCaretShape(hit1);
658 Shape caret2 = getCaretShape(hit2);
659 TextHitInfo strong = policy.getStrongCaret(hit1, hit2, this);
660 if (strong == hit1)
662 carets[0] = caret1;
663 carets[1] = caret2;
665 else
667 carets[0] = caret2;
668 carets[1] = caret1;
671 return carets;
674 public int getCharacterCount ()
676 return length;
679 public byte getCharacterLevel (int index)
681 byte level;
682 if( bidi == null )
683 level = 0;
684 else
685 level = (byte) bidi.getLevelAt(index);
686 return level;
689 public float getDescent ()
691 return lm.getDescent();
694 public TextLayout getJustifiedLayout (float justificationWidth)
696 TextLayout newLayout = (TextLayout)clone();
698 if( hasWhitespace )
699 newLayout.handleJustify( justificationWidth );
701 return newLayout;
704 public float getLeading ()
706 return lm.getLeading();
709 public Shape getLogicalHighlightShape (int firstEndpoint, int secondEndpoint)
711 return getLogicalHighlightShape( firstEndpoint, secondEndpoint,
712 getBounds() );
715 public Shape getLogicalHighlightShape (int firstEndpoint, int secondEndpoint,
716 Rectangle2D bounds)
718 if( secondEndpoint - firstEndpoint <= 0 )
719 return new Rectangle2D.Float(); // Hmm?
721 if( firstEndpoint < 0 || secondEndpoint > getCharacterCount())
722 return new Rectangle2D.Float();
724 Rectangle2D r = null;
725 int ri = charIndices[ firstEndpoint ][0];
726 int gi = charIndices[ firstEndpoint ][1];
728 double advance = 0;
730 for( int i = 0; i < ri; i++ )
731 advance += runs[i].glyphVector.getLogicalBounds().getWidth();
733 for( int i = ri; i <= charIndices[ secondEndpoint - 1 ][0]; i++ )
735 Run run = runs[i];
736 GlyphVector gv = run.glyphVector;
737 int dg; // last index in this run to use.
738 if( i == charIndices[ secondEndpoint - 1 ][0] )
739 dg = charIndices[ secondEndpoint - 1][1];
740 else
741 dg = gv.getNumGlyphs() - 1;
743 for(; gi <= dg; gi++ )
745 Rectangle2D r2 = (gv.getGlyphLogicalBounds( gi )).
746 getBounds2D();
747 if( r == null )
748 r = r2;
749 else
750 r = r.createUnion(r2);
752 gi = 0; // reset glyph index into run for next run.
754 advance += gv.getLogicalBounds().getWidth();
757 return r;
760 public int[] getLogicalRangesForVisualSelection (TextHitInfo firstEndpoint,
761 TextHitInfo secondEndpoint)
763 // Check parameters.
764 checkHitInfo(firstEndpoint);
765 checkHitInfo(secondEndpoint);
767 // Convert to visual and order correctly.
768 int start = hitToCaret(firstEndpoint);
769 int end = hitToCaret(secondEndpoint);
770 if (start > end)
772 // Swap start and end so that end >= start.
773 int temp = start;
774 start = end;
775 end = temp;
778 // Now walk through the visual indices and mark the included pieces.
779 boolean[] include = new boolean[length];
780 for (int i = start; i < end; i++)
782 include[visualToLogical[i]] = true;
785 // Count included runs.
786 int numRuns = 0;
787 boolean in = false;
788 for (int i = 0; i < length; i++)
790 if (include[i] != in) // At each run in/out point we toggle the in var.
792 in = ! in;
793 if (in) // At each run start we count up.
794 numRuns++;
798 // Put together the ranges array.
799 int[] ranges = new int[numRuns * 2];
800 int index = 0;
801 in = false;
802 for (int i = 0; i < length; i++)
804 if (include[i] != in)
806 ranges[index] = i;
807 index++;
808 in = ! in;
811 // If the last run ends at the very end, include that last bit too.
812 if (in)
813 ranges[index] = length;
815 return ranges;
818 public TextHitInfo getNextLeftHit(int offset)
820 return getNextLeftHit(offset, DEFAULT_CARET_POLICY);
823 public TextHitInfo getNextLeftHit(int offset, CaretPolicy policy)
825 if (policy == null)
826 throw new IllegalArgumentException("Null policy not allowed");
827 if (offset < 0 || offset > length)
828 throw new IllegalArgumentException("Offset out of bounds");
830 TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
831 TextHitInfo hit2 = hit1.getOtherHit();
833 TextHitInfo strong = policy.getStrongCaret(hit1, hit2, this);
834 TextHitInfo next = getNextLeftHit(strong);
835 TextHitInfo ret = null;
836 if (next != null)
838 TextHitInfo next2 = getVisualOtherHit(next);
839 ret = policy.getStrongCaret(next2, next, this);
841 return ret;
844 public TextHitInfo getNextLeftHit (TextHitInfo hit)
846 checkHitInfo(hit);
847 int index = hitToCaret(hit);
848 TextHitInfo next = null;
849 if (index != 0)
851 index--;
852 next = caretToHit(index);
854 return next;
857 public TextHitInfo getNextRightHit(int offset)
859 return getNextRightHit(offset, DEFAULT_CARET_POLICY);
862 public TextHitInfo getNextRightHit(int offset, CaretPolicy policy)
864 if (policy == null)
865 throw new IllegalArgumentException("Null policy not allowed");
866 if (offset < 0 || offset > length)
867 throw new IllegalArgumentException("Offset out of bounds");
869 TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
870 TextHitInfo hit2 = hit1.getOtherHit();
872 TextHitInfo next = getNextRightHit(policy.getStrongCaret(hit1, hit2, this));
873 TextHitInfo ret = null;
874 if (next != null)
876 TextHitInfo next2 = getVisualOtherHit(next);
877 ret = policy.getStrongCaret(next2, next, this);
879 return ret;
882 public TextHitInfo getNextRightHit(TextHitInfo hit)
884 checkHitInfo(hit);
885 int index = hitToCaret(hit);
886 TextHitInfo next = null;
887 if (index < length)
889 index++;
890 next = caretToHit(index);
892 return next;
895 public Shape getOutline (AffineTransform tx)
897 float x = 0f;
898 GeneralPath gp = new GeneralPath();
899 for(int i = 0; i < runs.length; i++)
901 GlyphVector gv = runs[i].glyphVector;
902 gp.append( gv.getOutline( x, 0f ), false );
903 Rectangle2D r = gv.getLogicalBounds();
904 x += r.getWidth();
906 if( tx != null )
907 gp.transform( tx );
908 return gp;
911 public float getVisibleAdvance ()
913 float totalAdvance = 0f;
915 if( runs.length <= 0 )
916 return 0f;
918 // No trailing whitespace
919 if( !Character.isWhitespace( string[offset + length - 1]) )
920 return getAdvance();
922 // Get length of all runs up to the last
923 for(int i = 0; i < runs.length - 1; i++)
924 totalAdvance += runs[i].glyphVector.getLogicalBounds().getWidth();
926 int lastRun = runs[runs.length - 1].runStart;
927 int j = length - 1;
928 while( j >= lastRun && Character.isWhitespace( string[j] ) ) j--;
930 if( j < lastRun )
931 return totalAdvance; // entire last run is whitespace
933 int lastNonWSChar = j - lastRun;
934 j = 0;
935 while( runs[ runs.length - 1 ].glyphVector.getGlyphCharIndex( j )
936 <= lastNonWSChar )
938 totalAdvance += runs[ runs.length - 1 ].glyphVector
939 .getGlyphLogicalBounds( j )
940 .getBounds2D().getWidth();
941 j ++;
944 return totalAdvance;
947 public Shape getVisualHighlightShape (TextHitInfo firstEndpoint,
948 TextHitInfo secondEndpoint)
950 return getVisualHighlightShape( firstEndpoint, secondEndpoint,
951 getBounds() );
954 public Shape getVisualHighlightShape (TextHitInfo firstEndpoint,
955 TextHitInfo secondEndpoint,
956 Rectangle2D bounds)
958 GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
959 Shape caret1 = getCaretShape(firstEndpoint, bounds);
960 path.append(caret1, false);
961 Shape caret2 = getCaretShape(secondEndpoint, bounds);
962 path.append(caret2, false);
963 // Append left (top) bounds to selection if necessary.
964 int c1 = hitToCaret(firstEndpoint);
965 int c2 = hitToCaret(secondEndpoint);
966 if (c1 == 0 || c2 == 0)
968 path.append(left(bounds), false);
970 // Append right (bottom) bounds if necessary.
971 if (c1 == length || c2 == length)
973 path.append(right(bounds), false);
975 return path.getBounds2D();
979 * Returns the shape that makes up the left (top) edge of this text layout.
981 * @param b the bounds
983 * @return the shape that makes up the left (top) edge of this text layout
985 private Shape left(Rectangle2D b)
987 GeneralPath left = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
988 left.append(getCaretShape(TextHitInfo.beforeOffset(0)), false);
989 if (isVertical())
991 float y = (float) b.getMinY();
992 left.append(new Line2D.Float((float) b.getMinX(), y,
993 (float) b.getMaxX(), y), false);
995 else
997 float x = (float) b.getMinX();
998 left.append(new Line2D.Float(x, (float) b.getMinY(),
999 x, (float) b.getMaxY()), false);
1001 return left.getBounds2D();
1005 * Returns the shape that makes up the right (bottom) edge of this text
1006 * layout.
1008 * @param b the bounds
1010 * @return the shape that makes up the right (bottom) edge of this text
1011 * layout
1013 private Shape right(Rectangle2D b)
1015 GeneralPath right = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
1016 right.append(getCaretShape(TextHitInfo.afterOffset(length)), false);
1017 if (isVertical())
1019 float y = (float) b.getMaxY();
1020 right.append(new Line2D.Float((float) b.getMinX(), y,
1021 (float) b.getMaxX(), y), false);
1023 else
1025 float x = (float) b.getMaxX();
1026 right.append(new Line2D.Float(x, (float) b.getMinY(),
1027 x, (float) b.getMaxY()), false);
1029 return right.getBounds2D();
1032 public TextHitInfo getVisualOtherHit (TextHitInfo hit)
1034 checkHitInfo(hit);
1035 int hitIndex = hit.getCharIndex();
1037 int index;
1038 boolean leading;
1039 if (hitIndex == -1 || hitIndex == length)
1041 // Boundary case.
1042 int visual;
1043 if (isLeftToRight() == (hitIndex == -1))
1044 visual = 0;
1045 else
1046 visual = length - 1;
1047 index = visualToLogical[visual];
1048 if (isLeftToRight() == (hitIndex == -1))
1049 leading = isCharacterLTR(index); // LTR.
1050 else
1051 leading = ! isCharacterLTR(index); // RTL.
1053 else
1055 // Normal case.
1056 int visual = logicalToVisual[hitIndex];
1057 boolean b;
1058 if (isCharacterLTR(hitIndex) == hit.isLeadingEdge())
1060 visual--;
1061 b = false;
1063 else
1065 visual++;
1066 b = true;
1068 if (visual >= 0 && visual < length)
1070 index = visualToLogical[visual];
1071 leading = b == isLeftToRight();
1073 else
1075 index = b == isLeftToRight() ? length : -1;
1076 leading = index == length;
1079 return leading ? TextHitInfo.leading(index) : TextHitInfo.trailing(index);
1083 * This is a protected method of a <code>final</code> class, meaning
1084 * it exists only to taunt you.
1086 protected void handleJustify (float justificationWidth)
1088 // We assume that the text has non-trailing whitespace.
1089 // First get the change in width to insert into the whitespaces.
1090 double deltaW = justificationWidth - getVisibleAdvance();
1091 int nglyphs = 0; // # of whitespace chars
1093 // determine last non-whitespace char.
1094 int lastNWS = offset + length - 1;
1095 while( Character.isWhitespace( string[lastNWS] ) ) lastNWS--;
1097 // locations of the glyphs.
1098 int[] wsglyphs = new int[length * 10];
1099 for(int run = 0; run < runs.length; run++ )
1101 Run current = runs[run];
1102 for(int i = 0; i < current.glyphVector.getNumGlyphs(); i++ )
1104 int cindex = current.runStart
1105 + current.glyphVector.getGlyphCharIndex( i );
1106 if( Character.isWhitespace( string[cindex] ) )
1107 // && cindex < lastNWS )
1109 wsglyphs[ nglyphs * 2 ] = run;
1110 wsglyphs[ nglyphs * 2 + 1] = i;
1111 nglyphs++;
1115 deltaW = deltaW / nglyphs; // Change in width per whitespace glyph
1116 double w = 0;
1117 int cws = 0;
1118 // Shift all characters
1119 for(int run = 0; run < runs.length; run++ )
1121 Run current = runs[run];
1122 for(int i = 0; i < current.glyphVector.getNumGlyphs(); i++ )
1124 if( wsglyphs[ cws * 2 ] == run && wsglyphs[ cws * 2 + 1 ] == i )
1126 cws++; // update 'current whitespace'
1127 w += deltaW; // increment the shift
1129 Point2D p = current.glyphVector.getGlyphPosition( i );
1130 p.setLocation( p.getX() + w, p.getY() );
1131 current.glyphVector.setGlyphPosition( i, p );
1136 public TextHitInfo hitTestChar (float x, float y)
1138 return hitTestChar(x, y, getNaturalBounds());
1142 * Finds the character hit at the specified point. This 'clips' this
1143 * text layout against the specified <code>bounds</code> rectangle. That
1144 * means that in the case where a point is outside these bounds, this method
1145 * returns the leading edge of the first character or the trailing edge of
1146 * the last character.
1148 * @param x the X location to test
1149 * @param y the Y location to test
1150 * @param bounds the bounds to test against
1152 * @return the character hit at the specified point
1154 public TextHitInfo hitTestChar (float x, float y, Rectangle2D bounds)
1156 // Check bounds.
1157 if (isVertical())
1159 if (y < bounds.getMinY())
1160 return TextHitInfo.leading(0);
1161 else if (y > bounds.getMaxY())
1162 return TextHitInfo.trailing(getCharacterCount() - 1);
1164 else
1166 if (x < bounds.getMinX())
1167 return TextHitInfo.leading(0);
1168 else if (x > bounds.getMaxX())
1169 return TextHitInfo.trailing(getCharacterCount() - 1);
1172 TextHitInfo hitInfo = null;
1173 if (isVertical())
1175 // Search for the run at the location.
1176 // TODO: Perform binary search for maximum efficiency. However, we
1177 // need the run location laid out statically to do that.
1178 int numRuns = runs.length;
1179 Run hitRun = null;
1180 for (int i = 0; i < numRuns && hitRun == null; i++)
1182 Run run = runs[i];
1183 Rectangle2D lBounds = run.glyphVector.getLogicalBounds();
1184 if (lBounds.getMinY() + run.location <= y
1185 && lBounds.getMaxY() + run.location >= y)
1186 hitRun = run;
1188 // Now we have (hopefully) found a run that hits. Now find the
1189 // right character.
1190 if (hitRun != null)
1192 GlyphVector gv = hitRun.glyphVector;
1193 for (int i = hitRun.runStart;
1194 i < hitRun.runEnd && hitInfo == null; i++)
1196 int gi = i - hitRun.runStart;
1197 Rectangle2D lBounds = gv.getGlyphLogicalBounds(gi)
1198 .getBounds2D();
1199 if (lBounds.getMinY() + hitRun.location <= y
1200 && lBounds.getMaxY() + hitRun.location >= y)
1202 // Found hit. Now check if we are leading or trailing.
1203 boolean leading = true;
1204 if (lBounds.getCenterY() + hitRun.location <= y)
1205 leading = false;
1206 hitInfo = leading ? TextHitInfo.leading(i)
1207 : TextHitInfo.trailing(i);
1212 else
1214 // Search for the run at the location.
1215 // TODO: Perform binary search for maximum efficiency. However, we
1216 // need the run location laid out statically to do that.
1217 int numRuns = runs.length;
1218 Run hitRun = null;
1219 for (int i = 0; i < numRuns && hitRun == null; i++)
1221 Run run = runs[i];
1222 Rectangle2D lBounds = run.glyphVector.getLogicalBounds();
1223 if (lBounds.getMinX() + run.location <= x
1224 && lBounds.getMaxX() + run.location >= x)
1225 hitRun = run;
1227 // Now we have (hopefully) found a run that hits. Now find the
1228 // right character.
1229 if (hitRun != null)
1231 GlyphVector gv = hitRun.glyphVector;
1232 for (int i = hitRun.runStart;
1233 i < hitRun.runEnd && hitInfo == null; i++)
1235 int gi = i - hitRun.runStart;
1236 Rectangle2D lBounds = gv.getGlyphLogicalBounds(gi)
1237 .getBounds2D();
1238 if (lBounds.getMinX() + hitRun.location <= x
1239 && lBounds.getMaxX() + hitRun.location >= x)
1241 // Found hit. Now check if we are leading or trailing.
1242 boolean leading = true;
1243 if (lBounds.getCenterX() + hitRun.location <= x)
1244 leading = false;
1245 hitInfo = leading ? TextHitInfo.leading(i)
1246 : TextHitInfo.trailing(i);
1251 return hitInfo;
1254 public boolean isLeftToRight ()
1256 return leftToRight;
1259 public boolean isVertical ()
1261 return false; // FIXME: How do you create a vertical layout?
1264 public int hashCode ()
1266 // This is implemented in sync to equals().
1267 if (hash == 0 && runs.length > 0)
1269 hash = runs.length;
1270 for (int i = 0; i < runs.length; i++)
1271 hash ^= runs[i].glyphVector.hashCode();
1273 return hash;
1276 public String toString ()
1278 return "TextLayout [string:"+ new String(string, offset, length)
1279 +" Rendercontext:"+
1280 frc+"]";
1284 * Returns the natural bounds of that text layout. This is made up
1285 * of the ascent plus descent and the text advance.
1287 * @return the natural bounds of that text layout
1289 private Rectangle2D getNaturalBounds()
1291 if (naturalBounds == null)
1292 naturalBounds = new Rectangle2D.Float(0.0F, -getAscent(), getAdvance(),
1293 getAscent() + getDescent());
1294 return naturalBounds;
1297 private void checkHitInfo(TextHitInfo hit)
1299 if (hit == null)
1300 throw new IllegalArgumentException("Null hit info not allowed");
1301 int index = hit.getInsertionIndex();
1302 if (index < 0 || index > length)
1303 throw new IllegalArgumentException("Hit index out of range");
1306 private int hitToCaret(TextHitInfo hit)
1308 int index = hit.getCharIndex();
1309 int ret;
1310 if (index < 0)
1311 ret = isLeftToRight() ? 0 : length;
1312 else if (index >= length)
1313 ret = isLeftToRight() ? length : 0;
1314 else
1316 ret = logicalToVisual[index];
1317 if (hit.isLeadingEdge() != isCharacterLTR(index))
1318 ret++;
1320 return ret;
1323 private TextHitInfo caretToHit(int index)
1325 TextHitInfo hit;
1326 if (index == 0 || index == length)
1328 if ((index == length) == isLeftToRight())
1329 hit = TextHitInfo.leading(length);
1330 else
1331 hit = TextHitInfo.trailing(-1);
1333 else
1335 int logical = visualToLogical[index];
1336 boolean leading = isCharacterLTR(logical); // LTR.
1337 hit = leading ? TextHitInfo.leading(logical)
1338 : TextHitInfo.trailing(logical);
1340 return hit;
1343 private boolean isCharacterLTR(int index)
1345 byte level = getCharacterLevel(index);
1346 return (level & 1) == 0;
1350 * Finds the run that holds the specified (logical) character index. This
1351 * returns <code>null</code> when the index is not inside the range.
1353 * @param index the index of the character to find
1355 * @return the run that holds the specified character
1357 private Run findRunAtIndex(int index)
1359 Run found = null;
1360 // TODO: Can we do better than linear searching here?
1361 for (int i = 0; i < runs.length && found == null; i++)
1363 Run run = runs[i];
1364 if (run.runStart <= index && run.runEnd > index)
1365 found = run;
1367 return found;
1371 * Computes the layout locations for each run.
1373 private void layoutRuns()
1375 float loc = 0.0F;
1376 float lastWidth = 0.0F;
1377 for (int i = 0; i < runs.length; i++)
1379 runs[i].location = loc;
1380 Rectangle2D bounds = runs[i].glyphVector.getLogicalBounds();
1381 loc += isVertical() ? bounds.getHeight() : bounds.getWidth();
1386 * Inner class describing a caret policy
1388 public static class CaretPolicy
1390 public CaretPolicy()
1394 public TextHitInfo getStrongCaret(TextHitInfo hit1,
1395 TextHitInfo hit2,
1396 TextLayout layout)
1398 byte l1 = layout.getCharacterLevel(hit1.getCharIndex());
1399 byte l2 = layout.getCharacterLevel(hit2.getCharIndex());
1400 TextHitInfo strong;
1401 if (l1 == l2)
1403 if (hit2.isLeadingEdge() && ! hit1.isLeadingEdge())
1404 strong = hit2;
1405 else
1406 strong = hit1;
1408 else
1410 if (l1 < l2)
1411 strong = hit1;
1412 else
1413 strong = hit2;
1415 return strong;