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)
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
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
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
;
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
;
56 * @author Sven de Marothy
58 public final class TextLayout
implements Cloneable
61 * Holds the layout data that belongs to one run of characters.
66 * The actual glyph vector.
68 GlyphVector glyphVector
;
71 * The font for this text run.
76 * The start of the run.
86 * The layout location of the beginning of the run.
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
)
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.
123 private FontRenderContext frc
;
124 private char[] string
;
127 private Rectangle2D boundsCache
;
128 private LineMetrics lm
;
131 * The total advance of this text layout. This is cache for maximum
134 private float totalAdvance
= -1F
;
137 * The cached natural bounds.
139 private Rectangle2D naturalBounds
;
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)}.
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.
181 * The default caret policy.
183 public static final TextLayout
.CaretPolicy DEFAULT_CARET_POLICY
=
187 * Constructs a TextLayout.
189 public TextLayout (String str
, Font font
, FontRenderContext frc
)
192 string
= str
.toCharArray();
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
);
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
,
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
++)
229 cleaned
.add(runs
[i
]);
231 runs
= new Run
[cleaned
.size()];
232 runs
= (Run
[]) cleaned
.toArray(runs
);
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
};
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
)
270 leftToRight
= t
.leftToRight
;
272 if( endIndex
> t
.getCharacterCount() )
273 endIndex
= t
.getCharacterCount();
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;
301 determineWhiteSpace();
305 private void setCharIndices()
307 charIndices
= new int[ getCharacterCount() ][2];
310 for(int run
= 0; run
< runs
.length
; run
++)
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
);
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
];
338 // We scan the runs in visual order and set the mappings accordingly.
339 for (int i
= 0; i
< runs
.length
; i
++)
342 if (run
.isLeftToRight())
344 for (lIndex
= run
.runStart
; lIndex
< run
.runEnd
; lIndex
++)
346 logicalToVisual
[lIndex
] = vIndex
;
347 visualToLogical
[vIndex
] = lIndex
;
353 for (lIndex
= run
.runEnd
- 1; lIndex
>= run
.runStart
; lIndex
--)
355 logicalToVisual
[lIndex
] = vIndex
;
356 visualToLogical
[vIndex
] = lIndex
;
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())
369 iter
.setIndex( idx
);
370 return sb
.toString();
373 private static Font
getFont(AttributedCharacterIterator iter
)
375 Font f
= (Font
)iter
.getAttribute(TextAttribute
.FONT
);
379 Float i
= (Float
)iter
.getAttribute(TextAttribute
.SIZE
);
381 size
= (int)i
.floatValue();
384 f
= new Font("Dialog", Font
.PLAIN
, size
);
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;
397 int endOffs
= offset
+ length
;
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
:
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
:
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
] ) )
427 // Check the remaining chars
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
++)
443 GlyphVector gv
= run
.glyphVector
;
444 g2
.drawGlyphVector(gv
, x
, y
);
445 Rectangle2D r
= gv
.getLogicalBounds();
450 public boolean equals (Object obj
)
452 if( !( obj
instanceof TextLayout
) )
455 return equals( (TextLayout
) obj
);
458 public boolean equals (TextLayout tl
)
460 if( runs
.length
!= tl
.runs
.length
)
462 // Compare all glyph vectors.
463 for( int i
= 0; i
< runs
.length
; i
++ )
464 if( !runs
[i
].equals( tl
.runs
[i
] ) )
469 public float getAdvance ()
471 if (totalAdvance
== -1F
)
474 for(int i
= 0; i
< runs
.length
; i
++)
477 GlyphVector gv
= run
.glyphVector
;
478 totalAdvance
+= gv
.getLogicalBounds().getWidth();
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];
514 for( int i
= 0; i
< ri
; i
++ )
517 GlyphVector gv
= run
.glyphVector
;
518 advance
+= gv
.getLogicalBounds().getWidth();
521 for( int i
= ri
; i
<= charIndices
[ secondEndpoint
- 1 ][0]; i
++ )
524 GlyphVector gv
= run
.glyphVector
;
526 if( i
== charIndices
[ secondEndpoint
- 1 ][0] )
527 dg
= charIndices
[ secondEndpoint
- 1][1];
529 dg
= gv
.getNumGlyphs() - 1;
531 for( int j
= 0; j
<= dg
; j
++ )
533 Rectangle2D r2
= (gv
.getGlyphVisualBounds( j
)).
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();
546 public Rectangle2D
getBounds()
548 if( boundsCache
== null )
549 boundsCache
= getOutline(new AffineTransform()).getBounds();
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.
568 info
[0] = getAdvance();
580 run
= findRunAtIndex(index
);
582 int glyphIndex
= index
- run
.runStart
;
583 Shape glyphBounds
= run
.glyphVector
.getGlyphLogicalBounds(glyphIndex
);
584 Rectangle2D glyphRect
= glyphBounds
.getBounds2D();
588 info
[0] = (float) glyphRect
.getMinY();
590 info
[0] = (float) glyphRect
.getMaxY();
595 info
[0] = (float) glyphRect
.getMinX();
597 info
[0] = (float) glyphRect
.getMaxX();
599 info
[0] += run
.location
;
600 info
[1] = run
.font
.getItalicAngle();
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
);
615 float y1
= (float) bounds
.getMinY();
617 float y2
= (float) bounds
.getMaxY();
620 // Shift x1 and x2 according to the slope.
624 GeneralPath path
= new GeneralPath(GeneralPath
.WIND_EVEN_ODD
, 2);
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
,
643 // The RI returns a 2-size array even when there's only one
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.
657 Shape caret1
= getCaretShape(hit1
);
658 Shape caret2
= getCaretShape(hit2
);
659 TextHitInfo strong
= policy
.getStrongCaret(hit1
, hit2
, this);
674 public int getCharacterCount ()
679 public byte getCharacterLevel (int index
)
685 level
= (byte) bidi
.getLevelAt(index
);
689 public float getDescent ()
691 return lm
.getDescent();
694 public TextLayout
getJustifiedLayout (float justificationWidth
)
696 TextLayout newLayout
= (TextLayout
)clone();
699 newLayout
.handleJustify( justificationWidth
);
704 public float getLeading ()
706 return lm
.getLeading();
709 public Shape
getLogicalHighlightShape (int firstEndpoint
, int secondEndpoint
)
711 return getLogicalHighlightShape( firstEndpoint
, secondEndpoint
,
715 public Shape
getLogicalHighlightShape (int firstEndpoint
, int secondEndpoint
,
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];
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
++ )
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];
741 dg
= gv
.getNumGlyphs() - 1;
743 for(; gi
<= dg
; gi
++ )
745 Rectangle2D r2
= (gv
.getGlyphLogicalBounds( gi
)).
750 r
= r
.createUnion(r2
);
752 gi
= 0; // reset glyph index into run for next run.
754 advance
+= gv
.getLogicalBounds().getWidth();
760 public int[] getLogicalRangesForVisualSelection (TextHitInfo firstEndpoint
,
761 TextHitInfo secondEndpoint
)
764 checkHitInfo(firstEndpoint
);
765 checkHitInfo(secondEndpoint
);
767 // Convert to visual and order correctly.
768 int start
= hitToCaret(firstEndpoint
);
769 int end
= hitToCaret(secondEndpoint
);
772 // Swap start and end so that end >= start.
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.
788 for (int i
= 0; i
< length
; i
++)
790 if (include
[i
] != in
) // At each run in/out point we toggle the in var.
793 if (in
) // At each run start we count up.
798 // Put together the ranges array.
799 int[] ranges
= new int[numRuns
* 2];
802 for (int i
= 0; i
< length
; i
++)
804 if (include
[i
] != in
)
811 // If the last run ends at the very end, include that last bit too.
813 ranges
[index
] = length
;
818 public TextHitInfo
getNextLeftHit(int offset
)
820 return getNextLeftHit(offset
, DEFAULT_CARET_POLICY
);
823 public TextHitInfo
getNextLeftHit(int offset
, CaretPolicy policy
)
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;
838 TextHitInfo next2
= getVisualOtherHit(next
);
839 ret
= policy
.getStrongCaret(next2
, next
, this);
844 public TextHitInfo
getNextLeftHit (TextHitInfo hit
)
847 int index
= hitToCaret(hit
);
848 TextHitInfo next
= null;
852 next
= caretToHit(index
);
857 public TextHitInfo
getNextRightHit(int offset
)
859 return getNextRightHit(offset
, DEFAULT_CARET_POLICY
);
862 public TextHitInfo
getNextRightHit(int offset
, CaretPolicy policy
)
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;
876 TextHitInfo next2
= getVisualOtherHit(next
);
877 ret
= policy
.getStrongCaret(next2
, next
, this);
882 public TextHitInfo
getNextRightHit(TextHitInfo hit
)
885 int index
= hitToCaret(hit
);
886 TextHitInfo next
= null;
890 next
= caretToHit(index
);
895 public Shape
getOutline (AffineTransform tx
)
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();
911 public float getVisibleAdvance ()
913 float totalAdvance
= 0f
;
915 if( runs
.length
<= 0 )
918 // No trailing whitespace
919 if( !Character
.isWhitespace( string
[offset
+ length
- 1]) )
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
;
928 while( j
>= lastRun
&& Character
.isWhitespace( string
[j
] ) ) j
--;
931 return totalAdvance
; // entire last run is whitespace
933 int lastNonWSChar
= j
- lastRun
;
935 while( runs
[ runs
.length
- 1 ].glyphVector
.getGlyphCharIndex( j
)
938 totalAdvance
+= runs
[ runs
.length
- 1 ].glyphVector
939 .getGlyphLogicalBounds( j
)
940 .getBounds2D().getWidth();
947 public Shape
getVisualHighlightShape (TextHitInfo firstEndpoint
,
948 TextHitInfo secondEndpoint
)
950 return getVisualHighlightShape( firstEndpoint
, secondEndpoint
,
954 public Shape
getVisualHighlightShape (TextHitInfo firstEndpoint
,
955 TextHitInfo secondEndpoint
,
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);
991 float y
= (float) b
.getMinY();
992 left
.append(new Line2D
.Float((float) b
.getMinX(), y
,
993 (float) b
.getMaxX(), y
), false);
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
1008 * @param b the bounds
1010 * @return the shape that makes up the right (bottom) edge of this text
1013 private Shape
right(Rectangle2D b
)
1015 GeneralPath right
= new GeneralPath(GeneralPath
.WIND_EVEN_ODD
);
1016 right
.append(getCaretShape(TextHitInfo
.afterOffset(length
)), false);
1019 float y
= (float) b
.getMaxY();
1020 right
.append(new Line2D
.Float((float) b
.getMinX(), y
,
1021 (float) b
.getMaxX(), y
), false);
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
)
1035 int hitIndex
= hit
.getCharIndex();
1039 if (hitIndex
== -1 || hitIndex
== length
)
1043 if (isLeftToRight() == (hitIndex
== -1))
1046 visual
= length
- 1;
1047 index
= visualToLogical
[visual
];
1048 if (isLeftToRight() == (hitIndex
== -1))
1049 leading
= isCharacterLTR(index
); // LTR.
1051 leading
= ! isCharacterLTR(index
); // RTL.
1056 int visual
= logicalToVisual
[hitIndex
];
1058 if (isCharacterLTR(hitIndex
) == hit
.isLeadingEdge())
1068 if (visual
>= 0 && visual
< length
)
1070 index
= visualToLogical
[visual
];
1071 leading
= b
== isLeftToRight();
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
;
1115 deltaW
= deltaW
/ nglyphs
; // Change in width per whitespace glyph
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
)
1159 if (y
< bounds
.getMinY())
1160 return TextHitInfo
.leading(0);
1161 else if (y
> bounds
.getMaxY())
1162 return TextHitInfo
.trailing(getCharacterCount() - 1);
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;
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
;
1180 for (int i
= 0; i
< numRuns
&& hitRun
== null; i
++)
1183 Rectangle2D lBounds
= run
.glyphVector
.getLogicalBounds();
1184 if (lBounds
.getMinY() + run
.location
<= y
1185 && lBounds
.getMaxY() + run
.location
>= y
)
1188 // Now we have (hopefully) found a run that hits. Now find the
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
)
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
)
1206 hitInfo
= leading ? TextHitInfo
.leading(i
)
1207 : TextHitInfo
.trailing(i
);
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
;
1219 for (int i
= 0; i
< numRuns
&& hitRun
== null; i
++)
1222 Rectangle2D lBounds
= run
.glyphVector
.getLogicalBounds();
1223 if (lBounds
.getMinX() + run
.location
<= x
1224 && lBounds
.getMaxX() + run
.location
>= x
)
1227 // Now we have (hopefully) found a run that hits. Now find the
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
)
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
)
1245 hitInfo
= leading ? TextHitInfo
.leading(i
)
1246 : TextHitInfo
.trailing(i
);
1254 public boolean isLeftToRight ()
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)
1270 for (int i
= 0; i
< runs
.length
; i
++)
1271 hash ^
= runs
[i
].glyphVector
.hashCode();
1276 public String
toString ()
1278 return "TextLayout [string:"+ new String(string
, offset
, length
)
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
)
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();
1311 ret
= isLeftToRight() ?
0 : length
;
1312 else if (index
>= length
)
1313 ret
= isLeftToRight() ? length
: 0;
1316 ret
= logicalToVisual
[index
];
1317 if (hit
.isLeadingEdge() != isCharacterLTR(index
))
1323 private TextHitInfo
caretToHit(int index
)
1326 if (index
== 0 || index
== length
)
1328 if ((index
== length
) == isLeftToRight())
1329 hit
= TextHitInfo
.leading(length
);
1331 hit
= TextHitInfo
.trailing(-1);
1335 int logical
= visualToLogical
[index
];
1336 boolean leading
= isCharacterLTR(logical
); // LTR.
1337 hit
= leading ? TextHitInfo
.leading(logical
)
1338 : TextHitInfo
.trailing(logical
);
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
)
1360 // TODO: Can we do better than linear searching here?
1361 for (int i
= 0; i
< runs
.length
&& found
== null; i
++)
1364 if (run
.runStart
<= index
&& run
.runEnd
> index
)
1371 * Computes the layout locations for each run.
1373 private void layoutRuns()
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
,
1398 byte l1
= layout
.getCharacterLevel(hit1
.getCharIndex());
1399 byte l2
= layout
.getCharacterLevel(hit2
.getCharIndex());
1403 if (hit2
.isLeadingEdge() && ! hit1
.isLeadingEdge())