KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > awt > font > TextLayout


1 /*
2  * @(#)TextLayout.java 1.97 04/05/05
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 /*
9  * (C) Copyright Taligent, Inc. 1996 - 1997, All Rights Reserved
10  * (C) Copyright IBM Corp. 1996-2003, All Rights Reserved
11  *
12  * The original version of this source code and documentation is
13  * copyrighted and owned by Taligent, Inc., a wholly-owned subsidiary
14  * of IBM. These materials are provided under terms of a License
15  * Agreement between Taligent and Sun. This technology is protected
16  * by multiple US and International patents.
17  *
18  * This notice and attribution to Taligent may not be removed.
19  * Taligent is a registered trademark of Taligent, Inc.
20  *
21  */

22
23 package java.awt.font;
24
25 import java.awt.Color JavaDoc;
26 import java.awt.Font JavaDoc;
27 import java.awt.Graphics2D JavaDoc;
28 import java.awt.Shape JavaDoc;
29 import java.awt.font.NumericShaper JavaDoc;
30 import java.awt.font.TextLine.TextLineMetrics JavaDoc;
31 import java.awt.geom.AffineTransform JavaDoc;
32 import java.awt.geom.GeneralPath JavaDoc;
33 import java.awt.geom.Point2D JavaDoc;
34 import java.awt.geom.Rectangle2D JavaDoc;
35 import java.text.AttributedString JavaDoc;
36 import java.text.AttributedCharacterIterator JavaDoc;
37 import java.text.AttributedCharacterIterator.Attribute;
38 import java.util.Map JavaDoc;
39 import java.util.HashMap JavaDoc;
40 import java.util.Hashtable JavaDoc;
41 import sun.font.AdvanceCache;
42 import sun.font.CoreMetrics;
43 import sun.font.Decoration;
44 import sun.font.FontLineMetrics;
45 import sun.font.FontResolver;
46 import sun.font.GraphicComponent;
47 import sun.text.CodePointIterator;
48
49 /**
50  *
51  * <code>TextLayout</code> is an immutable graphical representation of styled
52  * character data.
53  * <p>
54  * It provides the following capabilities:
55  * <ul>
56  * <li>implicit bidirectional analysis and reordering,
57  * <li>cursor positioning and movement, including split cursors for
58  * mixed directional text,
59  * <li>highlighting, including both logical and visual highlighting
60  * for mixed directional text,
61  * <li>multiple baselines (roman, hanging, and centered),
62  * <li>hit testing,
63  * <li>justification,
64  * <li>default font substitution,
65  * <li>metric information such as ascent, descent, and advance, and
66  * <li>rendering
67  * </ul>
68  * <p>
69  * A <code>TextLayout</code> object can be rendered using
70  * its <code>draw</code> method.
71  * <p>
72  * <code>TextLayout</code> can be constructed either directly or through
73  * the use of a {@link LineBreakMeasurer}. When constructed directly, the
74  * source text represents a single paragraph. <code>LineBreakMeasurer</code>
75  * allows styled text to be broken into lines that fit within a particular
76  * width. See the <code>LineBreakMeasurer</code> documentation for more
77  * information.
78  * <p>
79  * <code>TextLayout</code> construction logically proceeds as follows:
80  * <ul>
81  * <li>paragraph attributes are extracted and examined,
82  * <li>text is analyzed for bidirectional reordering, and reordering
83  * information is computed if needed,
84  * <li>text is segmented into style runs
85  * <li>fonts are chosen for style runs, first by using a font if the
86  * attribute {@link TextAttribute#FONT} is present, otherwise by computing
87  * a default font using the attributes that have been defined
88  * <li>if text is on multiple baselines, the runs or subruns are further
89  * broken into subruns sharing a common baseline,
90  * <li>glyphvectors are generated for each run using the chosen font,
91  * <li>final bidirectional reordering is performed on the glyphvectors
92  * </ul>
93  * <p>
94  * All graphical information returned from a <code>TextLayout</code>
95  * object's methods is relative to the origin of the
96  * <code>TextLayout</code>, which is the intersection of the
97  * <code>TextLayout</code> object's baseline with its left edge. Also,
98  * coordinates passed into a <code>TextLayout</code> object's methods
99  * are assumed to be relative to the <code>TextLayout</code> object's
100  * origin. Clients usually need to translate between a
101  * <code>TextLayout</code> object's coordinate system and the coordinate
102  * system in another object (such as a
103  * {@link java.awt.Graphics Graphics} object).
104  * <p>
105  * <code>TextLayout</code> objects are constructed from styled text,
106  * but they do not retain a reference to their source text. Thus,
107  * changes in the text previously used to generate a <code>TextLayout</code>
108  * do not affect the <code>TextLayout</code>.
109  * <p>
110  * Three methods on a <code>TextLayout</code> object
111  * (<code>getNextRightHit</code>, <code>getNextLeftHit</code>, and
112  * <code>hitTestChar</code>) return instances of {@link TextHitInfo}.
113  * The offsets contained in these <code>TextHitInfo</code> objects
114  * are relative to the start of the <code>TextLayout</code>, <b>not</b>
115  * to the text used to create the <code>TextLayout</code>. Similarly,
116  * <code>TextLayout</code> methods that accept <code>TextHitInfo</code>
117  * instances as parameters expect the <code>TextHitInfo</code> object's
118  * offsets to be relative to the <code>TextLayout</code>, not to any
119  * underlying text storage model.
120  * <p>
121  * <strong>Examples</strong>:<p>
122  * Constructing and drawing a <code>TextLayout</code> and its bounding
123  * rectangle:
124  * <blockquote><pre>
125  * Graphics2D g = ...;
126  * Point2D loc = ...;
127  * Font font = Font.getFont("Helvetica-bold-italic");
128  * FontRenderContext frc = g.getFontRenderContext();
129  * TextLayout layout = new TextLayout("This is a string", font, frc);
130  * layout.draw(g, (float)loc.getX(), (float)loc.getY());
131  *
132  * Rectangle2D bounds = layout.getBounds();
133  * bounds.setRect(bounds.getX()+loc.getX(),
134  * bounds.getY()+loc.getY(),
135  * bounds.getWidth(),
136  * bounds.getHeight());
137  * g.draw(bounds);
138  * </pre>
139  * </blockquote>
140  * <p>
141  * Hit-testing a <code>TextLayout</code> (determining which character is at
142  * a particular graphical location):
143  * <blockquote><pre>
144  * Point2D click = ...;
145  * TextHitInfo hit = layout.hitTestChar(
146  * (float) (click.getX() - loc.getX()),
147  * (float) (click.getY() - loc.getY()));
148  * </pre>
149  * </blockquote>
150  * <p>
151  * Responding to a right-arrow key press:
152  * <blockquote><pre>
153  * int insertionIndex = ...;
154  * TextHitInfo next = layout.getNextRightHit(insertionIndex);
155  * if (next != null) {
156  * // translate graphics to origin of layout on screen
157  * g.translate(loc.getX(), loc.getY());
158  * Shape[] carets = layout.getCaretShapes(next.getInsertionIndex());
159  * g.draw(carets[0]);
160  * if (carets[1] != null) {
161  * g.draw(carets[1]);
162  * }
163  * }
164  * </pre></blockquote>
165  * <p>
166  * Drawing a selection range corresponding to a substring in the source text.
167  * The selected area may not be visually contiguous:
168  * <blockquote><pre>
169  * // selStart, selLimit should be relative to the layout,
170  * // not to the source text
171  *
172  * int selStart = ..., selLimit = ...;
173  * Color selectionColor = ...;
174  * Shape selection = layout.getLogicalHighlightShape(selStart, selLimit);
175  * // selection may consist of disjoint areas
176  * // graphics is assumed to be tranlated to origin of layout
177  * g.setColor(selectionColor);
178  * g.fill(selection);
179  * </pre></blockquote>
180  * <p>
181  * Drawing a visually contiguous selection range. The selection range may
182  * correspond to more than one substring in the source text. The ranges of
183  * the corresponding source text substrings can be obtained with
184  * <code>getLogicalRangesForVisualSelection()</code>:
185  * <blockquote><pre>
186  * TextHitInfo selStart = ..., selLimit = ...;
187  * Shape selection = layout.getVisualHighlightShape(selStart, selLimit);
188  * g.setColor(selectionColor);
189  * g.fill(selection);
190  * int[] ranges = getLogicalRangesForVisualSelection(selStart, selLimit);
191  * // ranges[0], ranges[1] is the first selection range,
192  * // ranges[2], ranges[3] is the second selection range, etc.
193  * </pre></blockquote>
194  * <p>
195  * @see LineBreakMeasurer
196  * @see TextAttribute
197  * @see TextHitInfo
198  */

