KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > batik > gvt > renderer > StrokingTextPainter


1 /*
2
3    Copyright 1999-2003 The Apache Software Foundation
4
5    Licensed under the Apache License, Version 2.0 (the "License");
6    you may not use this file except in compliance with the License.
7    You may obtain a copy of the License at
8
9        http://www.apache.org/licenses/LICENSE-2.0
10
11    Unless required by applicable law or agreed to in writing, software
12    distributed under the License is distributed on an "AS IS" BASIS,
13    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14    See the License for the specific language governing permissions and
15    limitations under the License.
16
17 */

18
19 package org.apache.batik.gvt.renderer;
20
21 import java.awt.Composite JavaDoc;
22 import java.awt.Graphics2D JavaDoc;
23 import java.awt.Paint JavaDoc;
24 import java.awt.RenderingHints JavaDoc;
25 import java.awt.Shape JavaDoc;
26 import java.awt.Stroke JavaDoc;
27 import java.awt.font.FontRenderContext JavaDoc;
28 import java.awt.font.TextAttribute JavaDoc;
29 import java.awt.geom.GeneralPath JavaDoc;
30 import java.awt.geom.Point2D JavaDoc;
31 import java.awt.geom.Rectangle2D JavaDoc;
32 import java.text.AttributedCharacterIterator JavaDoc;
33 import java.text.AttributedString JavaDoc;
34 import java.text.CharacterIterator JavaDoc;
35 import java.util.ArrayList JavaDoc;
36 import java.util.HashSet JavaDoc;
37 import java.util.Iterator JavaDoc;
38 import java.util.List JavaDoc;
39 import java.util.Set JavaDoc;
40 import java.util.Vector JavaDoc;
41
42 import org.apache.batik.gvt.TextNode;
43 import org.apache.batik.gvt.TextPainter;
44 import org.apache.batik.gvt.font.FontFamilyResolver;
45 import org.apache.batik.gvt.font.GVTFont;
46 import org.apache.batik.gvt.font.GVTFontFamily;
47 import org.apache.batik.gvt.font.GVTGlyphMetrics;
48 import org.apache.batik.gvt.font.UnresolvedFontFamily;
49 import org.apache.batik.gvt.text.AttributedCharacterSpanIterator;
50 import org.apache.batik.gvt.text.BidiAttributedCharacterIterator;
51 import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
52 import org.apache.batik.gvt.text.Mark;
53 import org.apache.batik.gvt.text.TextHit;
54 import org.apache.batik.gvt.text.TextPath;
55 import org.apache.batik.gvt.text.TextPaintInfo;
56 import org.apache.batik.gvt.text.TextSpanLayout;
57
58
59 /**
60  * More sophisticated implementation of TextPainter which
61  * renders the attributed character iterator of a <tt>TextNode</tt>.
62  * <em>StrokingTextPainter includes support for stroke, fill, opacity,
63  * text-decoration, and other attributes.</em>
64  *
65  * @see org.apache.batik.gvt.TextPainter
66  * @see org.apache.batik.gvt.text.GVTAttributedCharacterIterator
67  *
68  * @author <a HREF="mailto:bill.haneman@ireland.sun.com">Bill Haneman</a>
69  * @version $Id: StrokingTextPainter.java,v 1.56 2005/03/27 08:58:34 cam Exp $
70  */

