OSDN Git Service

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