199 public final class TextLayout implements Cloneable JavaDoc {
200
201     private int characterCount;
202     private boolean isVerticalLine = false;
203     private byte baseline;
204     private float[] baselineOffsets; // why have these ?
205
private TextLine JavaDoc textLine;
206
207     // cached values computed from GlyphSets and set info:
208
// all are recomputed from scratch in buildCache()
209
private TextLine.TextLineMetrics JavaDoc lineMetrics = null;
210     private float visibleAdvance;
211     private int hashCodeCache;
212
213     /**
214      * temporary optimization
215      */

216     static class OptInfo implements Decoration.Label {
217         private static final float MAGIC_ADVANCE = -12345.67f;
218
219         // cache of required information for TextLine construction
220
private FontRenderContext JavaDoc frc;
221         private char[] chars;
222         private Font JavaDoc font;
223         private CoreMetrics metrics;
224         private Map JavaDoc attrs;
225
226         // deferred initialization
227
private float advance;
228     private Rectangle2D JavaDoc vb;
229         private Decoration decoration;
230         private String JavaDoc str;
231
232         private OptInfo(FontRenderContext JavaDoc frc, char[] chars, Font JavaDoc font, CoreMetrics metrics, Map JavaDoc attrs) {
233             this.frc = frc;
234             this.chars = chars;
235             this.font = font;
236             this.metrics = metrics;
237             this.attrs = attrs;
238
239         if (attrs != null) {
240         this.attrs = new HashMap JavaDoc(attrs); // sigh, need to clone since might change...
241
}
242
243             this.advance = MAGIC_ADVANCE;
244         }
245
246         TextLine JavaDoc createTextLine() {
247             return TextLine.fastCreateTextLine(frc, chars, font, metrics, attrs);
248         }
249
250         float getAdvance() {
251             if (advance == MAGIC_ADVANCE) {
252                 AdvanceCache adv = AdvanceCache.get(font, frc);
253                 advance = adv.getAdvance(chars, 0, chars.length); // we pretested the chars array so no exception here
254
}
255             return advance;
256         }
257
258         // Decoration.Label reqd.
259
public CoreMetrics getCoreMetrics() {
260             return metrics;
261         }
262
263         // Decoration.Label reqd.
264
public Rectangle2D JavaDoc getLogicalBounds() {
265             return new Rectangle2D.Float JavaDoc(0, -metrics.ascent, getAdvance(), metrics.height);
266         }
267
268         // Decoration.Label reqd.
269
public void handleDraw(Graphics2D JavaDoc g2d, float x, float y) {
270             if (str == null) {
271                 str = new String JavaDoc(chars, 0, chars.length);
272             }
273             g2d.drawString(str, x , y);
274         }
275
276         // Decoration.Label reqd.
277
public Rectangle2D JavaDoc handleGetCharVisualBounds(int index) {
278             // not used
279
throw new InternalError JavaDoc();
280         }
281
282         // Decoration.Label reqd.
283
public Rectangle2D JavaDoc handleGetVisualBounds() {
284         AdvanceCache adv = AdvanceCache.get(font, frc);
285         return adv.getVisualBounds(chars, 0, chars.length);
286         }
287
288         // Decoration.Label reqd.
289
public Shape JavaDoc handleGetOutline(float x, float y) {
290             // not used
291
throw new InternalError JavaDoc();
292         }
293
294         // if we could successfully draw, then return true
295
boolean draw(Graphics2D JavaDoc g2d, float x, float y) {
296             // If the frc differs from the graphics frc, we punt to TextLayout because the
297
// metrics might be different...
298
if (g2d.getFontRenderContext().equals(frc)) {
299                 Font JavaDoc oldFont = g2d.getFont();
300                 g2d.setFont(font);
301                     
302                 getDecoration().drawTextAndDecorations(this, g2d, x, y);
303
304                 g2d.setFont(oldFont);
305
306                 return true;
307             }
308             return false;
309         }
310
311     Rectangle2D JavaDoc getVisualBounds() {
312         if (vb == null) {
313         vb = getDecoration().getVisualBounds(this);
314         }
315         return (Rectangle2D JavaDoc)vb.clone();
316     }
317
318     Decoration getDecoration() {
319         if (decoration == null) {
320         if (attrs == null) {
321             decoration = Decoration.getDecoration(null);
322         } else {
323             decoration = Decoration.getDecoration(StyledParagraph.addInputMethodAttrs(attrs));
324         }
325         }
326         return decoration;
327     }
328
329         static OptInfo create(FontRenderContext JavaDoc frc, char[] chars, Font JavaDoc font, CoreMetrics metrics, Map JavaDoc attrs) {
330             // Preflight text to make sure advance cache supports it, otherwise it would throw an exception.
331
// We also need to preflight to make sure we don't require layout. If we limit optimizations to
332
// latin-1 we handle both cases. We could add an additional check for Japanese since currently
333
// it doesn't require layout and the advance cache would be simple, but right now we don't.
334

335             if (!font.isTransformed() && AdvanceCache.supportsText(chars)) {
336         if (attrs == null || attrs.get(TextAttribute.CHAR_REPLACEMENT) == null) {
337             return new OptInfo(frc, chars, font, metrics, attrs);
338         }
339             }
340             return null;
341         }
342     }
343     private OptInfo optInfo;
344
345     /*
346      * TextLayouts are supposedly immutable. If you mutate a TextLayout under
347      * the covers (like the justification code does) you'll need to set this
348      * back to false. Could be replaced with textLine != null <--> cacheIsValid.
349      */

350     private boolean cacheIsValid = false;
351
352
353     // This value is obtained from an attribute, and constrained to the
354
// interval [0,1]. If 0, the layout cannot be justified.
355
private float justifyRatio;
356
357     // If a layout is produced by justification, then that layout
358
// cannot be justified. To enforce this constraint the
359
// justifyRatio of the justified layout is set to this value.
360
private static final float ALREADY_JUSTIFIED = -53.9f;
361
362     // dx and dy specify the distance between the TextLayout's origin
363
// and the origin of the leftmost GlyphSet (TextLayoutComponent,
364
// actually). They were used for hanging punctuation support,
365
// which is no longer implemented. Currently they are both always 0,
366
// and TextLayout is not guaranteed to work with non-zero dx, dy
367
// values right now. They were left in as an aide and reminder to
368
// anyone who implements hanging punctuation or other similar stuff.
369
// They are static now so they don't take up space in TextLayout
370
// instances.
371
private static float dx;
372     private static float dy;
373
374     /*
375      * Natural bounds is used internally. It is built on demand in
376      * getNaturalBounds.
377      */

378     private Rectangle2D JavaDoc naturalBounds = null;
379
380     /*
381      * boundsRect encloses all of the bits this TextLayout can draw. It
382      * is build on demand in getBounds.
383      */

384     private Rectangle2D JavaDoc boundsRect = null;
385
386     /*
387      * flag to supress/allow carets inside of ligatures when hit testing or
388      * arrow-keying
389      */

390     private boolean caretsInLigaturesAreAllowed = false;
391
392     /**
393      * Defines a policy for determining the strong caret location.
394      * This class contains one method, <code>getStrongCaret</code>, which
395      * is used to specify the policy that determines the strong caret in
396      * dual-caret text. The strong caret is used to move the caret to the
397      * left or right. Instances of this class can be passed to
398      * <code>getCaretShapes</code>, <code>getNextLeftHit</code> and
399      * <code>getNextRightHit</code> to customize strong caret
400      * selection.
401      * <p>
402      * To specify alternate caret policies, subclass <code>CaretPolicy</code>
403      * and override <code>getStrongCaret</code>. <code>getStrongCaret</code>
404      * should inspect the two <code>TextHitInfo</code> arguments and choose
405      * one of them as the strong caret.
406      * <p>
407      * Most clients do not need to use this class.
408      */

409     public static class CaretPolicy {
410
411         /**
412          * Constructs a <code>CaretPolicy</code>.
413          */

414          public CaretPolicy() {
415          }
416
417         /**
418          * Chooses one of the specified <code>TextHitInfo</code> instances as
419          * a strong caret in the specified <code>TextLayout</code>.
420          * @param hit1 a valid hit in <code>layout</code>
421          * @param hit2 a valid hit in <code>layout</code>
422          * @param layout the <code>TextLayout</code> in which
423          * <code>hit1</code> and <code>hit2</code> are used
424          * @return <code>hit1</code> or <code>hit2</code>
425          * (or an equivalent <code>TextHitInfo</code>), indicating the
426          * strong caret.
427          */

428         public TextHitInfo JavaDoc getStrongCaret(TextHitInfo JavaDoc hit1,
429                                           TextHitInfo JavaDoc hit2,
430                                           TextLayout JavaDoc layout) {
431
432             // default implmentation just calls private method on layout
433
return layout.getStrongHit(hit1, hit2);
434         }
435     }
436
437     /**
438      * This <code>CaretPolicy</code> is used when a policy is not specified
439      * by the client. With this policy, a hit on a character whose direction
440      * is the same as the line direction is stronger than a hit on a
441      * counterdirectional character. If the characters' directions are
442      * the same, a hit on the leading edge of a character is stronger
443      * than a hit on the trailing edge of a character.
444      */

445     public static final CaretPolicy DEFAULT_CARET_POLICY = new CaretPolicy();
446
447     /**
448      * Constructs a <code>TextLayout</code> from a <code>String</code>
449      * and a {@link Font}. All the text is styled using the specified
450      * <code>Font</code>.
451      * <p>
452      * The <code>String</code> must specify a single paragraph of text,
453      * because an entire paragraph is required for the bidirectional
454      * algorithm.
455      * @param string the text to display
456      * @param font a <code>Font</code> used to style the text
457      * @param frc contains information about a graphics device which is needed
458      * to measure the text correctly.
459      * Text measurements can vary slightly depending on the
460      * device resolution, and attributes such as antialiasing. This
461      * parameter does not specify a translation between the
462      * <code>TextLayout</code> and user space.
463      */

464     public TextLayout(String JavaDoc string, Font JavaDoc font, FontRenderContext JavaDoc frc) {
465
466         if (font == null) {
467             throw new IllegalArgumentException JavaDoc("Null font passed to TextLayout constructor.");
468         }
469
470         if (string == null) {
471             throw new IllegalArgumentException JavaDoc("Null string passed to TextLayout constructor.");
472         }
473
474         if (string.length() == 0) {
475             throw new IllegalArgumentException JavaDoc("Zero length string passed to TextLayout constructor.");
476         }
477
478         char[] text = string.toCharArray();
479         if (sameBaselineUpTo(font, text, 0, text.length) == text.length) {
480             fastInit(text, font, null, frc);
481         } else {
482             AttributedString JavaDoc as = new AttributedString JavaDoc(string);
483             as.addAttribute(TextAttribute.FONT, font);
484             standardInit(as.getIterator(), text, frc);
485         }
486     }
487
488     /**
489      * Constructs a <code>TextLayout</code> from a <code>String</code>
490      * and an attribute set.
491      * <p>
492      * All the text is styled using the provided attributes.
493      * <p>
494      * <code>string</code> must specify a single paragraph of text because an
495      * entire paragraph is required for the bidirectional algorithm.
496      * @param string the text to display
497      * @param attributes the attributes used to style the text
498      * @param frc contains information about a graphics device which is needed
499      * to measure the text correctly.
500      * Text measurements can vary slightly depending on the
501      * device resolution, and attributes such as antialiasing. This
502      * parameter does not specify a translation between the
503      * <code>TextLayout</code> and user space.
504      */

505     public TextLayout(String JavaDoc string, Map JavaDoc<? extends Attribute,?> attributes,
506               FontRenderContext JavaDoc frc)
507     {
508
509         if (string == null) {
510             throw new IllegalArgumentException JavaDoc("Null string passed to TextLayout constructor.");
511         }
512
513         if (attributes == null) {
514             throw new IllegalArgumentException JavaDoc("Null map passed to TextLayout constructor.");
515         }
516
517         if (string.length() == 0) {
518             throw new IllegalArgumentException JavaDoc("Zero length string passed to TextLayout constructor.");
519         }
520
521         char[] text = string.toCharArray();
522         Font JavaDoc font = singleFont(text, 0, text.length, attributes);
523         if (font != null) {
524             fastInit(text, font, attributes, frc);
525         } else {
526             AttributedString JavaDoc as = new AttributedString JavaDoc(string, attributes);
527             standardInit(as.getIterator(), text, frc);
528         }
529     }
530
531     /*
532      * Determines a font for the attributes, and if a single font can render
533      * all the text on one baseline, return it, otherwise null. If the
534      * attributes specify a font, assume it can display all the text without
535      * checking.
536      * If the AttributeSet contains an embedded graphic, return null.
537      */

538     private static Font JavaDoc singleFont(char[] text,
539                                    int start,
540                                    int limit,
541                                    Map JavaDoc attributes) {
542
543         if (attributes.get(TextAttribute.CHAR_REPLACEMENT) != null) {
544             return null;
545         }
546
547         Font JavaDoc font = (Font JavaDoc)attributes.get(TextAttribute.FONT);
548         if (font == null) {
549             if (attributes.get(TextAttribute.FAMILY) != null) {
550                 font = Font.getFont(attributes);
551                 if (font.canDisplayUpTo(text, start, limit) != -1) {
552                     return null;
553                 }
554             } else {
555                 FontResolver resolver = FontResolver.getInstance();
556         CodePointIterator iter = CodePointIterator.create(text, start, limit);
557         int fontIndex = resolver.nextFontRunIndex(iter);
558         if (iter.charIndex() == limit) {
559             font = resolver.getFont(fontIndex, attributes);
560         }
561             }
562         }
563
564         if (sameBaselineUpTo(font, text, start, limit) != limit) {
565             return null;
566         }
567
568         return font;
569     }
570
571     /**
572      * Constructs a <code>TextLayout</code> from an iterator over styled text.
573      * <p>
574      * The iterator must specify a single paragraph of text because an
575      * entire paragraph is required for the bidirectional
576      * algorithm.
577      * @param text the styled text to display
578      * @param frc contains information about a graphics device which is needed
579      * to measure the text correctly.
580      * Text measurements can vary slightly depending on the
581      * device resolution, and attributes such as antialiasing. This
582      * parameter does not specify a translation between the
583      * <code>TextLayout</code> and user space.
584      */

585     public TextLayout(AttributedCharacterIterator JavaDoc text, FontRenderContext JavaDoc frc) {
586
587         if (text == null) {
588             throw new IllegalArgumentException JavaDoc("Null iterator passed to TextLayout constructor.");
589         }
590
591         int start = text.getBeginIndex();
592         int limit = text.getEndIndex();
593         if (start == limit) {
594             throw new IllegalArgumentException JavaDoc("Zero length iterator passed to TextLayout constructor.");
595         }
596
597         int len = limit - start;
598         text.first();
599         char[] chars = new char[len];
600         int n = 0;
601         for (char c = text.first(); c != text.DONE; c = text.next()) {
602             chars[n++] = c;
603         }
604
605         text.first();
606         if (text.getRunLimit() == limit) {
607
608             Map JavaDoc attributes = text.getAttributes();
609             Font JavaDoc font = singleFont(chars, 0, len, attributes);
610             if (font != null) {
611                 fastInit(chars, font, attributes, frc);
612                 return;
613             }
614         }
615
616         standardInit(text, chars, frc);
617     }
618
619     /**
620      * Creates a <code>TextLayout</code> from a {@link TextLine} and
621      * some paragraph data. This method is used by {@link TextMeasurer}.
622      * @param textLine the line measurement attributes to apply to the
623      * the resulting <code>TextLayout</code>
624      * @param baseline the baseline of the text
625      * @param baselineOffsets the baseline offsets for this
626      * <code>TextLayout</code>. This should already be normalized to
627      * <code>baseline</code>
628      * @param justifyRatio <code>0</code> if the <code>TextLayout</code>
629      * cannot be justified; <code>1</code> otherwise.
630      */

631     TextLayout(TextLine JavaDoc textLine,
632                byte baseline,
633                float[] baselineOffsets,
634                float justifyRatio) {
635
636         this.characterCount = textLine.characterCount();
637         this.baseline = baseline;
638         this.baselineOffsets = baselineOffsets;
639         this.textLine = textLine;
640         this.justifyRatio = justifyRatio;
641     }
642
643     /**
644      * Initialize the paragraph-specific data.
645      */

646     private void paragraphInit(byte aBaseline, CoreMetrics lm, Map JavaDoc paragraphAttrs, char[] text) {
647         
648         baseline = aBaseline;
649
650         // normalize to current baseline
651
baselineOffsets = TextLine.getNormalizedOffsets(lm.baselineOffsets, baseline);
652         
653         justifyRatio = TextLine.getJustifyRatio(paragraphAttrs);
654
655     if (paragraphAttrs != null) {
656         Object JavaDoc o = paragraphAttrs.get(TextAttribute.NUMERIC_SHAPING);
657         if (o != null) {
658         try {
659           NumericShaper JavaDoc shaper = (NumericShaper JavaDoc)o;
660           shaper.shape(text, 0, text.length);
661         }
662         catch (ClassCastException JavaDoc e) {
663         }
664         }
665     }
666     }
667
668     /*
669      * the fast init generates a single glyph set. This requires:
670      * all one style
671      * all renderable by one font (ie no embedded graphics)
672      * all on one baseline
673      */

674     private void fastInit(char[] chars, Font JavaDoc font, Map JavaDoc attrs, FontRenderContext JavaDoc frc) {
675         // Object vf = attrs.get(TextAttribute.ORIENTATION);
676
// isVerticalLine = TextAttribute.ORIENTATION_VERTICAL.equals(vf);
677
isVerticalLine = false;
678
679         LineMetrics JavaDoc lm = font.getLineMetrics(chars, 0, chars.length, frc);
680         CoreMetrics cm = CoreMetrics.get(lm);
681         byte glyphBaseline = (byte) cm.baselineIndex;
682
683         if (attrs == null) {
684             baseline = glyphBaseline;
685             baselineOffsets = cm.baselineOffsets;
686             justifyRatio = 1.0f;
687         } else {
688             paragraphInit(glyphBaseline, cm, attrs, chars);
689         }
690
691         characterCount = chars.length;
692
693         optInfo = OptInfo.create(frc, chars, font, cm, attrs);
694         if (optInfo == null) {
695             textLine = TextLine.fastCreateTextLine(frc, chars, font, cm, attrs);
696         }
697     }
698
699     private void initTextLine() {
700         textLine = optInfo.createTextLine();
701         optInfo = null;
702     }
703
704     /*
705      * the standard init generates multiple glyph sets based on style,
706      * renderable, and baseline runs.
707      * @param chars the text in the iterator, extracted into a char array
708      */

709     private void standardInit(AttributedCharacterIterator JavaDoc text, char[] chars, FontRenderContext JavaDoc frc) {
710
711         characterCount = chars.length;
712
713         // set paragraph attributes
714
{
715             // If there's an embedded graphic at the start of the
716
// paragraph, look for the first non-graphic character
717
// and use it and its font to initialize the paragraph.
718
// If not, use the first graphic to initialize.
719

720             Map JavaDoc paragraphAttrs = text.getAttributes();
721
722             boolean haveFont = TextLine.advanceToFirstFont(text);
723
724             if (haveFont) {
725                 Font JavaDoc defaultFont = TextLine.getFontAtCurrentPos(text);
726                 int charsStart = text.getIndex() - text.getBeginIndex();
727                 LineMetrics JavaDoc lm = defaultFont.getLineMetrics(chars, charsStart, charsStart+1, frc);
728                 CoreMetrics cm = CoreMetrics.get(lm);
729                 paragraphInit((byte)cm.baselineIndex, cm, paragraphAttrs, chars);
730             }
731             else {
732                 // hmmm what to do here? Just try to supply reasonable
733
// values I guess.
734

735                 GraphicAttribute JavaDoc graphic = (GraphicAttribute JavaDoc)
736                                 paragraphAttrs.get(TextAttribute.CHAR_REPLACEMENT);
737                 byte defaultBaseline = getBaselineFromGraphic(graphic);
738         CoreMetrics cm = GraphicComponent.createCoreMetrics(graphic);
739                 paragraphInit(defaultBaseline, cm, paragraphAttrs, chars);
740             }
741         }
742
743         textLine = TextLine.standardCreateTextLine(frc, text, chars, baselineOffsets);
744     }
745
746     /*
747      * A utility to rebuild the ascent/descent/leading/advance cache.
748      * You'll need to call this if you clone and mutate (like justification,
749      * editing methods do)
750      */

751     private void ensureCache() {
752         if (!cacheIsValid) {
753             buildCache();
754         }
755     }
756
757     private void buildCache() {
758         if (textLine == null) {
759             initTextLine();
760         }
761
762         lineMetrics = textLine.getMetrics();
763
764         // compute visibleAdvance
765
if (textLine.isDirectionLTR()) {
766
767             int lastNonSpace = characterCount-1;
768             while (lastNonSpace != -1) {
769                 int logIndex = textLine.visualToLogical(lastNonSpace);
770                 if (!textLine.isCharSpace(logIndex)) {
771                     break;
772                 }
773                 else {
774                     --lastNonSpace;
775                 }
776             }
777             if (lastNonSpace == characterCount-1) {
778                 visibleAdvance = lineMetrics.advance;
779             }
780             else if (lastNonSpace == -1) {
781                 visibleAdvance = 0;
782             }
783             else {
784                 int logIndex = textLine.visualToLogical(lastNonSpace);
785                 visibleAdvance = textLine.getCharLinePosition(logIndex)
786                                         + textLine.getCharAdvance(logIndex);
787             }
788         }
789         else {
790
791             int leftmostNonSpace = 0;
792             while (leftmostNonSpace != characterCount) {
793                 int logIndex = textLine.visualToLogical(leftmostNonSpace);
794                 if (!textLine.isCharSpace(logIndex)) {
795                     break;
796                 }
797                 else {
798                     ++leftmostNonSpace;
799                 }
800             }
801             if (leftmostNonSpace == characterCount) {
802                 visibleAdvance = 0;
803             }
804             else if (leftmostNonSpace == 0) {
805                 visibleAdvance = lineMetrics.advance;
806             }
807             else {
808                 int logIndex = textLine.visualToLogical(leftmostNonSpace);
809                 float pos = textLine.getCharLinePosition(logIndex);
810                 visibleAdvance = lineMetrics.advance - pos;
811             }
812         }
813
814         // naturalBounds, boundsRect will be generated on demand
815
naturalBounds = null;
816         boundsRect = null;
817
818         // hashCode will be regenerated on demand
819
hashCodeCache = 0;
820
821         cacheIsValid = true;
822     }
823
824     /**
825      * The 'natural bounds' encloses all the carets the layout can draw.
826      *
827      */

828     private Rectangle2D JavaDoc getNaturalBounds() {
829         ensureCache();
830
831         if (naturalBounds == null) {
832             naturalBounds = textLine.getItalicBounds();
833         }
834
835         return naturalBounds;
836     }
837
838     /**
839      * Creates a copy of this <code>TextLayout</code>.
840      */

841     protected Object JavaDoc clone() {
842         /*
843          * !!! I think this is safe. Once created, nothing mutates the
844          * glyphvectors or arrays. But we need to make sure.
845          * {jbr} actually, that's not quite true. The justification code
846          * mutates after cloning. It doesn't actually change the glyphvectors
847          * (that's impossible) but it replaces them with justified sets. This
848          * is a problem for GlyphIterator creation, since new GlyphIterators
849          * are created by cloning a prototype. If the prototype has outdated
850          * glyphvectors, so will the new ones. A partial solution is to set the
851          * prototypical GlyphIterator to null when the glyphvectors change. If
852          * you forget this one time, you're hosed.
853          */

854         try {
855             return super.clone();
856         }
857         catch (CloneNotSupportedException JavaDoc e) {
858             throw new InternalError JavaDoc();
859         }
860     }
861
862     /*
863      * Utility to throw an expection if an invalid TextHitInfo is passed
864      * as a parameter. Avoids code duplication.
865      */

866     private void checkTextHit(TextHitInfo JavaDoc hit) {
867         if (hit == null) {
868             throw new IllegalArgumentException JavaDoc("TextHitInfo is null.");
869         }
870
871         if (hit.getInsertionIndex() < 0 ||
872             hit.getInsertionIndex() > characterCount) {
873             throw new IllegalArgumentException JavaDoc("TextHitInfo is out of range");
874         }
875     }
876
877     /**
878      * Creates a copy of this <code>TextLayout</code> justified to the
879      * specified width.
880      * <p>
881      * If this <code>TextLayout</code> has already been justified, an
882      * exception is thrown. If this <code>TextLayout</code> object's
883      * justification ratio is zero, a <code>TextLayout</code> identical
884      * to this <code>TextLayout</code> is returned.
885      * @param justificationWidth the width to use when justifying the line.
886      * For best results, it should not be too differ