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
;
41 import gnu
.java
.lang
.CPStringBuilder
;
44 import java
.awt
.Graphics2D
;
45 import java
.awt
.Shape
;
46 import java
.awt
.geom
.AffineTransform
;
47 import java
.awt
.geom
.Line2D
;
48 import java
.awt
.geom
.Rectangle2D
;
49 import java
.awt
.geom
.GeneralPath
;
50 import java
.awt
.geom
.Point2D
;
51 import java
.text
.CharacterIterator
;
52 import java
.text
.AttributedCharacterIterator
;
53 import java
.text
.Bidi
;
54 import java
.util
.ArrayList
;
58 * @author Sven de Marothy
60 public final class TextLayout
implements Cloneable
63 * Holds the layout data that belongs to one run of characters.
68 * The actual glyph vector.
70 GlyphVector glyphVector
;
73 * The font for this text run.
78 * The start of the run.
88 * The layout location of the beginning of the run.
93 * Initializes the Run instance.
95 * @param gv the glyph vector
96 * @param start the start index of the run
97 * @param end the end index of the run
99 Run(GlyphVector gv
, Font f
, int start
, int end
)
108 * Returns <code>true</code> when this run is left to right,
109 * <code>false</code> otherwise.
111 * @return <code>true</code> when this run is left to right,
112 * <code>false</code> otherwise
114 boolean isLeftToRight()
116 return (glyphVector
.getLayoutFlags() & GlyphVector
.FLAG_RUN_RTL
) == 0;
121 * The laid out character runs.
125 private FontRenderContext frc
;
126 private char[] string
;
129 private Rectangle2D boundsCache
;
130 private LineMetrics lm
;
133 * The total advance of this text layout. This is cache for maximum
136 private float totalAdvance
= -1F
;
139 * The cached natural bounds.
141 private Rectangle2D naturalBounds
;
145 * Fixt index is the glyphvector, second index is the (first) glyph.
147 private int[][] charIndices
;
150 * Base directionality, determined from the first char.
152 private boolean leftToRight
;
155 * Whether this layout contains whitespace or not.
157 private boolean hasWhitespace
= false;
160 * The {@link Bidi} object that is used for reordering and by
161 * {@link #getCharacterLevel(int)}.
166 * Mpas the logical position of each individual character in the original
167 * string to its visual position.
169 private int[] logicalToVisual
;
172 * Maps visual positions of a character to its logical position
173 * in the original string.
175 private int[] visualToLogical
;
178 * The cached hashCode.
183 * The default caret policy.
185 public static final TextLayout
.CaretPolicy DEFAULT_CARET_POLICY
=
189 * Constructs a TextLayout.
191 public TextLayout (String str
, Font font
, FontRenderContext frc
)
194 string
= str
.toCharArray();
196 length
= this.string
.length
;
197 lm
= font
.getLineMetrics(this.string
, offset
, length
, frc
);
199 // Get base direction and whitespace info
200 getStringProperties();
202 if (Bidi
.requiresBidi(string
, offset
, offset
+ length
))
204 bidi
= new Bidi(str
, leftToRight ? Bidi
.DIRECTION_LEFT_TO_RIGHT
205 : Bidi
.DIRECTION_RIGHT_TO_LEFT
);
206 int rc
= bidi
.getRunCount();
207 byte[] table
= new byte[ rc
];
208 for(int i
= 0; i
< table
.length
; i
++)
209 table
[i
] = (byte)bidi
.getRunLevel(i
);
212 for(int i
= 0; i
< rc
; i
++)
214 int start
= bidi
.getRunStart(i
);
215 int end
= bidi
.getRunLimit(i
);
216 if(start
!= end
) // no empty runs.
218 GlyphVector gv
= font
.layoutGlyphVector(frc
,
220 ((table
[i
] & 1) == 0) ? Font
.LAYOUT_LEFT_TO_RIGHT
221 : Font
.LAYOUT_RIGHT_TO_LEFT
);
222 runs
[i
] = new Run(gv
, font
, start
, end
);
225 Bidi
.reorderVisually( table
, 0, runs
, 0, runs
.length
);
226 // Clean up null runs.
227 ArrayList cleaned
= new ArrayList(rc
);
228 for (int i
= 0; i
< rc
; i
++)
231 cleaned
.add(runs
[i
]);
233 runs
= new Run
[cleaned
.size()];
234 runs
= (Run
[]) cleaned
.toArray(runs
);
238 GlyphVector gv
= font
.layoutGlyphVector( frc
, string
, offset
, length
,
239 leftToRight ? Font
.LAYOUT_LEFT_TO_RIGHT
240 : Font
.LAYOUT_RIGHT_TO_LEFT
);
241 Run run
= new Run(gv
, font
, 0, length
);
242 runs
= new Run
[]{ run
};
249 public TextLayout (String string
,
250 Map
<?
extends AttributedCharacterIterator
.Attribute
, ?
> attributes
,
251 FontRenderContext frc
)
253 this( string
, new Font( attributes
), frc
);
256 public TextLayout (AttributedCharacterIterator text
, FontRenderContext frc
)
258 // FIXME: Very rudimentary.
259 this(getText(text
), getFont(text
), frc
);
263 * Package-private constructor to make a textlayout from an existing one.
264 * This is used by TextMeasurer for returning sub-layouts, and it
265 * saves a lot of time in not having to relayout the text.
267 TextLayout(TextLayout t
, int startIndex
, int endIndex
)
272 leftToRight
= t
.leftToRight
;
274 if( endIndex
> t
.getCharacterCount() )
275 endIndex
= t
.getCharacterCount();
277 offset
= startIndex
+ offset
;
278 length
= endIndex
- startIndex
;
280 int startingRun
= t
.charIndices
[startIndex
][0];
281 int nRuns
= 1 + t
.charIndices
[endIndex
- 1][0] - startingRun
;
283 runs
= new Run
[nRuns
];
284 for( int i
= 0; i
< nRuns
; i
++ )
286 Run run
= t
.runs
[i
+ startingRun
];
287 GlyphVector gv
= run
.glyphVector
;
288 Font font
= run
.font
;
289 // Copy only the relevant parts of the first and last runs.
290 int beginGlyphIndex
= (i
> 0) ?
0 : t
.charIndices
[startIndex
][1];
291 int numEntries
= ( i
< nRuns
- 1) ? gv
.getNumGlyphs() :
292 1 + t
.charIndices
[endIndex
- 1][1] - beginGlyphIndex
;
294 int[] codes
= gv
.getGlyphCodes(beginGlyphIndex
, numEntries
, null);
295 gv
= font
.createGlyphVector(frc
, codes
);
296 runs
[i
] = new Run(gv
, font
, run
.runStart
- startIndex
,
297 run
.runEnd
- startIndex
);
299 runs
[nRuns
- 1].runEnd
= endIndex
- 1;
303 determineWhiteSpace();
307 private void setCharIndices()
309 charIndices
= new int[ getCharacterCount() ][2];
312 for(int run
= 0; run
< runs
.length
; run
++)
315 Run current
= runs
[run
];
316 GlyphVector gv
= current
.glyphVector
;
317 for( int gi
= 0; gi
< gv
.getNumGlyphs(); gi
++)
319 if( gv
.getGlyphCharIndex( gi
) != currentChar
)
321 charIndices
[ i
][0] = run
;
322 charIndices
[ i
][1] = gi
;
323 currentChar
= gv
.getGlyphCharIndex( gi
);
331 * Initializes the logicalToVisual and visualToLogial maps.
333 private void setupMappings()
335 int numChars
= getCharacterCount();
336 logicalToVisual
= new int[numChars
];
337 visualToLogical
= new int[numChars
];
340 // We scan the runs in visual order and set the mappings accordingly.
341 for (int i
= 0; i
< runs
.length
; i
++)
344 if (run
.isLeftToRight())
346 for (lIndex
= run
.runStart
; lIndex
< run
.runEnd
; lIndex
++)
348 logicalToVisual
[lIndex
] = vIndex
;
349 visualToLogical
[vIndex
] = lIndex
;
355 for (lIndex
= run
.runEnd
- 1; lIndex
>= run
.runStart
; lIndex
--)
357 logicalToVisual
[lIndex
] = vIndex
;
358 visualToLogical
[vIndex
] = lIndex
;
365 private static String
getText(AttributedCharacterIterator iter
)
367 CPStringBuilder sb
= new CPStringBuilder();
368 int idx
= iter
.getIndex();
369 for(char c
= iter
.first(); c
!= CharacterIterator
.DONE
; c
= iter
.next())
371 iter
.setIndex( idx
);
372 return sb
.toString();
375 private static Font
getFont(AttributedCharacterIterator iter
)
377 Font f
= (Font
)iter
.getAttribute(TextAttribute
.FONT
);
381 Float i
= (Float
)iter
.getAttribute(TextAttribute
.SIZE
);
383 size
= (int)i
.floatValue();
386 f
= new Font("Dialog", Font
.PLAIN
, size
);
392 * Scan the character run for the first strongly directional character,
393 * which in turn defines the base directionality of the whole layout.
395 private void getStringProperties()
397 boolean gotDirection
= false;
399 int endOffs
= offset
+ length
;
401 while( i
< endOffs
&& !gotDirection
)
402 switch( Character
.getDirectionality(string
[i
++]) )
404 case Character
.DIRECTIONALITY_LEFT_TO_RIGHT
:
405 case Character
.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING
:
406 case Character
.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE
:
410 case Character
.DIRECTIONALITY_RIGHT_TO_LEFT
:
411 case Character
.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC
:
412 case Character
.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING
:
413 case Character
.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE
:
418 determineWhiteSpace();
421 private void determineWhiteSpace()
423 // Determine if there's whitespace in the thing.
424 // Ignore trailing chars.
425 int i
= offset
+ length
- 1;
426 hasWhitespace
= false;
427 while( i
>= offset
&& Character
.isWhitespace( string
[i
] ) )
429 // Check the remaining chars
431 if( Character
.isWhitespace( string
[i
--] ) )
432 hasWhitespace
= true;
435 protected Object
clone ()
437 return new TextLayout( this, 0, length
);
440 public void draw (Graphics2D g2
, float x
, float y
)
442 for(int i
= 0; i
< runs
.length
; i
++)
445 GlyphVector gv
= run
.glyphVector
;
446 g2
.drawGlyphVector(gv
, x
, y
);
447 Rectangle2D r
= gv
.getLogicalBounds();
452 public boolean equals (Object obj
)
454 if( !( obj
instanceof TextLayout
) )
457 return equals( (TextLayout
) obj
);
460 public boolean equals (TextLayout tl
)
462 if( runs
.length
!= tl
.runs
.length
)
464 // Compare all glyph vectors.
465 for( int i
= 0; i
< runs
.length
; i
++ )
466 if( !runs
[i
].equals( tl
.runs
[i
] ) )
471 public float getAdvance ()
473 if (totalAdvance
== -1F
)
476 for(int i
= 0; i
< runs
.length
; i
++)
479 GlyphVector gv
= run
.glyphVector
;
480 totalAdvance
+= gv
.getLogicalBounds().getWidth();
486 public float getAscent ()
488 return lm
.getAscent();
491 public byte getBaseline ()
493 return (byte)lm
.getBaselineIndex();
496 public float[] getBaselineOffsets ()
498 return lm
.getBaselineOffsets();
501 public Shape
getBlackBoxBounds (int firstEndpoint
, int secondEndpoint
)
503 if( secondEndpoint
- firstEndpoint
<= 0 )
504 return new Rectangle2D
.Float(); // Hmm?
506 if( firstEndpoint
< 0 || secondEndpoint
> getCharacterCount())
507 return new Rectangle2D
.Float();
509 GeneralPath gp
= new GeneralPath();
511 int ri
= charIndices
[ firstEndpoint
][0];
512 int gi
= charIndices
[ firstEndpoint
][1];
516 for( int i
= 0; i
< ri
; i
++ )
519 GlyphVector gv
= run
.glyphVector
;
520 advance
+= gv
.getLogicalBounds().getWidth();
523 for( int i
= ri
; i
<= charIndices
[ secondEndpoint
- 1 ][0]; i
++ )
526 GlyphVector gv
= run
.glyphVector
;
528 if( i
== charIndices
[ secondEndpoint
- 1 ][0] )
529 dg
= charIndices
[ secondEndpoint
- 1][1];
531 dg
= gv
.getNumGlyphs() - 1;
533 for( int j
= 0; j
<= dg
; j
++ )
535 Rectangle2D r2
= (gv
.getGlyphVisualBounds( j
)).
537 Point2D p
= gv
.getGlyphPosition( j
);
538 r2
.setRect( advance
+ r2
.getX(), r2
.getY(),
539 r2
.getWidth(), r2
.getHeight() );
540 gp
.append(r2
, false);
543 advance
+= gv
.getLogicalBounds().getWidth();
548 public Rectangle2D
getBounds()
550 if( boundsCache
== null )
551 boundsCache
= getOutline(new AffineTransform()).getBounds();
555 public float[] getCaretInfo (TextHitInfo hit
)
557 return getCaretInfo(hit
, getNaturalBounds());
560 public float[] getCaretInfo (TextHitInfo hit
, Rectangle2D bounds
)
562 float[] info
= new float[2];
563 int index
= hit
.getCharIndex();
564 boolean leading
= hit
.isLeadingEdge();
565 // For the boundary cases we return the boundary runs.
570 info
[0] = getAdvance();
582 run
= findRunAtIndex(index
);
584 int glyphIndex
= index
- run
.runStart
;
585 Shape glyphBounds
= run
.glyphVector
.getGlyphLogicalBounds(glyphIndex
);
586 Rectangle2D glyphRect
= glyphBounds
.getBounds2D();
590 info
[0] = (float) glyphRect
.getMinY();
592 info
[0] = (float) glyphRect
.getMaxY();
597 info
[0] = (float) glyphRect
.getMinX();
599 info
[0] = (float) glyphRect
.getMaxX();
601 info
[0] += run
.location
;
602 info
[1] = run
.font
.getItalicAngle();
607 public Shape
getCaretShape(TextHitInfo hit
)
609 return getCaretShape(hit
, getBounds());
612 public Shape
getCaretShape(TextHitInfo hit
, Rectangle2D bounds
)
614 // TODO: Handle vertical shapes somehow.
615 float[] info
= getCaretInfo(hit
);
617 float y1
= (float) bounds
.getMinY();
619 float y2
= (float) bounds
.getMaxY();
622 // Shift x1 and x2 according to the slope.
626 GeneralPath path
= new GeneralPath(GeneralPath
.WIND_EVEN_ODD
, 2);
632 public Shape
[] getCaretShapes(int offset
)
634 return getCaretShapes(offset
, getNaturalBounds());
637 public Shape
[] getCaretShapes(int offset
, Rectangle2D bounds
)
639 return getCaretShapes(offset
, bounds
, DEFAULT_CARET_POLICY
);
642 public Shape
[] getCaretShapes(int offset
, Rectangle2D bounds
,
645 // The RI returns a 2-size array even when there's only one
647 Shape
[] carets
= new Shape
[2];
648 TextHitInfo hit1
= TextHitInfo
.afterOffset(offset
);
649 int caretHit1
= hitToCaret(hit1
);
650 TextHitInfo hit2
= hit1
.getOtherHit();
651 int caretHit2
= hitToCaret(hit2
);
652 if (caretHit1
== caretHit2
)
654 carets
[0] = getCaretShape(hit1
);
655 carets
[1] = null; // The RI returns null in this seldom case.
659 Shape caret1
= getCaretShape(hit1
);
660 Shape caret2
= getCaretShape(hit2
);
661 TextHitInfo strong
= policy
.getStrongCaret(hit1
, hit2
, this);
676 public int getCharacterCount ()
681 public byte getCharacterLevel (int index
)
687 level
= (byte) bidi
.getLevelAt(index
);
691 public float getDescent ()
693 return lm
.getDescent();
696 public TextLayout
getJustifiedLayout (float justificationWidth
)
698 TextLayout newLayout
= (TextLayout
)clone();
701 newLayout
.handleJustify( justificationWidth
);
706 public float getLeading ()
708 return lm
.getLeading();
711 public Shape
getLogicalHighlightShape (int firstEndpoint
, int secondEndpoint
)
713 return getLogicalHighlightShape( firstEndpoint
, secondEndpoint
,
717 public Shape
getLogicalHighlightShape (int firstEndpoint
, int secondEndpoint
,
720 if( secondEndpoint
- firstEndpoint
<= 0 )
721 return new Rectangle2D
.Float(); // Hmm?
723 if( firstEndpoint
< 0 || secondEndpoint
> getCharacterCount())
724 return new Rectangle2D
.Float();
726 Rectangle2D r
= null;
727 int ri
= charIndices
[ firstEndpoint
][0];
728 int gi
= charIndices
[ firstEndpoint
][1];
732 for( int i
= 0; i
< ri
; i
++ )
733 advance
+= runs
[i
].glyphVector
.getLogicalBounds().getWidth();
735 for( int i
= ri
; i
<= charIndices
[ secondEndpoint
- 1 ][0]; i
++ )
738 GlyphVector gv
= run
.glyphVector
;
739 int dg
; // last index in this run to use.
740 if( i
== charIndices
[ secondEndpoint
- 1 ][0] )
741 dg
= charIndices
[ secondEndpoint
- 1][1];
743 dg
= gv
.getNumGlyphs() - 1;
745 for(; gi
<= dg
; gi
++ )
747 Rectangle2D r2
= (gv
.getGlyphLogicalBounds( gi
)).
752 r
= r
.createUnion(r2
);
754 gi
= 0; // reset glyph index into run for next run.
756 advance
+= gv
.getLogicalBounds().getWidth();
762 public int[] getLogicalRangesForVisualSelection (TextHitInfo firstEndpoint
,
763 TextHitInfo secondEndpoint
)
766 checkHitInfo(firstEndpoint
);
767 checkHitInfo(secondEndpoint
);
769 // Convert to visual and order correctly.
770 int start
= hitToCaret(firstEndpoint
);
771 int end
= hitToCaret(secondEndpoint
);
774 // Swap start and end so that end >= start.
780 // Now walk through the visual indices and mark the included pieces.
781 boolean[] include
= new boolean[length
];
782 for (int i
= start
; i
< end
; i
++)
784 include
[visualToLogical
[i
]] = true;
787 // Count included runs.
790 for (int i
= 0; i
< length
; i
++)
792 if (include
[i
] != in
) // At each run in/out point we toggle the in var.
795 if (in
) // At each run start we count up.
800 // Put together the ranges array.
801 int[] ranges
= new int[numRuns
* 2];
804 for (int i
= 0; i
< length
; i
++)
806 if (include
[i
] != in
)
813 // If the last run ends at the very end, include that last bit too.
815 ranges
[index
] = length
;
820 public TextHitInfo
getNextLeftHit(int offset
)
822 return getNextLeftHit(offset
, DEFAULT_CARET_POLICY
);
825 public TextHitInfo
getNextLeftHit(int offset
, CaretPolicy policy
)
828 throw new IllegalArgumentException("Null policy not allowed");
829 if (offset
< 0 || offset
> length
)
830 throw new IllegalArgumentException("Offset out of bounds");
832 TextHitInfo hit1
= TextHitInfo
.afterOffset(offset
);
833 TextHitInfo hit2
= hit1
.getOtherHit();
835 TextHitInfo strong
= policy
.getStrongCaret(hit1
, hit2
, this);
836 TextHitInfo next
= getNextLeftHit(strong
);
837 TextHitInfo ret
= null;
840 TextHitInfo next2
= getVisualOtherHit(next
);
841 ret
= policy
.getStrongCaret(next2
, next
, this);
846 public TextHitInfo
getNextLeftHit (TextHitInfo hit
)
849 int index
= hitToCaret(hit
);
850 TextHitInfo next
= null;
854 next
= caretToHit(index
);
859 public TextHitInfo
getNextRightHit(int offset
)
861 return getNextRightHit(offset
, DEFAULT_CARET_POLICY
);
864 public TextHitInfo
getNextRightHit(int offset
, CaretPolicy policy
)
867 throw new IllegalArgumentException("Null policy not allowed");
868 if (offset
< 0 || offset
> length
)
869 throw new IllegalArgumentException("Offset out of bounds");
871 TextHitInfo hit1
= TextHitInfo
.afterOffset(offset
);
872 TextHitInfo hit2
= hit1
.getOtherHit();
874 TextHitInfo next
= getNextRightHit(policy
.getStrongCaret(hit1
, hit2
, this));
875 TextHitInfo ret
= null;
878 TextHitInfo next2
= getVisualOtherHit(next
);
879 ret
= policy
.getStrongCaret(next2
, next
, this);
884 public TextHitInfo
getNextRightHit(TextHitInfo hit
)
887 int index
= hitToCaret(hit
);
888 TextHitInfo next
= null;
892 next
= caretToHit(index
);
897 public Shape
getOutline (AffineTransform tx
)
900 GeneralPath gp
= new GeneralPath();
901 for(int i
= 0; i
< runs
.length
; i
++)
903 GlyphVector gv
= runs
[i
].glyphVector
;
904 gp
.append( gv
.getOutline( x
, 0f
), false );
905 Rectangle2D r
= gv
.getLogicalBounds();
913 public float getVisibleAdvance ()
915 float totalAdvance
= 0f
;
917 if( runs
.length
<= 0 )
920 // No trailing whitespace
921 if( !Character
.isWhitespace( string
[offset
+ length
- 1]) )
924 // Get length of all runs up to the last
925 for(int i
= 0; i
< runs
.length
- 1; i
++)
926 totalAdvance
+= runs
[i
].glyphVector
.getLogicalBounds().getWidth();
928 int lastRun
= runs
[runs
.length
- 1].runStart
;
930 while( j
>= lastRun
&& Character
.isWhitespace( string
[j
] ) ) j
--;
933 return totalAdvance
; // entire last run is whitespace
935 int lastNonWSChar
= j
- lastRun
;
937 while( runs
[ runs
.length
- 1 ].glyphVector
.getGlyphCharIndex( j
)
940 totalAdvance
+= runs
[ runs
.length
- 1 ].glyphVector
941 .getGlyphLogicalBounds( j
)
942 .getBounds2D().getWidth();
949 public Shape
getVisualHighlightShape (TextHitInfo firstEndpoint
,
950 TextHitInfo secondEndpoint
)
952 return getVisualHighlightShape( firstEndpoint
, secondEndpoint
,
956 public Shape
getVisualHighlightShape (TextHitInfo firstEndpoint
,
957 TextHitInfo secondEndpoint
,
960 GeneralPath path
= new GeneralPath(GeneralPath
.WIND_EVEN_ODD
);
961 Shape caret1
= getCaretShape(firstEndpoint
, bounds
);
962 path
.append(caret1
, false);
963 Shape caret2
= getCaretShape(secondEndpoint
, bounds
);
964 path
.append(caret2
, false);
965 // Append left (top) bounds to selection if necessary.
966 int c1
= hitToCaret(firstEndpoint
);
967 int c2
= hitToCaret(secondEndpoint
);
968 if (c1
== 0 || c2
== 0)
970 path
.append(left(bounds
), false);
972 // Append right (bottom) bounds if necessary.
973 if (c1
== length
|| c2
== length
)
975 path
.append(right(bounds
), false);
977 return path
.getBounds2D();
981 * Returns the shape that makes up the left (top) edge of this text layout.
983 * @param b the bounds
985 * @return the shape that makes up the left (top) edge of this text layout
987 private Shape
left(Rectangle2D b
)
989 GeneralPath left
= new GeneralPath(GeneralPath
.WIND_EVEN_ODD
);
990 left
.append(getCaretShape(TextHitInfo
.beforeOffset(0)), false);
993 float y
= (float) b
.getMinY();
994 left
.append(new Line2D
.Float((float) b
.getMinX(), y
,
995 (float) b
.getMaxX(), y
), false);
999 float x
= (float) b
.getMinX();
1000 left
.append(new Line2D
.Float(x
, (float) b
.getMinY(),
1001 x
, (float) b
.getMaxY()), false);
1003 return left
.getBounds2D();
1007 * Returns the shape that makes up the right (bottom) edge of this text
1010 * @param b the bounds
1012 * @return the shape that makes up the right (bottom) edge of this text
1015 private Shape
right(Rectangle2D b
)
1017 GeneralPath right
= new GeneralPath(GeneralPath
.WIND_EVEN_ODD
);
1018 right
.append(getCaretShape(TextHitInfo
.afterOffset(length
)), false);
1021 float y
= (float) b
.getMaxY();
1022 right
.append(new Line2D
.Float((float) b
.getMinX(), y
,
1023 (float) b
.getMaxX(), y
), false);
1027 float x
= (float) b
.getMaxX();
1028 right
.append(new Line2D
.Float(x
, (float) b
.getMinY(),
1029 x
, (float) b
.getMaxY()), false);
1031 return right
.getBounds2D();
1034 public TextHitInfo
getVisualOtherHit (TextHitInfo hit
)
1037 int hitIndex
= hit
.getCharIndex();
1041 if (hitIndex
== -1 || hitIndex
== length
)
1045 if (isLeftToRight() == (hitIndex
== -1))
1048 visual
= length
- 1;
1049 index
= visualToLogical
[visual
];
1050 if (isLeftToRight() == (hitIndex
== -1))
1051 leading
= isCharacterLTR(index
); // LTR.
1053 leading
= ! isCharacterLTR(index
); // RTL.
1058 int visual
= logicalToVisual
[hitIndex
];
1060 if (isCharacterLTR(hitIndex
) == hit
.isLeadingEdge())
1070 if (visual
>= 0 && visual
< length
)
1072 index
= visualToLogical
[visual
];
1073 leading
= b
== isLeftToRight();
1077 index
= b
== isLeftToRight() ? length
: -1;
1078 leading
= index
== length
;
1081 return leading ? TextHitInfo
.leading(index
) : TextHitInfo
.trailing(index
);
1085 * This is a protected method of a <code>final</code> class, meaning
1086 * it exists only to taunt you.
1088 protected void handleJustify (float justificationWidth
)
1090 // We assume that the text has non-trailing whitespace.
1091 // First get the change in width to insert into the whitespaces.
1092 double deltaW
= justificationWidth
- getVisibleAdvance();
1093 int nglyphs
= 0; // # of whitespace chars
1095 // determine last non-whitespace char.
1096 int lastNWS
= offset
+ length
- 1;
1097 while( Character
.isWhitespace( string
[lastNWS
] ) ) lastNWS
--;
1099 // locations of the glyphs.
1100 int[] wsglyphs
= new int[length
* 10];
1101 for(int run
= 0; run
< runs
.length
; run
++ )
1103 Run current
= runs
[run
];
1104 for(int i
= 0; i
< current
.glyphVector
.getNumGlyphs(); i
++ )
1106 int cindex
= current
.runStart
1107 + current
.glyphVector
.getGlyphCharIndex( i
);
1108 if( Character
.isWhitespace( string
[cindex
] ) )
1109 // && cindex < lastNWS )
1111 wsglyphs
[ nglyphs
* 2 ] = run
;
1112 wsglyphs
[ nglyphs
* 2 + 1] = i
;
1117 deltaW
= deltaW
/ nglyphs
; // Change in width per whitespace glyph
1120 // Shift all characters
1121 for(int run
= 0; run
< runs
.length
; run
++ )
1123 Run current
= runs
[run
];
1124 for(int i
= 0; i
< current
.glyphVector
.getNumGlyphs(); i
++ )
1126 if( wsglyphs
[ cws
* 2 ] == run
&& wsglyphs
[ cws
* 2 + 1 ] == i
)
1128 cws
++; // update 'current whitespace'
1129 w
+= deltaW
; // increment the shift
1131 Point2D p
= current
.glyphVector
.getGlyphPosition( i
);
1132 p
.setLocation( p
.getX() + w
, p
.getY() );
1133 current
.glyphVector
.setGlyphPosition( i
, p
);
1138 public TextHitInfo
hitTestChar (float x
, float y
)
1140 return hitTestChar(x
, y
, getNaturalBounds());
1144 * Finds the character hit at the specified point. This 'clips' this
1145 * text layout against the specified <code>bounds</code> rectangle. That
1146 * means that in the case where a point is outside these bounds, this method
1147 * returns the leading edge of the first character or the trailing edge of
1148 * the last character.
1150 * @param x the X location to test
1151 * @param y the Y location to test
1152 * @param bounds the bounds to test against
1154 * @return the character hit at the specified point
1156 public TextHitInfo
hitTestChar (float x
, float y
, Rectangle2D bounds
)
1161 if (y
< bounds
.getMinY())
1162 return TextHitInfo
.leading(0);
1163 else if (y
> bounds
.getMaxY())
1164 return TextHitInfo
.trailing(getCharacterCount() - 1);
1168 if (x
< bounds
.getMinX())
1169 return TextHitInfo
.leading(0);
1170 else if (x
> bounds
.getMaxX())
1171 return TextHitInfo
.trailing(getCharacterCount() - 1);
1174 TextHitInfo hitInfo
= null;
1177 // Search for the run at the location.
1178 // TODO: Perform binary search for maximum efficiency. However, we
1179 // need the run location laid out statically to do that.
1180 int numRuns
= runs
.length
;
1182 for (int i
= 0; i
< numRuns
&& hitRun
== null; i
++)
1185 Rectangle2D lBounds
= run
.glyphVector
.getLogicalBounds();
1186 if (lBounds
.getMinY() + run
.location
<= y
1187 && lBounds
.getMaxY() + run
.location
>= y
)
1190 // Now we have (hopefully) found a run that hits. Now find the
1194 GlyphVector gv
= hitRun
.glyphVector
;
1195 for (int i
= hitRun
.runStart
;
1196 i
< hitRun
.runEnd
&& hitInfo
== null; i
++)
1198 int gi
= i
- hitRun
.runStart
;
1199 Rectangle2D lBounds
= gv
.getGlyphLogicalBounds(gi
)
1201 if (lBounds
.getMinY() + hitRun
.location
<= y
1202 && lBounds
.getMaxY() + hitRun
.location
>= y
)
1204 // Found hit. Now check if we are leading or trailing.
1205 boolean leading
= true;
1206 if (lBounds
.getCenterY() + hitRun
.location
<= y
)
1208 hitInfo
= leading ? TextHitInfo
.leading(i
)
1209 : TextHitInfo
.trailing(i
);
1216 // Search for the run at the location.
1217 // TODO: Perform binary search for maximum efficiency. However, we
1218 // need the run location laid out statically to do that.
1219 int numRuns
= runs
.length
;
1221 for (int i
= 0; i
< numRuns
&& hitRun
== null; i
++)
1224 Rectangle2D lBounds
= run
.glyphVector
.getLogicalBounds();
1225 if (lBounds
.getMinX() + run
.location
<= x
1226 && lBounds
.getMaxX() + run
.location
>= x
)
1229 // Now we have (hopefully) found a run that hits. Now find the
1233 GlyphVector gv
= hitRun
.glyphVector
;
1234 for (int i
= hitRun
.runStart
;
1235 i
< hitRun
.runEnd
&& hitInfo
== null; i
++)
1237 int gi
= i
- hitRun
.runStart
;
1238 Rectangle2D lBounds
= gv
.getGlyphLogicalBounds(gi
)
1240 if (lBounds
.getMinX() + hitRun
.location
<= x
1241 && lBounds
.getMaxX() + hitRun
.location
>= x
)
1243 // Found hit. Now check if we are leading or trailing.
1244 boolean leading
= true;
1245 if (lBounds
.getCenterX() + hitRun
.location
<= x
)
1247 hitInfo
= leading ? TextHitInfo
.leading(i
)
1248 : TextHitInfo
.trailing(i
);
1256 public boolean isLeftToRight ()
1261 public boolean isVertical ()
1263 return false; // FIXME: How do you create a vertical layout?
1266 public int hashCode ()
1268 // This is implemented in sync to equals().
1269 if (hash
== 0 && runs
.length
> 0)
1272 for (int i
= 0; i
< runs
.length
; i
++)
1273 hash ^
= runs
[i
].glyphVector
.hashCode();
1278 public String
toString ()
1280 return "TextLayout [string:"+ new String(string
, offset
, length
)
1286 * Returns the natural bounds of that text layout. This is made up
1287 * of the ascent plus descent and the text advance.
1289 * @return the natural bounds of that text layout
1291 private Rectangle2D
getNaturalBounds()
1293 if (naturalBounds
== null)
1294 naturalBounds
= new Rectangle2D
.Float(0.0F
, -getAscent(), getAdvance(),
1295 getAscent() + getDescent());
1296 return naturalBounds
;
1299 private void checkHitInfo(TextHitInfo hit
)
1302 throw new IllegalArgumentException("Null hit info not allowed");
1303 int index
= hit
.getInsertionIndex();
1304 if (index
< 0 || index
> length
)
1305 throw new IllegalArgumentException("Hit index out of range");
1308 private int hitToCaret(TextHitInfo hit
)
1310 int index
= hit
.getCharIndex();
1313 ret
= isLeftToRight() ?
0 : length
;
1314 else if (index
>= length
)
1315 ret
= isLeftToRight() ? length
: 0;
1318 ret
= logicalToVisual
[index
];
1319 if (hit
.isLeadingEdge() != isCharacterLTR(index
))
1325 private TextHitInfo
caretToHit(int index
)
1328 if (index
== 0 || index
== length
)
1330 if ((index
== length
) == isLeftToRight())
1331 hit
= TextHitInfo
.leading(length
);
1333 hit
= TextHitInfo
.trailing(-1);
1337 int logical
= visualToLogical
[index
];
1338 boolean leading
= isCharacterLTR(logical
); // LTR.
1339 hit
= leading ? TextHitInfo
.leading(logical
)
1340 : TextHitInfo
.trailing(logical
);
1345 private boolean isCharacterLTR(int index
)
1347 byte level
= getCharacterLevel(index
);
1348 return (level
& 1) == 0;
1352 * Finds the run that holds the specified (logical) character index. This
1353 * returns <code>null</code> when the index is not inside the range.
1355 * @param index the index of the character to find
1357 * @return the run that holds the specified character
1359 private Run
findRunAtIndex(int index
)
1362 // TODO: Can we do better than linear searching here?
1363 for (int i
= 0; i
< runs
.length
&& found
== null; i
++)
1366 if (run
.runStart
<= index
&& run
.runEnd
> index
)
1373 * Computes the layout locations for each run.
1375 private void layoutRuns()
1378 float lastWidth
= 0.0F
;
1379 for (int i
= 0; i
< runs
.length
; i
++)
1381 runs
[i
].location
= loc
;
1382 Rectangle2D bounds
= runs
[i
].glyphVector
.getLogicalBounds();
1383 loc
+= isVertical() ? bounds
.getHeight() : bounds
.getWidth();
1388 * Inner class describing a caret policy
1390 public static class CaretPolicy
1392 public CaretPolicy()
1396 public TextHitInfo
getStrongCaret(TextHitInfo hit1
,
1400 byte l1
= layout
.getCharacterLevel(hit1
.getCharIndex());
1401 byte l2
= layout
.getCharacterLevel(hit2
.getCharIndex());
1405 if (hit2
.isLeadingEdge() && ! hit1
.isLeadingEdge())