71 public class StrokingTextPainter extends BasicTextPainter {
72
73     public static final
74         AttributedCharacterIterator.Attribute JavaDoc PAINT_INFO =
75         GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO;
76
77     public static final
78         AttributedCharacterIterator.Attribute JavaDoc FLOW_REGIONS =
79         GVTAttributedCharacterIterator.TextAttribute.FLOW_REGIONS;
80
81     public static final
82         AttributedCharacterIterator.Attribute JavaDoc FLOW_PARAGRAPH =
83         GVTAttributedCharacterIterator.TextAttribute.FLOW_PARAGRAPH;
84
85     public static final
86         AttributedCharacterIterator.Attribute JavaDoc TEXT_COMPOUND_DELIMITER
87         = GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_DELIMITER;
88
89     public static final
90         AttributedCharacterIterator.Attribute JavaDoc GVT_FONT
91         = GVTAttributedCharacterIterator.TextAttribute.GVT_FONT;
92
93     public static final
94         AttributedCharacterIterator.Attribute JavaDoc GVT_FONT_FAMILIES
95         = GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES;
96
97     public static final
98         AttributedCharacterIterator.Attribute JavaDoc BIDI_LEVEL
99         = GVTAttributedCharacterIterator.TextAttribute.BIDI_LEVEL;
100
101     public static final
102         AttributedCharacterIterator.Attribute JavaDoc XPOS
103         = GVTAttributedCharacterIterator.TextAttribute.X;
104
105     public static final
106         AttributedCharacterIterator.Attribute JavaDoc YPOS
107         = GVTAttributedCharacterIterator.TextAttribute.Y;
108
109     public static final
110         AttributedCharacterIterator.Attribute JavaDoc TEXTPATH
111         = GVTAttributedCharacterIterator.TextAttribute.TEXTPATH;
112
113
114     public static final AttributedCharacterIterator.Attribute JavaDoc WRITING_MODE
115         = GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE;
116
117     public static final Integer JavaDoc WRITING_MODE_TTB
118         = GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_TTB;
119
120     public static final Integer JavaDoc WRITING_MODE_RTL
121         = GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_RTL;
122
123     public static final
124         AttributedCharacterIterator.Attribute JavaDoc ANCHOR_TYPE
125         = GVTAttributedCharacterIterator.TextAttribute.ANCHOR_TYPE;
126
127     public static final Integer JavaDoc ADJUST_SPACING =
128         GVTAttributedCharacterIterator.TextAttribute.ADJUST_SPACING;
129     public static final Integer JavaDoc ADJUST_ALL =
130         GVTAttributedCharacterIterator.TextAttribute.ADJUST_ALL;
131     public static final GVTAttributedCharacterIterator.TextAttribute ALT_GLYPH_HANDLER =
132         GVTAttributedCharacterIterator.TextAttribute.ALT_GLYPH_HANDLER;
133
134     static Set JavaDoc extendedAtts = new HashSet JavaDoc();
135
136     static {
137         extendedAtts.add(FLOW_PARAGRAPH);
138         extendedAtts.add(TEXT_COMPOUND_DELIMITER);
139         extendedAtts.add(GVT_FONT);
140         // extendedAtts.add(BIDI_LEVEL);
141
}
142
143     /**
144      * A unique instance of this class.
145      */

146     protected static TextPainter singleton = new StrokingTextPainter();
147
148     /**
149      * Returns a unique instance of this class.
150      */

151     public static TextPainter getInstance() {
152     return singleton;
153     }
154
155     /**
156      * Paints the specified text node using the specified Graphics2D.
157      *
158      * @param node the text node to paint
159      * @param g2d the Graphics2D to use
160      */

161     public void paint(TextNode node, Graphics2D JavaDoc g2d) {
162         AttributedCharacterIterator JavaDoc aci;
163         aci = node.getAttributedCharacterIterator();
164
165         List JavaDoc textRuns = getTextRuns(node, aci);
166
167         // draw the underline and overline first, then the actual text
168
// and finally the strikethrough
169
paintDecorations(textRuns, g2d, TextSpanLayout.DECORATION_UNDERLINE);
170         paintDecorations(textRuns, g2d, TextSpanLayout.DECORATION_OVERLINE);
171         paintTextRuns(textRuns, g2d);
172         paintDecorations
173             (textRuns, g2d, TextSpanLayout.DECORATION_STRIKETHROUGH);
174     }
175
176     protected void printAttrs(AttributedCharacterIterator JavaDoc aci) {
177         aci.first();
178         int start = aci.getBeginIndex();
179         System.out.print("AttrRuns: ");
180         while (aci.current() != CharacterIterator.DONE) {
181             int end = aci.getRunLimit();
182             System.out.print(""+(end-start)+", ");
183             aci.setIndex(end);
184             start = end;
185         }
186         System.out.println("");
187     }
188
189     // static long reorderTime, fontMatchingTime, layoutTime;
190
public List JavaDoc getTextRuns(TextNode node, AttributedCharacterIterator JavaDoc aci) {
191         List JavaDoc textRuns = node.getTextRuns();
192         if (textRuns != null) {
193             return textRuns;
194         }
195
196         AttributedCharacterIterator JavaDoc[] chunkACIs = getTextChunkACIs(aci);
197         textRuns = computeTextRuns(node, aci, chunkACIs);
198
199         // t1 = System.currentTimeMillis();
200
// layoutTime += t1-t0;
201
// System.out.println("Reorder: " + reorderTime + " FontMatching: " + fontMatchingTime + " Layout: " + layoutTime);
202
// cache the textRuns so don't need to recalculate
203
node.setTextRuns(textRuns);
204         return textRuns;
205    }
206
207     public List JavaDoc computeTextRuns(TextNode node,
208                                 AttributedCharacterIterator JavaDoc aci,
209                                 AttributedCharacterIterator JavaDoc [] chunkACIs) {
210         int [][] chunkCharMaps = new int[chunkACIs.length][];
211
212         // long t0, t1;
213
// t0 = System.currentTimeMillis();
214
// reorder each chunk ACI for bidi text
215
int chunkStart = aci.getBeginIndex();
216         for (int i = 0; i < chunkACIs.length; i++) {
217             BidiAttributedCharacterIterator iter;
218             iter = new BidiAttributedCharacterIterator
219                 (chunkACIs[i], fontRenderContext, chunkStart);
220             chunkACIs [i] = iter;
221             chunkCharMaps[i] = iter.getCharMap();
222             // t1 = System.currentTimeMillis();
223
// reorderTime += t1-t0;
224
// t0=t1;
225
chunkACIs [i] = createModifiedACIForFontMatching
226                 (node, chunkACIs[i]);
227             
228             chunkStart += (chunkACIs[i].getEndIndex()-
229                            chunkACIs[i].getBeginIndex());
230             // t1 = System.currentTimeMillis();
231
// fontMatchingTime += t1-t0;
232
// t0 = t1;
233
}
234
235         // create text runs for each chunk and add them to the list
236
List JavaDoc textRuns = new ArrayList JavaDoc();
237         TextChunk chunk, prevChunk=null;
238         int currentChunk = 0;
239
240         Point2D JavaDoc location = node.getLocation();
241         do {
242         // Text Chunks contain one or more TextRuns, which they
243
// create from the ACI.
244
chunkACIs[currentChunk].first();
245
246             chunk = getTextChunk(node,
247                                  chunkACIs[currentChunk],
248                                  chunkCharMaps[currentChunk],
249                                  textRuns,
250                                  prevChunk);
251             
252             // Adjust according to text-anchor property value
253
chunkACIs[currentChunk].first();
254             if (chunk != null) {
255                 location = adjustChunkOffsets(location, textRuns, chunk);
256             }
257             prevChunk = chunk;
258             currentChunk++;
259         
260         } while (chunk != null && currentChunk < chunkACIs.length);
261
262         return textRuns;
263     }
264
265     /**
266      * Returns an array of ACIs, one for each text chunk within the given
267      * text node.
268      */

269     protected AttributedCharacterIterator JavaDoc[] getTextChunkACIs
270         (AttributedCharacterIterator JavaDoc aci) {
271
272         List JavaDoc aciList = new ArrayList JavaDoc();
273         int chunkStartIndex = aci.getBeginIndex();
274         aci.first();
275         Object JavaDoc writingMode = aci.getAttribute(WRITING_MODE);
276         boolean vertical = (writingMode == WRITING_MODE_TTB);
277
278         while (aci.setIndex(chunkStartIndex) != CharacterIterator.DONE) {
279             TextPath prevTextPath = null;
280             for (int start=chunkStartIndex, end=0;
281                  aci.setIndex(start) != CharacterIterator.DONE; start=end) {
282
283                 TextPath textPath = (TextPath) aci.getAttribute(TEXTPATH);
284
285                 if (start != chunkStartIndex) {
286                     // If we aren't the first composite in a chunck see
287
// if we need to form a new TextChunk...
288
// We only create new chunks when given an absolute
289
// location in progression direction [Spec says
290
// to do it for either but this doesn't make sense].
291
if (vertical) {
292                         Float JavaDoc runY = (Float JavaDoc) aci.getAttribute(YPOS);
293                         // Check for absolute location in layout direction.
294
if ((runY != null) && !runY.isNaN())
295                             break; // If so end of chunk...
296
} else {
297                         Float JavaDoc runX = (Float JavaDoc) aci.getAttribute(XPOS);
298                         // Check for absolute location in layout direction.
299
if ((runX != null) && !runX.isNaN())
300                             break; // If so end of chunk...
301
}
302
303                     // Do additional check for the start of a textPath
304
if ((prevTextPath == null) && (textPath != null))
305                         break; // If so end of chunk.
306

307                     // Form a new chunk at the end of a text path.
308
// [ This is not mentioned in the spec but makes
309
// sense].
310
if ((prevTextPath != null) && (textPath == null))
311                         break;
312                 }
313
314                 prevTextPath = textPath;
315
316                 // We need to text chunk based on flow paragraphs.
317
// This prevents BIDI reordering across paragraphs.
318
if (aci.getAttribute(FLOW_PARAGRAPH) != null) {
319                     end = aci.getRunLimit(FLOW_PARAGRAPH);
320                     // System.out.println("End: " + end);
321
aci.setIndex(end);
322                     break;
323                 }
324
325                 // find end of compound.
326
end = aci.getRunLimit(TEXT_COMPOUND_DELIMITER);
327
328                 if (start != chunkStartIndex)
329                     // If we aren't starting a new chunk then we know
330
// we don't have any absolute positioning so there
331
// is no reason to consider spliting the chunk further.
332
continue;
333                 
334                 // We are starting a new chunk
335
// So check if we need to split it further...
336
TextNode.Anchor anchor;
337                 anchor = (TextNode.Anchor) aci.getAttribute(ANCHOR_TYPE);
338                 if (anchor == TextNode.Anchor.START)
339                     continue;
340
341                 // We need to check if we have a list of X's & Y's if
342
// so we need to create TextChunk ACI's for each char
343
// (technically we have to do this for
344
// text-anchor:start as well but since that is the
345
// default layout it doesn't matter in that case.
346
if (vertical) {
347                     Float JavaDoc runY = (Float JavaDoc) aci.getAttribute(YPOS);
348                     // Check for absolute location in layout direction.
349
if ((runY == null) || runY.isNaN())
350                         // No absolute positioning in text direction continue
351
continue;
352                 } else {
353                     Float JavaDoc runX = (Float JavaDoc) aci.getAttribute(XPOS);
354                     // Check for absolute location in layout direction.
355
if ((runX == null) || runX.isNaN())
356                         // No absolute positioning in text direction continue
357
continue;
358                 }
359
360                 // Splitting the compound into one char chunks until
361
// we run out of Xs.
362
for (int i=start+1; i< end; i++) {
363                     aci.setIndex(i);
364                     if (vertical) {
365                         Float JavaDoc runY = (Float JavaDoc) aci.getAttribute(YPOS);
366                         if ((runY == null) || runY.isNaN())
367                             break;
368                     } else {
369                         Float JavaDoc runX = (Float JavaDoc) aci.getAttribute(XPOS);
370                         if ((runX == null) || runX.isNaN())
371                             break;
372                     }
373                     aciList.add(new AttributedCharacterSpanIterator
374                         (aci, i-1, i));
375                     chunkStartIndex = i;
376                 }
377             }
378             
379             // found the end of a text chunck
380
int chunkEndIndex = aci.getIndex();
381             // System.out.println("Bounds: " + chunkStartIndex +
382
// "," + chunkEndIndex);
383
aciList.add(new AttributedCharacterSpanIterator
384                 (aci, chunkStartIndex, chunkEndIndex));
385
386             chunkStartIndex = chunkEndIndex;
387         }
388
389         // copy the text chunks into an array
390
AttributedCharacterIterator JavaDoc[] aciArray =
391             new AttributedCharacterIterator JavaDoc[aciList.size()];
392         Iterator JavaDoc iter = aciList.iterator();
393         for (int i=0; iter.hasNext(); ++i) {
394             aciArray[i] = (AttributedCharacterIterator JavaDoc)iter.next();
395         }
396         return aciArray;
397     }
398
399     /**
400      * Returns a new AttributedCharacterIterator that contains resolved GVTFont
401      * attributes. This is then used when creating the text runs so that the
402      * text can be split on changes of font as well as tspans and trefs.
403      *
404      * @param node The text node that the aci belongs to.
405      * @param aci The aci to be modified should already be split into
406      * text chunks.
407      *
408      * @return The new modified aci.
409      */

410     protected AttributedCharacterIterator JavaDoc createModifiedACIForFontMatching
411         (TextNode node, AttributedCharacterIterator JavaDoc aci) {
412
413         aci.first();
414         AttributedString JavaDoc as = null;
415         int asOff = 0;
416         int begin = aci.getBeginIndex();
417         boolean moreChunks = true;
418         int start, end = aci.getRunStart(TEXT_COMPOUND_DELIMITER);
419         while (moreChunks) {
420             start = end;
421             end = aci.getRunLimit(TEXT_COMPOUND_DELIMITER);
422             int aciLength = end-start;
423
424             List JavaDoc fontFamilies;
425             fontFamilies = (List JavaDoc)aci.getAttributes().get(GVT_FONT_FAMILIES);
426
427             if (fontFamilies == null ) {
428                 // no font families set this chunk so just increment...
429
asOff += aciLength;
430                 moreChunks = (aci.setIndex(end) != AttributedCharacterIterator.DONE);
431                 continue;
432             }
433
434             // resolve any unresolved font families in the list
435
List JavaDoc resolvedFontFamilies = new ArrayList JavaDoc(fontFamilies.size());
436             for (int i = 0; i < fontFamilies.size(); i++) {
437                 GVTFontFamily fontFamily = (GVTFontFamily)fontFamilies.get(i);
438                 if (fontFamily instanceof UnresolvedFontFamily) {
439                     fontFamily = FontFamilyResolver.resolve
440                         ((UnresolvedFontFamily)fontFamily);
441                 }
442                 if (fontFamily != null) // Add font family if resolved
443
resolvedFontFamilies.add(fontFamily);
444             }
445
446             // if could not resolve at least one of the fontFamilies
447
// then use the default font
448
if (resolvedFontFamilies.size() == 0) {
449                 resolvedFontFamilies.add(FontFamilyResolver.defaultFont);
450             }
451
452             // create a list of fonts of the correct size
453
float fontSize = 12;
454             Float JavaDoc fsFloat = (Float JavaDoc)aci.getAttributes().get(TextAttribute.SIZE);
455             if (fsFloat != null) {
456                 fontSize = fsFloat.floatValue();
457             }
458
459             // now for each char or group of chars in the string,
460
// find a font that can display it.
461
boolean[] fontAssigned = new boolean[aciLength];
462
463             if (as == null)
464                 as = new AttributedString JavaDoc(aci);
465
466             GVTFont defaultFont = null;
467             int numSet=0;
468             int firstUnset=start;
469             boolean firstUnsetSet;
470             for (int i = 0; i < resolvedFontFamilies.size(); i++) {
471                 // assign this font to all characters it can display if it has
472
// not already been assigned
473
int currentIndex = firstUnset;
474                 firstUnsetSet = false;
475                 aci.setIndex(currentIndex);
476
477                 GVTFontFamily ff;
478                 ff = ((GVTFontFamily)resolvedFontFamilies.get(i));
479                 GVTFont font = ff.deriveFont(fontSize, aci);
480                 if (defaultFont == null)
481                     defaultFont = font;
482
483                 while (currentIndex < end) {
484                     int displayUpToIndex = font.canDisplayUpTo
485                         (aci, currentIndex, end);
486
487                     Object JavaDoc altGlyphElement = aci.getAttributes().get(ALT_GLYPH_HANDLER);
488                     if ( altGlyphElement != null ){
489                         //found all the glyph to be displayed
490
//consider the font matching done
491
displayUpToIndex = -1;
492                     }
493
494                     if (displayUpToIndex == -1) {
495                         // Can handle the whole thing...
496
displayUpToIndex = end;
497                     }
498
499                     if (displayUpToIndex <= currentIndex) {
500                         if (!firstUnsetSet) {
501                             firstUnset = currentIndex;
502                             firstUnsetSet = true;
503                         }
504                         // couldn't display the current char
505
currentIndex++;
506                     } else {
507                         // could display some text, so for each
508
// char it can display, if char not already
509
// assigned a font, assign this font to it
510
int runStart = -1;
511                         for (int j = currentIndex; j < displayUpToIndex; j++) {
512                             if (fontAssigned[j - start]) {
513                                 if (runStart != -1) {
514                     // System.out.println("Font 1: " + font);
515
as.addAttribute(GVT_FONT, font,
516                                                     runStart-begin, j-begin);
517                                     runStart=-1;
518                                 }
519                             } else {
520                                 if (runStart == -1)
521                                     runStart = j;
522                             }
523                             fontAssigned[j - start] = true;
524                             numSet++;
525                         }
526                         if (runStart != -1) {
527                 // System.out.println("Font 2: " + font);
528
as.addAttribute(GVT_FONT, font,
529                                             runStart-begin,
530                                             displayUpToIndex-begin);
531                         }
532
533                         // set currentIndex to be one after the char
534
// that couldn't display
535
currentIndex = displayUpToIndex+1;
536                     }
537                 }
538
539                 if (numSet == aciLength) // all chars have font set;
540
break;
541             }
542
543             // assign the first font to any chars haven't alreay been assigned
544
int runStart = -1;
545             GVTFontFamily prevFF = null;
546             GVTFont prevF = defaultFont;
547             for (int i = 0; i < aciLength; i++) {
548                 if (fontAssigned[i]) {
549                     if (runStart != -1) {
550             // System.out.println("Font 3: " + prevF);
551
as.addAttribute(GVT_FONT, prevF,
552                                         runStart+asOff, i+asOff);
553                         runStart = -1;
554                         prevF = null;
555                         prevFF = null;
556                     }
557                 } else {
558                     char c = aci.setIndex(start+i);
559                     GVTFontFamily fontFamily;
560                     fontFamily = FontFamilyResolver.getFamilyThatCanDisplay(c);
561                     // fontFamily = (GVTFontFamily)resolvedFontFamilies.get(0);
562

563                     if (runStart == -1) {
564                         // Starting a new run...
565
runStart = i;
566                         prevFF = fontFamily;
567                         if (prevFF == null)
568                             prevF = defaultFont;
569                         else
570                             prevF = fontFamily.deriveFont(fontSize, aci);
571                     } else if (prevFF != fontFamily) {
572                         // Font family changed...
573
// System.out.println("Font 4: " + prevF);
574
as.addAttribute(GVT_FONT, prevF,
575                                         runStart+asOff, i+asOff);
576                     
577                         runStart = i;
578                         prevFF = fontFamily;
579                         if (prevFF == null)
580                             prevF = defaultFont;
581                         else
582                             prevF = fontFamily.deriveFont(fontSize, aci);
583                     }
584                 }
585             }
586             if (runStart != -1) {
587         // System.out.println("Font 5: " + prevF);
588
as.addAttribute(GVT_FONT, prevF,
589                                 runStart+asOff, aciLength+asOff);
590         }
591
592             asOff += aciLength;
593             if (aci.setIndex(end) == AttributedCharacterIterator.DONE) {
594                 moreChunks = false;
595             }
596             start = end;
597         }
598         if (as != null)
599             return as.getIterator();
600
601         // Didn't do anything return original ACI
602
return aci;
603     }
604
605
606     protected TextChunk getTextChunk(TextNode node,
607                                    AttributedCharacterIterator JavaDoc aci,
608                                    int [] charMap,
609                                    List JavaDoc textRuns,
610                                    TextChunk prevChunk) {
611         int beginChunk = 0;
612         if (prevChunk != null)
613             beginChunk = prevChunk.end;
614         int endChunk = beginChunk;
615         int begin = aci.getIndex();
616         // System.out.println("New Chunk");
617
if (aci.current() == CharacterIterator.DONE)
618             return null;
619
620         // we now lay all aci's out at 0,0 then move them
621
// when we adjust the chunk offsets.
622
Point2D.Float JavaDoc offset = new Point2D.Float JavaDoc(0,0);
623         Point2D.Float JavaDoc advance = new Point2D.Float JavaDoc(0,0);
624         boolean isChunkStart = true;
625         TextSpanLayout layout = null;
626         do {
627             int start = aci.getRunStart(extendedAtts);
628             int end = aci.getRunLimit(extendedAtts);
629
630             AttributedCharacterIterator JavaDoc runaci;
631             runaci = new AttributedCharacterSpanIterator(aci, start, end);
632
633             int [] subCharMap = new int[end-start];
634             for (int i=0; i<subCharMap.length; i++) {
635                 subCharMap[i] = charMap[i+start-begin];
636             }
637
638             FontRenderContext JavaDoc frc = fontRenderContext;
639             RenderingHints JavaDoc rh = node.getRenderingHints();
640             // Check for optimizeSpeed, optimizeLegibility
641
// in these cases setup hintedFRC
642
if ((rh != null) &&
643                 (rh.get(RenderingHints.KEY_TEXT_ANTIALIASING) ==
644                   RenderingHints.VALUE_TEXT_ANTIALIAS_OFF)) {
645                 // In both these cases we want the non-antialiased
646
// font render context.
647
frc = aaOffFontRenderContext;
648             }
649
650             layout = getTextLayoutFactory().createTextLayout
651                 (runaci, subCharMap, offset, frc);
652
653             textRuns.add(new TextRun(layout, runaci, isChunkStart));
654             // System.out.println("TextRun: " + start + "->" + end +
655
// " Start: " + isChunkStart);
656

657             Point2D JavaDoc layoutAdvance = layout.getAdvance2D();
658             // System.out.println("layoutAdv: " + layoutAdvance);
659
advance.x += (float)layoutAdvance.getX();
660             advance.y += (float)layoutAdvance.getY();
661
662             ++endChunk;
663             if (aci.setIndex(end) == CharacterIterator.DONE) break;
664             isChunkStart = false;
665         } while (true);
666         
667         // System.out.println("Adv: " + advance);
668
// System.out.println("Chunks: [" + beginChunk + ", " +
669
// endChunk + "]");
670
return new TextChunk(beginChunk, endChunk, advance);
671     }
672
673
674
675     /**
676      * Adjusts the position of the text runs within the specified text chunk
677      * to account for any text anchor properties.
678      */

679     protected Point2D JavaDoc adjustChunkOffsets(Point2D JavaDoc location,
680                                          List JavaDoc textRuns,
681                                          TextChunk chunk) {
682         TextRun r = (TextRun) textRuns.get(chunk.begin);
683         int anchorType = r.getAnchorType();
684         Float JavaDoc length = r.getLength();
685         Integer JavaDoc lengthAdj = r.getLengthAdjust();
686
687         boolean doAdjust = true;
688         if ((length == null) || length.isNaN())
689             doAdjust = false;
690         
691         int numChars = 0;
692         for (int n=chunk.begin; n<chunk.end; ++n) {
693             r = (TextRun) textRuns.get(n);
694             AttributedCharacterIterator JavaDoc aci = r.getACI();
695             numChars += aci.getEndIndex()-aci.getBeginIndex();
696         }
697         if ((lengthAdj ==
698              GVTAttributedCharacterIterator.TextAttribute.ADJUST_SPACING) &&
699             (numChars == 1))
700             doAdjust = false;
701
702         float xScale = 1;
703         float yScale = 1;
704
705         r = (TextRun)textRuns.get(chunk.end-1);
706         TextSpanLayout layout = r.getLayout();
707         GVTGlyphMetrics lastMetrics =
708             layout.getGlyphMetrics(layout.getGlyphCount()-1);
709         Rectangle2D JavaDoc lastBounds = lastMetrics.getBounds2D();
710         float lastW = (float)(lastBounds.getWidth()+lastBounds.getX());
711         float lastH = (float)(lastBounds.getHeight());
712         Point2D JavaDoc visualAdvance;
713         
714         if (!doAdjust) {
715             // System.out.println("Adv: " + chunk.advance);
716
// System.out.println("LastBounds: " + lastBounds);
717
// System.out.println("LastMetrics.hadv: " + lastMetrics.getHorizontalAdvance());
718
// System.out.println("LastMetrics.vadv: " + lastMetrics.getVerticalAdvance());
719
visualAdvance = new Point2D.Float JavaDoc
720             ((float)(chunk.advance.getX() + lastW -
721                      lastMetrics.getHorizontalAdvance()),
722              (float)(chunk.advance.getY() + lastH -
723                      lastMetrics.getVerticalAdvance()));
724         } else {
725             Point2D JavaDoc advance = chunk.advance;
726
727             // We have to do this here since textLength needs to be
728
// handled at the text chunk level. Otherwise tspans get
729
// messed up.
730
if (layout.isVertical()) {
731                 if (lengthAdj == ADJUST_SPACING) {
732                     yScale = (float)
733                         ((length.floatValue()-lastH)/
734                          (advance.getY()-lastMetrics.getVerticalAdvance()));
735                 } else {
736                     double adv = (advance.getY()-
737                                   lastMetrics.getVerticalAdvance() + lastH);
738                     yScale = (float)(length.floatValue()/adv);
739                 }
740                 visualAdvance = new Point2D.Float JavaDoc(0, length.floatValue());
741             } else {
742                 if (lengthAdj == ADJUST_SPACING) {
743                     xScale = (float)
744                         ((length.floatValue()-lastW)/
745                          (advance.getX()-lastMetrics.getHorizontalAdvance()));
746                 } else {
747                     double adv = (advance.getX() + lastW -
748                                   lastMetrics.getHorizontalAdvance());
749                     xScale = (float)(length.floatValue()/adv);
750                 }
751                 visualAdvance = new Point2D.Float JavaDoc(length.floatValue(), 0);
752             }
753
754             // System.out.println("Adv: " + advance + " Len: " + length +
755
// " scale: [" + xScale + ", " + yScale + "]");
756
Point2D.Float JavaDoc adv = new Point2D.Float JavaDoc(0,0);
757             for (int n=chunk.begin; n<chunk.end; ++n) {
758                 r = (TextRun) textRuns.get(n);
759                 layout = r.getLayout();
760                 layout.setScale(xScale, yScale, lengthAdj==ADJUST_SPACING);
761                 Point2D JavaDoc lAdv = layout.getAdvance2D();
762                 adv.x += (float)lAdv.getX();
763                 adv.y += (float)lAdv.getY();
764             }
765             chunk.advance = adv;
766         }
767
768         float dx = 0f;
769         float dy = 0f;
770         switch(anchorType){
771         case TextNode.Anchor.ANCHOR_MIDDLE:
772             dx = (float) (-visualAdvance.getX()/2d);
773             dy = (float) (-visualAdvance.getY()/2d);
774             break;
775         case TextNode.Anchor.ANCHOR_END:
776             dx = (float) (-visualAdvance.getX());
777             dy = (float) (-visualAdvance.getY());
778             break;
779         default:
780             break;
781             // leave untouched
782
}
783
784         // System.out.println("DX/DY: [" + dx + ", " + dy + "]");
785

786         r = (TextRun) textRuns.get(chunk.begin);
787         layout = r.getLayout();
788         AttributedCharacterIterator JavaDoc runaci = r.getACI();
789         runaci.first();
790         boolean vertical = layout.isVertical();
791         Float JavaDoc runX = (Float JavaDoc) runaci.getAttribute(XPOS);
792         Float JavaDoc runY = (Float JavaDoc) runaci.getAttribute(YPOS);
793         TextPath textPath = (TextPath) runaci.getAttribute(TEXTPATH);
794
795         // The point that the next peice of normal text should be
796
// layed out from, only used for normal text not text on a path.
797
float absX = (float)location.getX();
798         float absY = (float)location.getY();
799         // TextPath Shift used to account for startOffset.
800
float tpShiftX = 0;
801         float tpShiftY = 0;
802
803         // Of course X and Y override that, but they don't apply for
804
// text on a path.
805
if ((runX != null) && (!runX.isNaN())) {
806         absX = runX.floatValue();
807         tpShiftX = absX;
808     }
809     
810     if ((runY != null) && (!runY.isNaN())) {
811         absY = runY.floatValue();
812         tpShiftY = absY;
813     }
814
815         // Factor in text-anchor in writing direction.
816
// Ignore tpShift in non-writing direction.
817
if (vertical) {
818             absY += dy;
819             tpShiftY += dy;
820             tpShiftX = 0;
821         } else {
822             absX += dx;
823             tpShiftX += dx;
824             tpShiftY = 0;
825         }
826
827         // System.out.println("ABS: [" + absX + "," + absY + "," +
828
// visualAdvance.getX() + "," +
829
// visualAdvance.getY() + "]");
830
for (int n=chunk.begin; n<chunk.end; ++n) {
831             r = (TextRun) textRuns.get(n);
832             layout = r.getLayout();
833             runaci = r.getACI();
834             runaci.first();
835             textPath = (TextPath) runaci.getAttribute(TEXTPATH);
836             if (vertical) {
837                 runX = (Float JavaDoc) runaci.getAttribute(XPOS);
838                 if ((runX != null) && (!runX.isNaN())) {
839                     absX = runX.floatValue();
840                 }
841             } else {
842                 runY = (Float JavaDoc) runaci.getAttribute(YPOS);
843                 if ((runY != null) && (!runY.isNaN())) {
844                     absY = runY.floatValue();
845                 }
846             }
847
848             if (textPath == null) {
849                 layout.setOffset(new Point2D.Float JavaDoc(absX, absY));
850
851                 Point2D JavaDoc ladv = layout.getAdvance2D();
852                 absX += ladv.getX();
853                 absY += ladv.getY();
854             } else {
855                 layout.setOffset(new Point2D.Float JavaDoc(tpShiftX, tpShiftY));
856
857                 Point2D JavaDoc ladv = layout.getAdvance2D();
858                 tpShiftX += (float)ladv.getX();
859                 tpShiftY += (float)ladv.getY();
860
861                 ladv = layout.getTextPathAdvance();
862                 absX = (float)ladv.getX();
863                 absY = (float)ladv.getY();
864             }
865         }
866         return new Point2D.Float JavaDoc(absX, absY);
867     }
868
869     /**
870      * Paints decorations of the specified type.
871      */

872     protected void paintDecorations(List JavaDoc textRuns,
873                                   Graphics2D JavaDoc g2d,
874                                   int decorationType) {
875         Paint JavaDoc prevPaint = null;
876         Paint JavaDoc prevStrokePaint = null;
877         Stroke JavaDoc prevStroke = null;
878         Rectangle2D JavaDoc decorationRect = null;
879         double yLoc = 0, height = 0;
880         
881         for (int i = 0; i < textRuns.size(); i++) {
882             TextRun textRun = (TextRun)textRuns.get(i);
883             AttributedCharacterIterator JavaDoc runaci = textRun.getACI();
884             runaci.first();
885             
886             TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO);
887             if ((tpi != null) && (tpi.composite != null)) {
888                 g2d.setComposite(tpi.composite);
889             }
890
891             Paint JavaDoc paint = null;
892             Stroke JavaDoc stroke = null;
893             Paint JavaDoc strokePaint = null;
894             if (tpi != null) {
895                 switch (decorationType) {
896                 case TextSpanLayout.DECORATION_UNDERLINE :
897                     paint = tpi.underlinePaint;
898                     stroke = tpi.underlineStroke;
899                     strokePaint = tpi.underlineStrokePaint;
900                     break;
901                 case TextSpanLayout.DECORATION_OVERLINE :
902                     paint = tpi.overlinePaint;
903                     stroke = tpi.overlineStroke;
904                     strokePaint = tpi.overlineStrokePaint;
905                     break;
906                 case TextSpanLayout.DECORATION_STRIKETHROUGH :
907                     paint = tpi.strikethroughPaint;
908                     stroke = tpi.strikethroughStroke;
909                     strokePaint = tpi.strikethroughStrokePaint;
910                     break;
911                 default:
912                     // should never get here
913
return;
914                 }
915             }
916
917             if (textRun.isFirstRunInChunk()) {
918                 Shape JavaDoc s = textRun.getLayout().getDecorationOutline
919                     (decorationType);
920                 Rectangle2D JavaDoc r2d = s.getBounds2D();
921                 yLoc = r2d.getY();
922                 height = r2d.getHeight();
923             }
924                 
925             if (textRun.isFirstRunInChunk() ||
926                 (paint != prevPaint) ||
927                 (stroke != prevStroke) ||
928                 (strokePaint != prevStrokePaint)) {
929                 // if there is a current decoration, draw it now
930
if (decorationRect != null) {
931
932                     if (prevPaint != null) {
933                         // fill the decoration
934
g2d.setPaint(prevPaint);
935                         g2d.fill(decorationRect);
936                     }
937                     if (prevStroke != null && prevStrokePaint != null) {
938                         // stroke the decoration
939
g2d.setPaint(prevStrokePaint);
940                         g2d.setStroke(prevStroke);
941                         g2d.draw(decorationRect);
942                     }
943                     decorationRect = null;
944                 }
945             }
946
947             if ((paint != null || strokePaint != null)
948                 && !textRun.getLayout().isVertical()
949                 && !textRun.getLayout().isOnATextPath()) {
950
951                 // this text run should be decorated with the
952
// specified decoration type
953
// NOTE: decorations are only supported for plain
954
// horizontal layouts
955

956                 Shape JavaDoc decorationShape =
957                     textRun.getLayout().getDecorationOutline(decorationType);
958                 if (decorationRect == null) {
959                     // create a new one
960
Rectangle2D JavaDoc r2d = decorationShape.getBounds2D();
961                     decorationRect = new Rectangle2D.Double JavaDoc
962                         (r2d.getX(), yLoc, r2d.getWidth(), height);
963                 } else {
964                     // extend the current one
965
Rectangle2D JavaDoc bounds = decorationShape.getBounds2D();
966                     double minX = Math.min(decorationRect.getX(),
967                                            bounds.getX());
968                     double maxX = Math.max(decorationRect.getMaxX(),
969                                            bounds.getMaxX());
970                     decorationRect.setRect(minX, yLoc, maxX-minX, height);
971                 }
972             }
973             prevPaint = paint;
974             prevStroke = stroke;
975             prevStrokePaint = strokePaint;
976         }
977
978         // if there is a decoration rect that hasn't been drawn yet, draw it now
979

980         if (decorationRect != null) {
981
982             if (prevPaint != null) {
983                 // fill the decoration
984
g2d.setPaint(prevPaint);
985                 g2d.fill(decorationRect);
986             }
987             if (prevStroke != null && prevStrokePaint != null) {
988                 // stroke the decoration
989
g2d.setPaint(prevStrokePaint);
990                 g2d.setStroke(prevStroke);
991                 g2d.draw(decorationRect);
992             }
993         }
994     }
995
996
997     /**
998      * Paints the text in each text run. Decorations are not painted here.
999      */

