OSDN Git Service

Merged gcj-eclipse branch to trunk.
[pf3gnuchains/gcc-fork.git] / libjava / classpath / java / awt / font / TextLayout.java
1 /* TextLayout.java --
2    Copyright (C) 2006  Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
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.
10
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.
15
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.
20
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.
25
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. */
37
38
39 package java.awt.font;
40
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;
54
55 /**
56  * @author Sven de Marothy
57  */
58 public final class TextLayout implements Cloneable
59 {
60   /**
61    * Holds the layout data that belongs to one run of characters.
62    */
63   private class Run
64   {
65     /**
66      * The actual glyph vector.
67      */
68     GlyphVector glyphVector;
69
70     /**
71      * The font for this text run.
72      */
73     Font font;
74
75     /**
76      * The start of the run.
77      */
78     int runStart;
79
80     /**
81      * The end of the run.
82      */
83     int runEnd;
84
85     /**
86      * The layout location of the beginning of the run.
87      */
88     float location;
89
90     /**
91      * Initializes the Run instance.
92      *
93      * @param gv the glyph vector
94      * @param start the start index of the run
95      * @param end the end index of the run
96      */
97     Run(GlyphVector gv, Font f, int start, int end)
98     {
99       glyphVector = gv;
100       font = f;
101       runStart = start;
102       runEnd = end;
103     }
104
105     /**
106      * Returns <code>true</code> when this run is left to right,
107      * <code>false</code> otherwise.
108      *
109      * @return <code>true</code> when this run is left to right,
110      *         <code>false</code> otherwise
111      */
112     boolean isLeftToRight()
113     {
114       return (glyphVector.getLayoutFlags() & GlyphVector.FLAG_RUN_RTL) == 0;
115     }
116   }
117
118   /**
119    * The laid out character runs.
120    */
121   private Run[] runs;
122
123   private FontRenderContext frc;
124   private char[] string;
125   private int offset;
126   private int length;
127   private Rectangle2D boundsCache;
128   private LineMetrics lm;
129
130   /**
131    * The total advance of this text layout. This is cache for maximum
132    * performance.
133    */
134   private float totalAdvance = -1F;
135   
136   /**
137    * The cached natural bounds.
138    */
139   private Rectangle2D naturalBounds;
140
141   /**
142    * Character indices.
143    * Fixt index is the glyphvector, second index is the (first) glyph.
144    */
145   private int[][] charIndices;
146
147   /**
148    * Base directionality, determined from the first char.
149    */
150   private boolean leftToRight;
151
152   /**
153    * Whether this layout contains whitespace or not.
154    */
155   private boolean hasWhitespace = false;
156
157   /**
158    * The {@link Bidi} object that is used for reordering and by
159    * {@link #getCharacterLevel(int)}.
160    */
161   private Bidi bidi;
162
163   /**
164    * Mpas the logical position of each individual character in the original
165    * string to its visual position.
166    */
167   private int[] logicalToVisual;
168
169   /**
170    * Maps visual positions of a character to its logical position
171    * in the original string.
172    */
173   private int[] visualToLogical;
174
175   /**
176    * The cached hashCode.
177    */
178   private int hash;
179
180   /**
181    * The default caret policy.
182    */
183   public static final TextLayout.CaretPolicy DEFAULT_CARET_POLICY =
184     new CaretPolicy();
185
186   /**
187    * Constructs a TextLayout.
188    */
189   public TextLayout (String str, Font font, FontRenderContext frc) 
190   {
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);
196
197     // Get base direction and whitespace info
198     getStringProperties();
199
200     if (Bidi.requiresBidi(string, offset, offset + length))
201       {
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);
208
209         runs = new Run[rc];
210         for(int i = 0; i < rc; i++)
211           {
212             int start = bidi.getRunStart(i);
213             int end = bidi.getRunLimit(i);
214             if(start != end) // no empty runs.
215               {
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);
221               }
222           }
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++)
227           {
228             if (runs[i] != null)
229               cleaned.add(runs[i]);
230           }
231         runs = new Run[cleaned.size()];
232         runs = (Run[]) cleaned.toArray(runs);
233       }
234     else
235       {
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 };
241       }
242     setCharIndices();
243     setupMappings();
244     layoutRuns();
245   }
246
247   public TextLayout (String string,
248                      Map<? extends AttributedCharacterIterator.Attribute, ?> attributes,
249                      FontRenderContext frc)  
250   {
251     this( string, new Font( attributes ), frc );
252   }
253
254   public TextLayout (AttributedCharacterIterator text, FontRenderContext frc)
255   {
256     // FIXME: Very rudimentary.
257     this(getText(text), getFont(text), frc);
258   }
259
260   /**
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.
264    */
265   TextLayout(TextLayout t, int startIndex, int endIndex)
266   {
267     frc = t.frc;
268     boundsCache = null;
269     lm = t.lm;
270     leftToRight = t.leftToRight;
271
272     if( endIndex > t.getCharacterCount() )
273       endIndex = t.getCharacterCount();
274     string = t.string;
275     offset = startIndex + offset;
276     length = endIndex - startIndex;
277
278     int startingRun = t.charIndices[startIndex][0];
279     int nRuns = 1 + t.charIndices[endIndex - 1][0] - startingRun;
280
281     runs = new Run[nRuns];
282     for( int i = 0; i < nRuns; i++ )
283       {
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;
291         
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);
296       }
297     runs[nRuns - 1].runEnd = endIndex - 1;
298
299     setCharIndices();
300     setupMappings();
301     determineWhiteSpace();
302     layoutRuns();
303   }
304
305   private void setCharIndices()
306   {
307     charIndices = new int[ getCharacterCount() ][2];
308     int i = 0;
309     int currentChar = 0;
310     for(int run = 0; run < runs.length; run++)
311       {
312         currentChar = -1;
313         Run current = runs[run];
314         GlyphVector gv = current.glyphVector;
315         for( int gi = 0; gi < gv.getNumGlyphs(); gi++)
316           {
317             if( gv.getGlyphCharIndex( gi ) != currentChar )
318               {
319                 charIndices[ i ][0] = run;
320                 charIndices[ i ][1] = gi;
321                 currentChar = gv.getGlyphCharIndex( gi );
322                 i++;
323               }
324           }
325       }
326   }
327
328   /**
329    * Initializes the logicalToVisual and visualToLogial maps.
330    */
331   private void setupMappings()
332   {
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++)
340       {
341         Run run = runs[i];
342         if (run.isLeftToRight())
343           {
344             for (lIndex = run.runStart; lIndex < run.runEnd; lIndex++)
345               {
346                 logicalToVisual[lIndex] = vIndex;
347                 visualToLogical[vIndex] = lIndex;
348                 vIndex++;
349               }
350           }
351         else
352           {
353             for (lIndex = run.runEnd - 1; lIndex >= run.runStart; lIndex--)
354               {
355                 logicalToVisual[lIndex] = vIndex;
356                 visualToLogical[vIndex] = lIndex;
357                 vIndex++;
358               }
359           }
360       }
361   }
362
363   private static String getText(AttributedCharacterIterator iter)
364   {
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();
371   }
372
373   private static Font getFont(AttributedCharacterIterator iter)
374   {
375     Font f = (Font)iter.getAttribute(TextAttribute.FONT);
376     if( f == null )
377       {
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 );
385       }
386     return f;
387   }
388
389   /**
390    * Scan the character run for the first strongly directional character,
391    * which in turn defines the base directionality of the whole layout.
392    */
393   private void getStringProperties()
394   {
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++]) )
401         {
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;
407           
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;
415         }
416     determineWhiteSpace();
417   }
418
419   private void determineWhiteSpace()
420   {
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;
431   }
432
433   protected Object clone ()
434   {
435     return new TextLayout( this, 0, length);
436   }
437
438   public void draw (Graphics2D g2, float x, float y) 
439   {    
440     for(int i = 0; i < runs.length; i++)
441       {
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();
447       }
448   }
449
450   public boolean equals (Object obj)
451   {
452     if( !( obj instanceof TextLayout) )
453       return false;
454
455     return equals( (TextLayout) obj );
456   }
457
458   public boolean equals (TextLayout tl)
459   {
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;
467   }
468
469   public float getAdvance ()
470   {
471     if (totalAdvance == -1F)
472       {
473         totalAdvance = 0f;
474         for(int i = 0; i < runs.length; i++)
475           {
476             Run run = runs[i];
477             GlyphVector gv = run.glyphVector;
478             totalAdvance += gv.getLogicalBounds().getWidth();
479           }
480       }
481     return totalAdvance;
482   }
483
484   public float getAscent ()
485   {
486     return lm.getAscent();
487   }
488
489   public byte getBaseline ()
490   {
491     return (byte)lm.getBaselineIndex();
492   }
493
494   public float[] getBaselineOffsets ()
495   {
496     return lm.getBaselineOffsets();
497   }
498
499   public Shape getBlackBoxBounds (int firstEndpoint, int secondEndpoint)
500   {
501     if( secondEndpoint - firstEndpoint <= 0 )
502       return new Rectangle2D.Float(); // Hmm? 
503
504     if( firstEndpoint < 0 || secondEndpoint > getCharacterCount())
505       return new Rectangle2D.Float();
506
507     GeneralPath gp = new GeneralPath();
508     
509     int ri = charIndices[ firstEndpoint ][0];
510     int gi = charIndices[ firstEndpoint ][1];
511
512     double advance = 0;
513    
514     for( int i = 0; i < ri; i++ )
515       {
516         Run run = runs[i];
517         GlyphVector gv = run.glyphVector;
518         advance += gv.getLogicalBounds().getWidth();
519       }
520     
521     for( int i = ri; i <= charIndices[ secondEndpoint - 1 ][0]; i++ )
522       {
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;
530
531         for( int j = 0; j <= dg; j++ )
532           {
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);
539           }
540
541         advance += gv.getLogicalBounds().getWidth();
542       }
543     return gp;
544   }
545
546   public Rectangle2D getBounds()
547   {
548     if( boundsCache == null )
549       boundsCache = getOutline(new AffineTransform()).getBounds();
550     return boundsCache;
551   }
552
553   public float[] getCaretInfo (TextHitInfo hit)
554   {
555     return getCaretInfo(hit, getNaturalBounds());
556   }
557
558   public float[] getCaretInfo (TextHitInfo hit, Rectangle2D bounds)
559   {
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;
565     
566     if (index >= length)
567       {
568         info[0] = getAdvance();
569         info[1] = 0;
570       }
571     else
572       {
573         if (index < 0)
574           {
575             run = runs[0];
576             index = 0;
577             leading = true;
578           }
579         else
580           run = findRunAtIndex(index);
581
582         int glyphIndex = index - run.runStart;
583         Shape glyphBounds = run.glyphVector.getGlyphLogicalBounds(glyphIndex);
584         Rectangle2D glyphRect = glyphBounds.getBounds2D();
585         if (isVertical())
586           {
587             if (leading)
588               info[0] = (float) glyphRect.getMinY();
589             else
590               info[0] = (float) glyphRect.getMaxY();
591           }
592         else
593           {
594             if (leading)
595               info[0] = (float) glyphRect.getMinX();
596             else
597               info[0] = (float) glyphRect.getMaxX();
598           }
599         info[0] += run.location;
600         info[1] = run.font.getItalicAngle();
601       }
602     return info;
603   }
604
605   public Shape getCaretShape(TextHitInfo hit)
606   {
607     return getCaretShape(hit, getBounds());
608   }
609
610   public Shape getCaretShape(TextHitInfo hit, Rectangle2D bounds)
611   {
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)
619       {
620         // Shift x1 and x2 according to the slope.
621         x1 -= y1 * info[1];
622         x2 -= y2 * info[1];
623       }
624     GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD, 2);
625     path.moveTo(x1, y1);
626     path.lineTo(x2, y2);
627     return path;
628   }
629
630   public Shape[] getCaretShapes(int offset)
631   {
632     return getCaretShapes(offset, getNaturalBounds());
633   }
634
635   public Shape[] getCaretShapes(int offset, Rectangle2D bounds)
636   {
637     return getCaretShapes(offset, bounds, DEFAULT_CARET_POLICY);
638   }
639
640   public Shape[] getCaretShapes(int offset, Rectangle2D bounds,
641                                 CaretPolicy policy)
642   {
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)
651       {
652         carets[0] = getCaretShape(hit1);
653         carets[1] = null; // The RI returns null in this seldom case.
654       }
655     else
656       {
657         Shape caret1 = getCaretShape(hit1);
658         Shape caret2 = getCaretShape(hit2);
659         TextHitInfo strong = policy.getStrongCaret(hit1, hit2, this);
660         if (strong == hit1)
661           {
662             carets[0] = caret1;
663             carets[1] = caret2;
664           }
665         else
666           {
667             carets[0] = caret2;
668             carets[1] = caret1;
669           }
670       }
671     return carets;
672   }
673
674   public int getCharacterCount ()
675   {
676     return length;
677   }
678
679   public byte getCharacterLevel (int index)
680   {
681     byte level;
682     if( bidi == null )
683       level = 0;
684     else
685       level = (byte) bidi.getLevelAt(index);
686     return level;
687   }
688
689   public float getDescent ()
690   {
691     return lm.getDescent();
692   }
693
694   public TextLayout getJustifiedLayout (float justificationWidth)
695   {
696     TextLayout newLayout = (TextLayout)clone();
697
698     if( hasWhitespace )
699       newLayout.handleJustify( justificationWidth );
700
701     return newLayout;
702   }
703
704   public float getLeading ()
705   {
706     return lm.getLeading();
707   }
708
709   public Shape getLogicalHighlightShape (int firstEndpoint, int secondEndpoint)
710   {
711     return getLogicalHighlightShape( firstEndpoint, secondEndpoint, 
712                                      getBounds() );
713   }
714
715   public Shape getLogicalHighlightShape (int firstEndpoint, int secondEndpoint,
716                                          Rectangle2D bounds)
717   {
718     if( secondEndpoint - firstEndpoint <= 0 )
719       return new Rectangle2D.Float(); // Hmm? 
720
721     if( firstEndpoint < 0 || secondEndpoint > getCharacterCount())
722       return new Rectangle2D.Float();
723
724     Rectangle2D r = null;
725     int ri = charIndices[ firstEndpoint ][0];
726     int gi = charIndices[ firstEndpoint ][1];
727
728     double advance = 0;
729    
730     for( int i = 0; i < ri; i++ )
731       advance += runs[i].glyphVector.getLogicalBounds().getWidth();
732
733     for( int i = ri; i <= charIndices[ secondEndpoint - 1 ][0]; i++ )
734       {
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;
742
743         for(; gi <= dg; gi++ )
744           {
745             Rectangle2D r2 = (gv.getGlyphLogicalBounds( gi )).
746               getBounds2D();
747             if( r == null )
748               r = r2;
749             else
750               r = r.createUnion(r2);
751           }
752         gi = 0; // reset glyph index into run for next run.
753
754         advance += gv.getLogicalBounds().getWidth();
755       }
756
757     return r;
758   }
759
760   public int[] getLogicalRangesForVisualSelection (TextHitInfo firstEndpoint,
761                                                    TextHitInfo secondEndpoint)
762   {
763     // Check parameters.
764     checkHitInfo(firstEndpoint);
765     checkHitInfo(secondEndpoint);
766
767     // Convert to visual and order correctly.
768     int start = hitToCaret(firstEndpoint);
769     int end = hitToCaret(secondEndpoint);
770     if (start > end)
771       {
772         // Swap start and end so that end >= start.
773         int temp = start;
774         start = end;
775         end = temp;
776       }
777
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++)
781       {
782         include[visualToLogical[i]] = true;
783       }
784
785     // Count included runs.
786     int numRuns = 0;
787     boolean in = false;
788     for (int i = 0; i < length; i++)
789       {
790         if (include[i] != in) // At each run in/out point we toggle the in var.
791           {
792             in = ! in;
793             if (in) // At each run start we count up.
794               numRuns++;
795           }
796       }
797
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++)
803       {
804         if (include[i] != in)
805           {
806             ranges[index] = i;
807             index++;
808             in = ! in;
809           }
810       }
811     // If the last run ends at the very end, include that last bit too.
812     if (in)
813       ranges[index] = length;
814
815     return ranges;
816   }
817
818   public TextHitInfo getNextLeftHit(int offset)
819   {
820     return getNextLeftHit(offset, DEFAULT_CARET_POLICY);
821   }
822
823   public TextHitInfo getNextLeftHit(int offset, CaretPolicy policy)
824   {
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");
829
830     TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
831     TextHitInfo hit2 = hit1.getOtherHit();
832
833     TextHitInfo strong = policy.getStrongCaret(hit1, hit2, this);
834     TextHitInfo next = getNextLeftHit(strong);
835     TextHitInfo ret = null;
836     if (next != null)
837       {
838         TextHitInfo next2 = getVisualOtherHit(next);
839         ret = policy.getStrongCaret(next2, next, this);
840       }
841     return ret;
842   }
843
844   public TextHitInfo getNextLeftHit (TextHitInfo hit)
845   {
846     checkHitInfo(hit);
847     int index = hitToCaret(hit);
848     TextHitInfo next = null;
849     if (index != 0)
850       {
851         index--;
852         next = caretToHit(index);
853       }
854     return next;
855   }
856
857   public TextHitInfo getNextRightHit(int offset)
858   {
859     return getNextRightHit(offset, DEFAULT_CARET_POLICY);
860   }
861
862   public TextHitInfo getNextRightHit(int offset, CaretPolicy policy)
863   {
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");
868
869     TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
870     TextHitInfo hit2 = hit1.getOtherHit();
871
872     TextHitInfo next = getNextRightHit(policy.getStrongCaret(hit1, hit2, this));
873     TextHitInfo ret = null;
874     if (next != null)
875       {
876         TextHitInfo next2 = getVisualOtherHit(next);
877         ret = policy.getStrongCaret(next2, next, this);
878       }
879     return ret;
880   }
881
882   public TextHitInfo getNextRightHit(TextHitInfo hit)
883   {
884     checkHitInfo(hit);
885     int index = hitToCaret(hit);
886     TextHitInfo next = null;
887     if (index < length)
888       {
889         index++;
890         next = caretToHit(index);
891       }
892     return next;
893   }
894
895   public Shape getOutline (AffineTransform tx)
896   {
897     float x = 0f;
898     GeneralPath gp = new GeneralPath();
899     for(int i = 0; i < runs.length; i++)
900       {
901         GlyphVector gv = runs[i].glyphVector;
902         gp.append( gv.getOutline( x, 0f ), false );
903         Rectangle2D r = gv.getLogicalBounds();
904         x += r.getWidth();
905       }
906     if( tx != null )
907       gp.transform( tx );
908     return gp;
909   }
910
911   public float getVisibleAdvance ()
912   {
913     float totalAdvance = 0f;
914
915     if( runs.length <= 0 )
916       return 0f;
917
918     // No trailing whitespace
919     if( !Character.isWhitespace( string[offset + length - 1]) )
920       return getAdvance();
921
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();
925
926     int lastRun = runs[runs.length - 1].runStart;
927     int j = length - 1;
928     while( j >= lastRun && Character.isWhitespace( string[j] ) ) j--;
929
930     if( j < lastRun )
931       return totalAdvance; // entire last run is whitespace
932
933     int lastNonWSChar = j - lastRun;
934     j = 0;
935     while( runs[ runs.length - 1 ].glyphVector.getGlyphCharIndex( j )
936            <= lastNonWSChar )
937       {
938         totalAdvance += runs[ runs.length - 1 ].glyphVector
939                                                .getGlyphLogicalBounds( j )
940                                                .getBounds2D().getWidth();
941         j ++;
942       }
943     
944     return totalAdvance;
945   }
946
947   public Shape getVisualHighlightShape (TextHitInfo firstEndpoint,
948                                         TextHitInfo secondEndpoint)
949   {
950     return getVisualHighlightShape( firstEndpoint, secondEndpoint, 
951                                     getBounds() );
952   }
953
954   public Shape getVisualHighlightShape (TextHitInfo firstEndpoint,
955                                         TextHitInfo secondEndpoint,
956                                         Rectangle2D bounds)
957   {
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)
967       {
968         path.append(left(bounds), false);
969       }
970     // Append right (bottom) bounds if necessary.
971     if (c1 == length || c2 == length)
972       {
973         path.append(right(bounds), false);
974       }
975     return path.getBounds2D();
976   }
977
978   /**
979    * Returns the shape that makes up the left (top) edge of this text layout.
980    *
981    * @param b the bounds
982    *
983    * @return the shape that makes up the left (top) edge of this text layout
984    */
985   private Shape left(Rectangle2D b)
986   {
987     GeneralPath left = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
988     left.append(getCaretShape(TextHitInfo.beforeOffset(0)), false);
989     if (isVertical())
990       {
991         float y = (float) b.getMinY();
992         left.append(new Line2D.Float((float) b.getMinX(), y,
993                                      (float) b.getMaxX(), y), false);
994       }
995     else
996       {
997         float x = (float) b.getMinX();
998         left.append(new Line2D.Float(x, (float) b.getMinY(),
999                                      x, (float) b.getMaxY()), false);
1000       }
1001     return left.getBounds2D();
1002   }
1003
1004   /**
1005    * Returns the shape that makes up the right (bottom) edge of this text
1006    * layout.
1007    *
1008    * @param b the bounds
1009    *
1010    * @return the shape that makes up the right (bottom) edge of this text
1011    *         layout
1012    */
1013   private Shape right(Rectangle2D b)
1014   {
1015     GeneralPath right = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
1016     right.append(getCaretShape(TextHitInfo.afterOffset(length)), false);
1017     if (isVertical())
1018       {
1019         float y = (float) b.getMaxY();
1020         right.append(new Line2D.Float((float) b.getMinX(), y,
1021                                       (float) b.getMaxX(), y), false);
1022       }
1023     else
1024       {
1025         float x = (float) b.getMaxX();
1026         right.append(new Line2D.Float(x, (float) b.getMinY(),
1027                                       x, (float) b.getMaxY()), false);
1028       }
1029     return right.getBounds2D();
1030   }
1031
1032   public TextHitInfo getVisualOtherHit (TextHitInfo hit)
1033   {
1034     checkHitInfo(hit);
1035     int hitIndex = hit.getCharIndex();
1036
1037     int index;
1038     boolean leading;
1039     if (hitIndex == -1 || hitIndex == length)
1040       {
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.
1052       }
1053     else
1054       {
1055         // Normal case.
1056         int visual = logicalToVisual[hitIndex];
1057         boolean b;
1058         if (isCharacterLTR(hitIndex) == hit.isLeadingEdge())
1059           {
1060             visual--;
1061             b = false;
1062           }
1063         else
1064           {
1065             visual++;
1066             b = true;
1067           }
1068         if (visual >= 0 && visual < length)
1069           {
1070             index = visualToLogical[visual];
1071             leading = b == isLeftToRight();
1072           }
1073         else
1074           {
1075             index = b == isLeftToRight() ? length : -1;
1076             leading = index == length;
1077           }
1078       }
1079     return leading ? TextHitInfo.leading(index) : TextHitInfo.trailing(index);
1080   }
1081
1082   /**
1083    * This is a protected method of a <code>final</code> class, meaning
1084    * it exists only to taunt you.
1085    */
1086   protected void handleJustify (float justificationWidth)
1087   {
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
1092
1093     // determine last non-whitespace char.
1094     int lastNWS = offset + length - 1;
1095     while( Character.isWhitespace( string[lastNWS] ) ) lastNWS--;
1096
1097     // locations of the glyphs.
1098     int[] wsglyphs = new int[length * 10];
1099     for(int run = 0; run < runs.length; run++ )
1100       {
1101       Run current = runs[run];
1102       for(int i = 0; i < current.glyphVector.getNumGlyphs(); i++ )
1103         {
1104           int cindex = current.runStart
1105                        + current.glyphVector.getGlyphCharIndex( i );
1106           if( Character.isWhitespace( string[cindex] ) )
1107             //        && cindex < lastNWS )
1108             {
1109               wsglyphs[ nglyphs * 2 ] = run;
1110               wsglyphs[ nglyphs * 2 + 1] = i;
1111               nglyphs++;
1112             }
1113         }
1114       }
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++ )
1120       {
1121         Run current = runs[run];
1122         for(int i = 0; i < current.glyphVector.getNumGlyphs(); i++ )
1123           {
1124             if( wsglyphs[ cws * 2 ] == run && wsglyphs[ cws * 2 + 1 ] == i )
1125               {
1126                 cws++; // update 'current whitespace'
1127                 w += deltaW; // increment the shift
1128               }
1129             Point2D p = current.glyphVector.getGlyphPosition( i );
1130             p.setLocation( p.getX() + w, p.getY() );
1131             current.glyphVector.setGlyphPosition( i, p );
1132           }
1133       }
1134   }
1135
1136   public TextHitInfo hitTestChar (float x, float y)
1137   {
1138     return hitTestChar(x, y, getNaturalBounds());
1139   }
1140
1141   /**
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.
1147    *
1148    * @param x the X location to test
1149    * @param y the Y location to test
1150    * @param bounds the bounds to test against
1151    *
1152    * @return the character hit at the specified point
1153    */
1154   public TextHitInfo hitTestChar (float x, float y, Rectangle2D bounds)
1155   {
1156     // Check bounds.
1157     if (isVertical())
1158       {
1159         if (y < bounds.getMinY())
1160           return TextHitInfo.leading(0);
1161         else if (y > bounds.getMaxY())
1162           return TextHitInfo.trailing(getCharacterCount() - 1);
1163       }
1164     else
1165       {
1166         if (x < bounds.getMinX())
1167           return TextHitInfo.leading(0);
1168         else if (x > bounds.getMaxX())
1169           return TextHitInfo.trailing(getCharacterCount() - 1);
1170       }
1171
1172     TextHitInfo hitInfo = null;
1173     if (isVertical())
1174       {
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++)
1181           {
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;
1187           }
1188         // Now we have (hopefully) found a run that hits. Now find the
1189         // right character.
1190         if (hitRun != null)
1191           {
1192             GlyphVector gv = hitRun.glyphVector;
1193             for (int i = hitRun.runStart;
1194                  i < hitRun.runEnd && hitInfo == null; i++)
1195               {
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)
1201                   {
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);
1208                   }
1209               }
1210           }
1211       }
1212     else
1213       {
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++)
1220           {
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;
1226           }
1227         // Now we have (hopefully) found a run that hits. Now find the
1228         // right character.
1229         if (hitRun != null)
1230           {
1231             GlyphVector gv = hitRun.glyphVector;
1232             for (int i = hitRun.runStart;
1233                  i < hitRun.runEnd && hitInfo == null; i++)
1234               {
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)
1240                   {
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);
1247                   }
1248               }
1249           }
1250       }
1251     return hitInfo;
1252   }
1253
1254   public boolean isLeftToRight ()
1255   {
1256     return leftToRight;
1257   }
1258
1259   public boolean isVertical ()
1260   {
1261     return false; // FIXME: How do you create a vertical layout?
1262   }
1263
1264   public int hashCode ()
1265   {
1266     // This is implemented in sync to equals().
1267     if (hash == 0 && runs.length > 0)
1268       {
1269         hash = runs.length;
1270         for (int i = 0; i < runs.length; i++)
1271           hash ^= runs[i].glyphVector.hashCode();
1272       }
1273     return hash;
1274   }
1275
1276   public String toString ()
1277   {
1278     return "TextLayout [string:"+ new String(string, offset, length)
1279     +" Rendercontext:"+
1280       frc+"]";
1281   }
1282
1283   /**
1284    * Returns the natural bounds of that text layout. This is made up
1285    * of the ascent plus descent and the text advance.
1286    *
1287    * @return the natural bounds of that text layout
1288    */
1289   private Rectangle2D getNaturalBounds()
1290   {
1291     if (naturalBounds == null)
1292       naturalBounds = new Rectangle2D.Float(0.0F, -getAscent(), getAdvance(),
1293                                             getAscent() + getDescent());
1294     return naturalBounds;
1295   }
1296
1297   private void checkHitInfo(TextHitInfo hit)
1298   {
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");
1304   }
1305
1306   private int hitToCaret(TextHitInfo hit)
1307   {
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
1315       {
1316         ret = logicalToVisual[index];
1317         if (hit.isLeadingEdge() != isCharacterLTR(index))
1318           ret++;
1319       }
1320     return ret;
1321   }
1322
1323   private TextHitInfo caretToHit(int index)
1324   {
1325     TextHitInfo hit;
1326     if (index == 0 || index == length)
1327       {
1328         if ((index == length) == isLeftToRight())
1329           hit = TextHitInfo.leading(length);
1330         else
1331           hit = TextHitInfo.trailing(-1);
1332       }
1333     else
1334       {
1335         int logical = visualToLogical[index];
1336         boolean leading = isCharacterLTR(logical); // LTR.
1337         hit = leading ? TextHitInfo.leading(logical)
1338                       : TextHitInfo.trailing(logical);
1339       }
1340     return hit;
1341   }
1342
1343   private boolean isCharacterLTR(int index)
1344   {
1345     byte level = getCharacterLevel(index);
1346     return (level & 1) == 0;
1347   }
1348
1349   /**
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.
1352    *
1353    * @param index the index of the character to find
1354    *
1355    * @return the run that holds the specified character
1356    */
1357   private Run findRunAtIndex(int index)
1358   {
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++)
1362       {
1363         Run run = runs[i];
1364         if (run.runStart <= index && run.runEnd > index)
1365           found = run;
1366       }
1367     return found;
1368   }
1369
1370   /**
1371    * Computes the layout locations for each run.
1372    */
1373   private void layoutRuns()
1374   {
1375     float loc = 0.0F;
1376     float lastWidth = 0.0F;
1377     for (int i = 0; i < runs.length; i++)
1378       {
1379         runs[i].location = loc;
1380         Rectangle2D bounds = runs[i].glyphVector.getLogicalBounds();
1381         loc += isVertical() ? bounds.getHeight() : bounds.getWidth();
1382       }
1383   }
1384
1385   /**
1386    * Inner class describing a caret policy
1387    */
1388   public static class CaretPolicy
1389   {
1390     public CaretPolicy()
1391     {
1392     }
1393
1394     public TextHitInfo getStrongCaret(TextHitInfo hit1,
1395                                       TextHitInfo hit2,
1396                                       TextLayout layout)
1397     {
1398       byte l1 = layout.getCharacterLevel(hit1.getCharIndex());
1399       byte l2 = layout.getCharacterLevel(hit2.getCharIndex());
1400       TextHitInfo strong;
1401       if (l1 == l2)
1402         {
1403           if (hit2.isLeadingEdge() && ! hit1.isLeadingEdge())
1404             strong = hit2;
1405           else
1406             strong = hit1;
1407         }
1408       else
1409         {
1410           if (l1 < l2)
1411             strong = hit1;
1412           else
1413             strong = hit2;
1414         }
1415       return strong;
1416     }
1417   }
1418 }
1419
1420