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   &nbs