1000    protected void paintTextRuns(List JavaDoc textRuns,
1001                               Graphics2D JavaDoc g2d) {
1002        for (int i = 0; i < textRuns.size(); i++) {
1003            TextRun textRun = (TextRun)textRuns.get(i);
1004            AttributedCharacterIterator JavaDoc runaci = textRun.getACI();
1005            runaci.first();
1006
1007            TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO);
1008            if ((tpi != null) && (tpi.composite != null)) {
1009                g2d.setComposite(tpi.composite);
1010            }
1011            textRun.getLayout().draw(g2d);
1012        }
1013    }
1014
1015    /**
1016     * Get a Shape in userspace coords which defines the textnode glyph outlines.
1017     * @param node the TextNode to measure
1018     */

1019    public Shape JavaDoc getOutline(TextNode node) {
1020
1021        GeneralPath JavaDoc outline = null;
1022        AttributedCharacterIterator JavaDoc aci = node.getAttributedCharacterIterator();
1023
1024        // get the list of text runs
1025
List JavaDoc textRuns = getTextRuns(node, aci);
1026
1027        // for each text run, get its outline and append it to the overall
1028
// outline
1029

1030        for (int i = 0; i < textRuns.size(); ++i) {
1031            TextRun textRun = (TextRun)textRuns.get(i);
1032            TextSpanLayout textRunLayout = textRun.getLayout();
1033            GeneralPath JavaDoc textRunOutline =
1034        new GeneralPath JavaDoc(textRunLayout.getOutline());
1035
1036            if (outline == null) {
1037               outline = textRunOutline;
1038            } else {
1039                outline.setWindingRule(GeneralPath.WIND_NON_ZERO);
1040                outline.append(textRunOutline, false);
1041            }
1042        }
1043
1044        // append any decoration outlines
1045
Shape JavaDoc underline = getDecorationOutline
1046            (textRuns, TextSpanLayout.DECORATION_UNDERLINE);
1047
1048        Shape JavaDoc strikeThrough = getDecorationOutline
1049            (textRuns, TextSpanLayout.DECORATION_STRIKETHROUGH);
1050        
1051        Shape JavaDoc overline = getDecorationOutline
1052            (textRuns, TextSpanLayout.DECORATION_OVERLINE);
1053        
1054        if (underline != null) {
1055            if (outline == null) {
1056                outline = new GeneralPath JavaDoc(underline);
1057            } else {
1058                outline.setWindingRule(GeneralPath.WIND_NON_ZERO);
1059                outline.append(underline, false);
1060            }
1061        }
1062        if (strikeThrough != null) {
1063            if (outline == null) {
1064                outline = new GeneralPath JavaDoc(strikeThrough);
1065            } else {
1066                outline.setWindingRule(GeneralPath.WIND_NON_ZERO);
1067                outline.append(strikeThrough, false);
1068            }
1069        }
1070        if (overline != null) {
1071            if (outline == null) {
1072                outline = new GeneralPath JavaDoc(overline);
1073            } else {
1074                outline.setWindingRule(GeneralPath.WIND_NON_ZERO);
1075                outline.append(overline, false);
1076            }
1077        }
1078
1079        return outline;
1080    }
1081
1082
1083    /**
1084     * Get a Rectangle2D in userspace coords which encloses the textnode
1085     * glyphs including stroke etc.
1086     */

1087     public Rectangle2D JavaDoc getBounds2D(TextNode node) {
1088        AttributedCharacterIterator JavaDoc aci;
1089        aci = node.getAttributedCharacterIterator();
1090
1091        // get the list of text runs
1092
List JavaDoc textRuns = getTextRuns(node, aci);
1093
1094        Rectangle2D JavaDoc bounds = null;
1095        // for each text run, get its stroke outline and append it to
1096
// the overall outline
1097
for (int i = 0; i < textRuns.size(); ++i) {
1098            TextRun textRun = (TextRun)textRuns.get(i);
1099            AttributedCharacterIterator JavaDoc textRunACI = textRun.getACI();
1100            TextSpanLayout textRunLayout = textRun.getLayout();
1101            Rectangle2D JavaDoc runBounds = textRunLayout.getBounds2D();
1102            if (runBounds != null) {
1103                if (bounds == null)
1104                    bounds = runBounds;
1105                else
1106                    bounds = bounds.createUnion(runBounds);
1107            }
1108        }
1109
1110
1111        // append any stroked decoration outlines
1112
Shape JavaDoc underline = getDecorationStrokeOutline
1113            (textRuns, TextSpanLayout.DECORATION_UNDERLINE);
1114
1115        if (underline != null) {
1116            if (bounds == null)
1117                bounds = underline.getBounds2D();
1118            else
1119                bounds = bounds.createUnion(underline.getBounds2D());
1120        }
1121
1122        Shape JavaDoc strikeThrough = getDecorationStrokeOutline
1123            (textRuns, TextSpanLayout.DECORATION_STRIKETHROUGH);
1124        if (strikeThrough != null) {
1125            if (bounds == null)
1126                bounds = strikeThrough.getBounds2D();
1127            else
1128                bounds = bounds.createUnion(strikeThrough.getBounds2D());
1129        }
1130
1131        Shape JavaDoc overline = getDecorationStrokeOutline
1132            (textRuns, TextSpanLayout.DECORATION_OVERLINE);
1133        if (overline != null) {
1134            if (bounds == null)
1135                bounds = overline.getBounds2D();
1136            else
1137                bounds = bounds.createUnion(overline.getBounds2D());
1138        }
1139        return bounds;
1140    }
1141
1142
1143    /**
1144     * Returns the outline of the specified decoration type.
1145     *
1146     * @param textRuns The list of text runs to get the decoration outline for.
1147     * @param decorationType Indicates the type of decoration required.
1148     * eg. underline, overline or strikethrough.
1149     *
1150     * @return The decoration outline or null if the text is not decorated.
1151     */

1152    protected Shape JavaDoc getDecorationOutline(List JavaDoc textRuns, int decorationType) {
1153
1154        GeneralPath JavaDoc outline = null;
1155
1156        Paint JavaDoc prevPaint = null;
1157        Paint JavaDoc prevStrokePaint = null;
1158        Stroke JavaDoc prevStroke = null;
1159        Rectangle2D JavaDoc decorationRect = null;
1160        double yLoc = 0, height = 0;
1161
1162        for (int i = 0; i < textRuns.size(); i++) {
1163            TextRun textRun = (TextRun)textRuns.get(i);
1164            AttributedCharacterIterator JavaDoc runaci = textRun.getACI();
1165            runaci.first();
1166
1167            Paint JavaDoc paint = null;
1168            Stroke JavaDoc stroke = null;
1169            Paint JavaDoc strokePaint = null;
1170            TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO);
1171            if (tpi != null) {
1172                switch (decorationType) {
1173                case TextSpanLayout.DECORATION_UNDERLINE :
1174                    paint = tpi.underlinePaint;
1175                    stroke = tpi.underlineStroke;
1176                    strokePaint = tpi.underlineStrokePaint;
1177                    break;
1178                case TextSpanLayout.DECORATION_OVERLINE :
1179                    paint = tpi.overlinePaint;
1180                    stroke = tpi.overlineStroke;
1181                    strokePaint = tpi.overlineStrokePaint;
1182                    break;
1183                case TextSpanLayout.DECORATION_STRIKETHROUGH :
1184                    paint = tpi.strikethroughPaint;
1185                    stroke = tpi.strikethroughStroke;
1186                    strokePaint = tpi.strikethroughStrokePaint;
1187                    break;
1188                default:
1189                    // should never get here
1190
return null;
1191                }
1192            }
1193
1194            if (textRun.isFirstRunInChunk()) {
1195                Shape JavaDoc s = textRun.getLayout().getDecorationOutline
1196                    (decorationType);
1197                Rectangle2D JavaDoc r2d = s.getBounds2D();
1198                yLoc = r2d.getY();
1199                height = r2d.getHeight();
1200            }
1201                
1202            if (textRun.isFirstRunInChunk() ||
1203        paint != prevPaint ||
1204        stroke != prevStroke ||
1205        strokePaint != prevStrokePaint) {
1206
1207                // if there is a current decoration, added it to the overall
1208
// outline
1209
if (decorationRect != null) {
1210                    if (outline == null) {
1211                        outline = new GeneralPath JavaDoc(decorationRect);
1212                    } else {
1213                        outline.append(decorationRect, false);
1214                    }
1215                    decorationRect = null;
1216                }
1217            }
1218
1219            if ((paint != null || strokePaint != null)
1220                && !textRun.getLayout().isVertical()
1221                && !textRun.getLayout().isOnATextPath()) {
1222
1223                // this text run should be decorated with the specified
1224
// decoration type note: decorations are only supported for
1225
// plain horizontal layouts
1226

1227                Shape JavaDoc decorationShape =
1228            textRun.getLayout().getDecorationOutline(decorationType);
1229                if (decorationRect == null) {
1230                    // create a new one
1231
Rectangle2D JavaDoc r2d = decorationShape.getBounds2D();
1232                    decorationRect = new Rectangle2D.Double JavaDoc
1233                        (r2d.getX(), yLoc, r2d.getWidth(), height);
1234                } else {
1235                    // extend the current one
1236
Rectangle2D JavaDoc bounds = decorationShape.getBounds2D();
1237                    double minX = Math.min(decorationRect.getX(),
1238                                           bounds.getX());
1239                    double maxX = Math.max(decorationRect.getMaxX(),
1240                                           bounds.getMaxX());
1241                    decorationRect.setRect(minX, yLoc, maxX-minX, height);
1242                }
1243            }
1244
1245            prevPaint = paint;
1246            prevStroke = stroke;
1247            prevStrokePaint = strokePaint;
1248        }
1249
1250        // if there is a decoration rect that hasn't been added to the overall outline
1251
if (decorationRect != null) {
1252            if (outline == null) {
1253                outline = new GeneralPath JavaDoc(decorationRect);
1254            } else {
1255                outline.append(decorationRect, false);
1256            }
1257        }
1258
1259        return outline;
1260    }
1261
1262    /**
1263     * Returns the strokeed outline of the specified decoration type.
1264     * If the decoration has no stroke it will return the fill outline
1265     *
1266     * @param textRuns The list of text runs to get the decoration outline for.
1267     * @param decorationType Indicates the type of decoration required.
1268     * eg. underline, overline or strikethrough.
1269     *
1270     * @return The decoration outline or null if the text is not decorated.
1271     */

1272    protected Shape JavaDoc getDecorationStrokeOutline
1273    (List JavaDoc textRuns, int decorationType) {
1274
1275        GeneralPath JavaDoc outline = null;
1276
1277        Paint JavaDoc prevPaint = null;
1278        Paint JavaDoc prevStrokePaint = null;
1279        Stroke JavaDoc prevStroke = null;
1280        Rectangle2D JavaDoc decorationRect = null;
1281        double yLoc = 0, height = 0;
1282
1283        for (int i = 0; i < textRuns.size(); i++) {
1284
1285            TextRun textRun = (TextRun)textRuns.get(i);
1286            AttributedCharacterIterator JavaDoc runaci = textRun.getACI();
1287            runaci.first();
1288
1289            Paint JavaDoc paint = null;
1290            Stroke JavaDoc stroke = null;
1291            Paint JavaDoc strokePaint = null;
1292            TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO);
1293            if (tpi != null) {
1294                switch (decorationType) {
1295                case TextSpanLayout.DECORATION_UNDERLINE :
1296                    paint = tpi.underlinePaint;
1297                    stroke = tpi.underlineStroke;
1298                    strokePaint = tpi.underlineStrokePaint;
1299                    break;
1300                case TextSpanLayout.DECORATION_OVERLINE :
1301                    paint = tpi.overlinePaint;
1302                    stroke = tpi.overlineStroke;
1303                    strokePaint = tpi.overlineStrokePaint;
1304                    break;
1305                case TextSpanLayout.DECORATION_STRIKETHROUGH :
1306                    paint = tpi.strikethroughPaint;
1307                    stroke = tpi.strikethroughStroke;
1308                    strokePaint = tpi.strikethroughStrokePaint;
1309                    break;
1310                default:
1311                    // should never get here
1312
return null;
1313                }
1314            }
1315
1316            if (textRun.isFirstRunInChunk()) {
1317                Shape JavaDoc s = textRun.getLayout().getDecorationOutline
1318                    (decorationType);
1319                Rectangle2D JavaDoc r2d = s.getBounds2D();
1320                yLoc = r2d.getY();
1321                height = r2d.getHeight();
1322            }
1323                
1324            if (textRun.isFirstRunInChunk() ||
1325        paint != prevPaint ||
1326        stroke != prevStroke ||
1327        strokePaint != prevStrokePaint) {
1328
1329                // if there is a current decoration, added it to the overall
1330
// outline
1331
if (decorationRect != null) {
1332            
1333                    Shape JavaDoc s = null;
1334                    if (prevStroke != null &&
1335                        prevStrokePaint != null)
1336                        s = prevStroke.createStrokedShape(decorationRect);
1337                    else if (prevPaint != null)
1338                        s = decorationRect;
1339                    if (s != null) {
1340                        if (outline == null)
1341                            outline = new GeneralPath JavaDoc(s);
1342                        else
1343                            outline.append(s, false);
1344                    }
1345                    decorationRect = null;
1346                }
1347            }
1348        
1349            if ((paint != null || strokePaint != null)
1350                && !textRun.getLayout().isVertical()
1351                && !textRun.getLayout().isOnATextPath()) {
1352
1353                // this text run should be decorated with the specified
1354
// decoration type note: decorations are only supported for
1355
// plain horizontal layouts
1356

1357                Shape JavaDoc decorationShape =
1358            textRun.getLayout().getDecorationOutline(decorationType);
1359
1360                if (decorationRect == null) {
1361                    // create a new one
1362
Rectangle2D JavaDoc r2d = decorationShape.getBounds2D();
1363                    decorationRect = new Rectangle2D.Double JavaDoc
1364                        (r2d.getX(), yLoc, r2d.getWidth(), height);
1365                } else {
1366                    // extend the current one
1367
Rectangle2D JavaDoc bounds = decorationShape.getBounds2D();
1368                    double minX = Math.min(decorationRect.getX(),
1369                                           bounds.getX());
1370                    double maxX = Math.max(decorationRect.getMaxX(),
1371                                           bounds.getMaxX());
1372                    decorationRect.setRect(minX, yLoc, maxX-minX, height);
1373                }
1374            }
1375
1376            prevPaint = paint;
1377            prevStroke = stroke;
1378            prevStrokePaint = strokePaint;
1379        }
1380
1381        // if there is a decoration rect that hasn't been added to the overall
1382
// outline
1383
if (decorationRect != null) {
1384            Shape JavaDoc s = null;
1385            if (prevStroke != null &&
1386                prevStrokePaint != null)
1387                s = prevStroke.createStrokedShape(decorationRect);
1388            else if (prevPaint != null)
1389                s = decorationRect;
1390            if (s != null) {
1391                if (outline == null)
1392                    outline = new GeneralPath JavaDoc(s);
1393                else
1394                    outline.append(s, false);
1395            }
1396        }
1397
1398        return outline;
1399    }
1400
1401
1402    public Mark getMark(TextNode node, int index, boolean leadingEdge) {
1403        AttributedCharacterIterator JavaDoc aci;
1404        aci = node.getAttributedCharacterIterator();
1405        if ((index < aci.getBeginIndex()) ||
1406            (index > aci.getEndIndex()))
1407            return null;
1408
1409        TextHit textHit = new TextHit(index, leadingEdge);
1410        return new BasicTextPainter.BasicMark(node, textHit);
1411    }
1412
1413    protected Mark hitTest(double x, double y, TextNode node) {
1414        AttributedCharacterIterator JavaDoc aci;
1415        aci = node.getAttributedCharacterIterator();
1416                           
1417        // get the list of text runs
1418
List JavaDoc textRuns = getTextRuns(node, aci);
1419
1420        // for each text run, see if its been hit
1421
for (int i = 0; i < textRuns.size(); ++i) {
1422            TextRun textRun = (TextRun)textRuns.get(i);
1423            TextSpanLayout layout = textRun.getLayout();
1424            TextHit textHit = layout.hitTestChar((float) x, (float) y);
1425            if (textHit != null && layout.getBounds2D().contains(x,y)) {
1426                return new BasicTextPainter.BasicMark(node, textHit);
1427            }
1428        }
1429
1430        return null;
1431    }
1432
1433    /**
1434     * Selects the first glyph in the text node.
1435     */

1436    public Mark selectFirst(TextNode node) {
1437        AttributedCharacterIterator JavaDoc aci;
1438        aci = node.getAttributedCharacterIterator();
1439        TextHit textHit = new TextHit(aci.getBeginIndex(), false);
1440        return new BasicTextPainter.BasicMark(node, textHit);
1441    }
1442
1443    /**
1444     * Selects the last glyph in the text node.
1445     */

1446    public Mark selectLast(TextNode node) {
1447        AttributedCharacterIterator JavaDoc aci;
1448        aci = node.getAttributedCharacterIterator();
1449        TextHit textHit = new TextHit(aci.getEndIndex()-1, false);
1450        return new BasicTextPainter.BasicMark(node, textHit);
1451    }
1452
1453    /**
1454     * Returns an array of ints representing begin/end index pairs into
1455     * an AttributedCharacterIterator which represents the text
1456     * selection delineated by two Mark instances.
1457     * <em>Note: The Mark instances passed must have been instantiated by
1458     * an instance of this enclosing TextPainter implementation.</em>
1459     */

1460    public int[] getSelected(Mark startMark,
1461                             Mark finishMark) {
1462
1463        if (startMark == null || finishMark == null) {
1464            return null;
1465        }
1466        BasicTextPainter.BasicMark start;
1467        BasicTextPainter.BasicMark finish;
1468        try {
1469            start = (BasicTextPainter.BasicMark) startMark;
1470            finish = (BasicTextPainter.BasicMark) finishMark;
1471        } catch (ClassCastException JavaDoc cce) {
1472            throw new Error JavaDoc
1473                ("This Mark was not instantiated by this TextPainter class!");
1474        }
1475
1476        TextNode textNode = start.getTextNode();
1477        if (textNode != finish.getTextNode())
1478            throw new Error JavaDoc("Markers are from different TextNodes!");
1479
1480        AttributedCharacterIterator JavaDoc aci;
1481        aci = textNode.getAttributedCharacterIterator();
1482                             
1483        int[] result = new int[2];
1484        result[0] = start.getHit().getCharIndex();
1485        result[1] = finish.getHit().getCharIndex();
1486
1487        // get the list of text runs
1488
List JavaDoc textRuns = getTextRuns(textNode, aci);
1489        Iterator JavaDoc trI = textRuns.iterator();
1490        int startGlyphIndex = -1;
1491        int endGlyphIndex = -1;
1492        TextSpanLayout startLayout=null, endLayout=null;
1493        while (trI.hasNext()) {
1494            TextRun tr = (TextRun)trI.next();
1495            TextSpanLayout tsl = tr.getLayout();
1496            if (startGlyphIndex == -1) {
1497                startGlyphIndex = tsl.getGlyphIndex(result[0]);
1498                if (startGlyphIndex != -1)
1499                    startLayout = tsl;
1500            }
1501                
1502            if (endGlyphIndex == -1) {
1503                endGlyphIndex = tsl.getGlyphIndex(result[1]);
1504                if (endGlyphIndex != -1)
1505                    endLayout = tsl;
1506            }
1507            if ((startGlyphIndex != -1) && (endGlyphIndex != -1))
1508                break;
1509        }
1510        if ((startLayout == null) || (endLayout == null))
1511            return null;
1512                
1513        int startCharCount = startLayout.getCharacterCount
1514            (startGlyphIndex, startGlyphIndex);
1515        int endCharCount = endLayout.getCharacterCount
1516            (endGlyphIndex, endGlyphIndex);
1517        if (startCharCount > 1) {
1518            if (result[0] > result[1] && startLayout.isLeftToRight()) {
1519                result[0] += startCharCount-1;
1520            } else if (result[1] > result[0] && !startLayout.isLeftToRight()) {
1521                result[0] -= startCharCount-1;
1522            }
1523        }
1524        if (endCharCount > 1) {
1525            if (result[1] > result[0] && endLayout.isLeftToRight()) {
1526                result[1] += endCharCount-1;
1527            } else if (result[0] > result[1] && !endLayout.isLeftToRight()) {
1528                result[1] -= endCharCount-1;
1529            }
1530        }
1531
1532        return result;
1533    }
1534
1535   /**
1536     * Return a Shape, in the coordinate system of the text layout,
1537     * which encloses the text selection delineated by two Mark instances.
1538     * <em>Note: The Mark instances passed must have been instantiated by
1539     * an instance of this enclosing TextPainter implementation.</em>
1540     */

1541    public Shape JavaDoc getHighlightShape(Mark beginMark, Mark endMark) {
1542
1543        if (beginMark == null || endMark == null) {
1544            return null;
1545        }
1546
1547        BasicTextPainter.BasicMark begin;
1548        BasicTextPainter.BasicMark end;
1549        try {
1550            begin = (BasicTextPainter.BasicMark) beginMark;
1551            end = (BasicTextPainter.BasicMark) endMark;
1552        } catch (ClassCastException JavaDoc cce) {
1553            throw new Error JavaDoc
1554                ("This Mark was not instantiated by this TextPainter class!");
1555        }
1556
1557        TextNode textNode = begin.getTextNode();
1558        if (textNode != end.getTextNode())
1559            throw new Error JavaDoc("Markers are from different TextNodes!");
1560        if (textNode == null)
1561            return null;
1562
1563        int beginIndex = begin.getHit().getCharIndex();
1564        int endIndex = end.getHit().getCharIndex();
1565        if (beginIndex > endIndex) {
1566            // Swap them...
1567
BasicTextPainter.BasicMark tmpMark = begin;
1568            begin = end; end = tmpMark;
1569
1570            int tmpIndex = beginIndex;
1571            beginIndex = endIndex; endIndex = tmpIndex;
1572        }
1573
1574        // get the list of text runs
1575
List JavaDoc textRuns = getTextRuns
1576            (textNode, textNode.getAttributedCharacterIterator());
1577
1578        GeneralPath JavaDoc highlightedShape = new GeneralPath JavaDoc();
1579
1580        // for each text run, append any highlight it may contain for
1581
// the current selection
1582
for (int i = 0; i < textRuns.size(); ++i) {
1583            TextRun textRun = (TextRun)textRuns.get(i);
1584            TextSpanLayout layout = textRun.getLayout();
1585
1586            Shape JavaDoc layoutHighlightedShape = layout.getHighlightShape
1587                (beginIndex, endIndex);
1588
1589            // append the highlighted shape of this layout to the
1590
// overall hightlighted shape
1591
if (( layoutHighlightedShape != null) &&
1592                (!layoutHighlightedShape.getBounds().isEmpty())) {
1593                highlightedShape.append(layoutHighlightedShape, false);
1594            }
1595        }
1596        return highlightedShape;
1597    }
1598
1599// inner classes
1600

1601    class TextChunk {
1602
1603        public int begin;
1604        public int end;
1605        public Point2D JavaDoc advance;
1606
1607        public TextChunk(int begin, int end, Point2D JavaDoc advance) {
1608            this.begin = begin;
1609            this.end = end;
1610            this.advance = new Point2D.Float JavaDoc((float) advance.getX(),
1611                                             (float) advance.getY());
1612        }
1613    }
1614
1615
1616    /**
1617     * Inner convenience class for associating a TextLayout for
1618     * sub-spans, and the ACI which iterates over that subspan.
1619     */

1620    public class TextRun {
1621
1622        protected AttributedCharacterIterator JavaDoc aci;
1623        protected TextSpanLayout layout;
1624        protected int anchorType;
1625        protected boolean firstRunInChunk;
1626        protected Float JavaDoc length;
1627        protected Integer JavaDoc lengthAdjust;
1628
1629        public TextRun(TextSpanLayout layout,
1630               AttributedCharacterIterator JavaDoc aci,
1631               boolean firstRunInChunk) {
1632
1633            this.layout = layout;
1634            this.aci = aci;
1635            this.aci.first();
1636            this.firstRunInChunk = firstRunInChunk;
1637            this.anchorType = TextNode.Anchor.ANCHOR_START;
1638
1639            TextNode.Anchor anchor = (TextNode.Anchor) aci.getAttribute
1640        (GVTAttributedCharacterIterator.TextAttribute.ANCHOR_TYPE);
1641            if (anchor != null) {
1642                this.anchorType = anchor.getType();
1643            }
1644
1645            // if writing mode is right to left, then need to reverse the
1646
// text anchor positions
1647
if (aci.getAttribute(WRITING_MODE) == WRITING_MODE_RTL) {
1648                if (anchorType == TextNode.Anchor.ANCHOR_START) {
1649                    anchorType = TextNode.Anchor.ANCHOR_END;
1650                } else if (anchorType == TextNode.Anchor.ANCHOR_END) {
1651                    anchorType = TextNode.Anchor.ANCHOR_START;
1652                }
1653                // leave middle as is
1654
}
1655
1656            length = (Float JavaDoc) aci.getAttribute
1657                (GVTAttributedCharacterIterator.TextAttribute.BBOX_WIDTH);
1658            lengthAdjust = (Integer JavaDoc) aci.getAttribute
1659                (GVTAttributedCharacterIterator.TextAttribute.LENGTH_ADJUST);
1660        }
1661
1662        public AttributedCharacterIterator JavaDoc getACI() {
1663            return aci;
1664        }
1665
1666        public TextSpanLayout getLayout() {
1667            return layout;
1668        }
1669
1670        public int getAnchorType() {
1671            return anchorType;
1672        }
1673
1674        public Float JavaDoc getLength() {
1675            return length;
1676        }
1677
1678        public Integer JavaDoc getLengthAdjust() {
1679            return lengthAdjust;
1680        }
1681
1682        public boolean isFirstRunInChunk() {
1683            return firstRunInChunk;
1684        }
1685
1686    }
1687}
1688
Popular Tags