KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > batik > gvt > text > GlyphLayout


1 /*
2
3    Copyright 2001-2004 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 package org.apache.batik.gvt.text;
19
20 import java.awt.BasicStroke JavaDoc;
21 import java.awt.Graphics2D JavaDoc;
22 import java.awt.Shape JavaDoc;
23 import java.awt.Stroke JavaDoc;
24 import java.awt.font.FontRenderContext JavaDoc;
25 import java.awt.font.TextAttribute JavaDoc;
26 import java.awt.geom.AffineTransform JavaDoc;
27 import java.awt.geom.Area JavaDoc;
28 import java.awt.geom.GeneralPath JavaDoc;
29 import java.awt.geom.PathIterator 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.CharacterIterator JavaDoc;
34 import java.util.HashSet JavaDoc;
35 import java.util.List JavaDoc;
36 import java.util.Set JavaDoc;
37
38 import org.apache.batik.gvt.font.AWTGVTFont;
39 import org.apache.batik.gvt.font.AltGlyphHandler;
40 import org.apache.batik.gvt.font.GVTFont;
41 import org.apache.batik.gvt.font.GVTGlyphMetrics;
42 import org.apache.batik.gvt.font.GVTGlyphVector;
43 import org.apache.batik.gvt.font.GVTLineMetrics;
44 import org.apache.batik.gvt.TextNode;
45 import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
46 import org.apache.batik.gvt.text.TextHit;
47 import org.apache.batik.gvt.text.TextSpanLayout;
48
49 /**
50  * Implementation of TextSpanLayout which uses java.awt.font.GlyphVector.
51  * @see org.apache.batik.gvt.text.TextSpanLayout
52  *
53  * @author <a HREF="mailto:bill.haneman@ireland.sun.com">Bill Haneman</a>
54  * @version $Id: GlyphLayout.java,v 1.63 2005/03/27 08:58:35 cam Exp $
55  */

56 public class GlyphLayout implements TextSpanLayout {
57
58     public static final char SOFT_HYPHEN = 0x00AD;
59     public static final char ZERO_WIDTH_SPACE = 0x200B;
60     public static final char ZERO_WIDTH_JOINER = 0x200D;
61     public static final char SPACE = ' ';
62
63     private GVTGlyphVector gv;
64     private GVTFont font;
65     private GVTLineMetrics metrics;
66     private AttributedCharacterIterator JavaDoc aci;
67     private FontRenderContext JavaDoc frc;
68     private Point2D JavaDoc advance;
69     private Point2D JavaDoc offset;
70     private float xScale=1;
71     private float yScale=1;
72     private Point2D JavaDoc prevCharPosition;
73     private TextPath textPath;
74     private Point2D JavaDoc textPathAdvance;
75     private int [] charMap;
76     private boolean vertical, adjSpacing=true;
77     private float [] glyphAdvances;
78     private boolean isAltGlyph; //false
79

80     // When layoutApplied is false it means that the glyph positions
81
// are different from where they would be if you did
82
// doExplicitGlyphLayout().
83
private boolean layoutApplied = false;
84     // When spacingApplied is false it means that xScale, yScale and
85
// kerning/wordspacing stuff haven't been applied. This can
86
// be rectified by calling adjustTextSpacing(). Note that when
87
// spacing is actually used layoutApplied will be cleared it
88
// is not garunteed that applying text spacing will cause it to
89
// be cleared (it will only be cleared if the glyphs move).
90
private boolean spacingApplied = false;
91     // When pathApplied is false it means that the text has not been
92
// layed out on the associated text path (if any). If there is an
93
// associated text path then this will clear both layoutApplied
94
// and spacing applied but neither will be touched if no text path
95
// is present.
96
private boolean pathApplied = false;
97
98
99     public static final AttributedCharacterIterator.Attribute JavaDoc FLOW_LINE_BREAK
100         = GVTAttributedCharacterIterator.TextAttribute.FLOW_LINE_BREAK;
101
102     public static final AttributedCharacterIterator.Attribute JavaDoc FLOW_PARAGRAPH
103         = GVTAttributedCharacterIterator.TextAttribute.FLOW_PARAGRAPH;
104
105     public static final AttributedCharacterIterator.Attribute JavaDoc
106         FLOW_EMPTY_PARAGRAPH
107         = GVTAttributedCharacterIterator.TextAttribute.FLOW_EMPTY_PARAGRAPH;
108
109     public static final AttributedCharacterIterator.Attribute JavaDoc LINE_HEIGHT
110         = GVTAttributedCharacterIterator.TextAttribute.LINE_HEIGHT;
111
112     public static final AttributedCharacterIterator.Attribute JavaDoc
113         TEXT_COMPOUND_DELIMITER
114         = GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_DELIMITER;
115
116     public static final AttributedCharacterIterator.Attribute JavaDoc
117         VERTICAL_ORIENTATION
118         = GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION;
119
120     public static final
121         AttributedCharacterIterator.Attribute JavaDoc VERTICAL_ORIENTATION_ANGLE =
122        GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION_ANGLE;
123
124     public static final
125         AttributedCharacterIterator.Attribute JavaDoc HORIZONTAL_ORIENTATION_ANGLE =
126      GVTAttributedCharacterIterator.TextAttribute.HORIZONTAL_ORIENTATION_ANGLE;
127
128     private static final AttributedCharacterIterator.Attribute JavaDoc X
129         = GVTAttributedCharacterIterator.TextAttribute.X;
130
131     private static final AttributedCharacterIterator.Attribute JavaDoc Y
132         = GVTAttributedCharacterIterator.TextAttribute.Y;
133
134     private static final AttributedCharacterIterator.Attribute JavaDoc DX
135         = GVTAttributedCharacterIterator.TextAttribute.DX;
136
137     private static final AttributedCharacterIterator.Attribute JavaDoc DY
138         = GVTAttributedCharacterIterator.TextAttribute.DY;
139
140     private static final AttributedCharacterIterator.Attribute JavaDoc ROTATION
141         = GVTAttributedCharacterIterator.TextAttribute.ROTATION;
142
143     private static final AttributedCharacterIterator.Attribute JavaDoc BASELINE_SHIFT
144         = GVTAttributedCharacterIterator.TextAttribute.BASELINE_SHIFT;
145
146     private static final AttributedCharacterIterator.Attribute JavaDoc WRITING_MODE
147         = GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE;
148
149     private static final Integer JavaDoc WRITING_MODE_TTB
150         = GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_TTB;
151
152     private static final Integer JavaDoc ORIENTATION_AUTO
153         = GVTAttributedCharacterIterator.TextAttribute.ORIENTATION_AUTO;
154
155     public static final AttributedCharacterIterator.Attribute JavaDoc GVT_FONT
156         = GVTAttributedCharacterIterator.TextAttribute.GVT_FONT;
157
158     static protected Set JavaDoc runAtts = new HashSet JavaDoc();
159
160     static {
161         runAtts.add(X);
162         runAtts.add(Y);
163         runAtts.add(DX);
164         runAtts.add(DY);
165         runAtts.add(ROTATION);
166         runAtts.add(BASELINE_SHIFT);
167     }
168
169     static protected Set JavaDoc szAtts = new HashSet JavaDoc();
170
171     static {
172         szAtts.add(TextAttribute.SIZE);
173         szAtts.add(GVT_FONT);
174         szAtts.add(LINE_HEIGHT);
175     }
176
177
178     /**
179      * Creates the specified text layout using the
180      * specified AttributedCharacterIterator and rendering context.
181      *
182      * @param aci the AttributedCharacterIterator whose text is to
183      * be laid out
184      * @param charMap Indicates how chars in aci map to original
185      * text char array.
186      * @param offset The offset position of this text layout
187      * @param frc the FontRenderContext to use for generating glyphs.
188      */

189     public GlyphLayout(AttributedCharacterIterator JavaDoc aci,
190                        int [] charMap,
191                        Point2D JavaDoc offset,
192                        FontRenderContext JavaDoc frc) {
193
194         this.aci = aci;
195         this.frc = frc;
196         this.offset = offset;
197         this.font = getFont();
198         this.charMap = charMap;
199
200         this.metrics = font.getLineMetrics
201             (aci, aci.getBeginIndex(), aci.getEndIndex(), frc);
202
203         // create the glyph vector
204
this.gv = null;
205         this.aci.first();
206         this.vertical = (aci.getAttribute(WRITING_MODE) == WRITING_MODE_TTB);
207         this.textPath = (TextPath) aci.getAttribute
208             (GVTAttributedCharacterIterator.TextAttribute.TEXTPATH);
209
210         AltGlyphHandler altGlyphHandler
211             = (AltGlyphHandler)this.aci.getAttribute
212             (GVTAttributedCharacterIterator.TextAttribute.ALT_GLYPH_HANDLER);
213         if (altGlyphHandler != null) {
214             // this must be an altGlyph text element, try and create
215
// the alternate glyphs
216
this.gv = altGlyphHandler.createGlyphVector
217                 (frc, this.font.getSize(), this.aci);
218             if ( this.gv != null ){
219                 this.isAltGlyph = true;
220             }
221         }
222         if (this.gv == null) {
223             // either not an altGlyph or the altGlyphHandler failed to
224
// create a glyph vector
225
this.gv = font.createGlyphVector(frc, this.aci);
226         }
227     }
228
229
230     public GVTGlyphVector getGlyphVector() {
231         return this.gv;
232     }
233
234
235     /**
236      * Returns the current text position at the beginning
237      * of glyph layout, before the application of explicit
238      * glyph positioning attributes.
239      */

240     public Point2D JavaDoc getOffset() {
241         return offset;
242     }
243
244     /**
245      * Sets the scaling factor to use for string. if ajdSpacing is
246      * true then only the spacing between glyphs will be adjusted
247      * otherwise the glyphs and the spaces between them will be
248      * adjusted. Only the scale factor in the progression direction
249      * is used (x for horizontal text, y for vertical text
250      * ).
251      * @param xScale Scale factor to apply in X direction.
252      * @param yScale Scale factor to apply in Y direction.
253      * @param adjSpacing True if only spaces should be adjusted.
254      */

255     public void setScale(float xScale, float yScale, boolean adjSpacing) {
256         // Fix the off axis scale factor.
257
if (vertical) xScale = 1;
258         else yScale = 1;
259
260         if ((xScale != this.xScale) ||
261             (yScale != this.yScale) ||
262             (adjSpacing != this.adjSpacing)) {
263             this.xScale = xScale;
264             this.yScale = yScale;
265             this.adjSpacing = adjSpacing;
266
267             // We don't affect layoutApplied directly...
268
// System.out.println("layoutApplied: " + layoutApplied);
269

270             // However if we did path layout or spacing it's all junk now...
271
spacingApplied = false;
272             glyphAdvances = null;
273             pathApplied = false;
274         }
275     }
276
277     /**
278      * Sets the text position used for the implicit origin
279      * of glyph layout. Ignored if multiple explicit glyph
280      * positioning attributes are present in ACI
281      * (e.g. if the aci has multiple X or Y values).
282      */

283     public void setOffset(Point2D JavaDoc offset) {
284         // System.err.println("SetOffset: " + offset + " - " + this.offset);
285
if ((offset.getX() != this.offset.getX()) ||
286             (offset.getY() != this.offset.getY())) {
287             if ((layoutApplied)||(spacingApplied)) {
288                 // Already layed out need to shift glyph positions to
289
// account for new offset.
290
float dx = (float)(offset.getX()-this.offset.getX());
291                 float dy = (float)(offset.getY()-this.offset.getY());
292                 int numGlyphs = gv.getNumGlyphs();
293
294                 // System.out.println("DXY: [" + dx +","+dy+"]");
295
float [] gp = gv.getGlyphPositions(0, numGlyphs+1, null);
296                 Point2D.Float JavaDoc pos = new Point2D.Float JavaDoc();
297                 for (int i=0; i<=numGlyphs; i++) {
298                     pos.x = gp[2*i ]+dx;
299                     pos.y = gp[2*i+1]+dy;
300                     gv.setGlyphPosition(i, pos);
301                 }
302             }
303
304             // When not layed out (or after updating) just set the new
305
// offset this will be factored in for any future layout
306
// operations.
307
this.offset = offset;
308
309             // We don't affect layoutApplied or spacingApplied since
310
// they both work off the offset value.
311

312             // However if we did path layout it's all junk now...
313
pathApplied = false;
314         }
315     }
316
317     public GVTGlyphMetrics getGlyphMetrics(int glyphIndex) {
318         return gv.getGlyphMetrics(glyphIndex);
319     }
320
321     /**
322      * Returns true if the advance direction of this text is vertical.
323      */

324     public boolean isVertical() {
325         return vertical;
326     }
327
328     /**
329      * Returns true if this layout in on a text path.
330      */

331     public boolean isOnATextPath() {
332         return (textPath != null);
333     }
334
335
336     /**
337      * Returns the number of glyphs in this layout.
338      */

339     public int getGlyphCount() {
340         return gv.getNumGlyphs();
341     }
342
343
344     /**
345      * Returns the number of chars represented by the glyphs within the
346      * specified range.
347      *
348      * @param startGlyphIndex The index of the first glyph in the range.
349      * @param endGlyphIndex The index of the last glyph in the range.
350      *
351      * @return The number of chars.
352      */

353     public int getCharacterCount(int startGlyphIndex, int endGlyphIndex) {
354         return gv.getCharacterCount(startGlyphIndex, endGlyphIndex);
355     }
356
357     /**
358      * Returns true if the text direction in this layout is from left to right.
359      */

360     public boolean isLeftToRight() {
361         aci.first();
362         int bidiLevel =
363             ((Integer JavaDoc)aci.getAttribute
364              (GVTAttributedCharacterIterator.TextAttribute.BIDI_LEVEL))
365             .intValue();
366
367         // Check if low bit is set if not then we are left to right
368
// (even bidi level).
369
return ((bidiLevel&0x01) == 0);
370     }
371
372
373     /**
374      * This method makes certain that the layout has been
375      * completed at this point (much of the layout is done lazily).
376      */

377     private final void syncLayout() {
378         if (!pathApplied) {
379             // System.out.println("Doing Path Layout: " + this);
380
doPathLayout();
381         }
382     }
383
384     /**
385      * Paints the text layout using the
386      * specified Graphics2D and rendering context.
387      * @param g2d the Graphics2D to use
388      */

389     public void draw(Graphics2D JavaDoc g2d) {
390         syncLayout();
391         gv.draw(g2d, aci);
392     }
393
394     /**
395      * Returns the current text position at the completion
396      * of glyph layout.
397      */

398     public Point2D JavaDoc getAdvance2D() {
399         adjustTextSpacing();
400         return advance;
401     }
402
403
404     /**
405      * Returns the outline of the completed glyph layout.
406      */

407     public Shape JavaDoc getOutline() {
408         syncLayout();
409
410         return gv.getOutline();
411     }
412
413     public float [] getGlyphAdvances() {
414         if (glyphAdvances != null)
415             return glyphAdvances;
416
417         if (!spacingApplied)
418             // This will layout the text if needed.
419
adjustTextSpacing();
420
421         int numGlyphs = gv.getNumGlyphs();
422         float [] glyphPos = gv.getGlyphPositions(0, numGlyphs+1, null);
423         glyphAdvances = new float[numGlyphs+1];
424         int off = 0;
425         if (isVertical())
426             off = 1;
427
428         float start = glyphPos[off];
429         for (int i=0; i<numGlyphs+1; i++) {
430             glyphAdvances[i] = glyphPos[i+i+off]-start;
431         }
432         return glyphAdvances;
433     }
434
435     /**
436      * Returns the outline of the specified decorations on the glyphs,
437      * @param decorationType an integer indicating the type(s) of decorations
438      * included in this shape. May be the result of "OR-ing" several
439      * values together:
440      * e.g. <tt>DECORATION_UNDERLINE | DECORATION_STRIKETHROUGH</tt>
441      */

442     public Shape JavaDoc getDecorationOutline(int decorationType) {
443         syncLayout();
444
445         Shape JavaDoc g = new GeneralPath JavaDoc();
446         if ((decorationType & DECORATION_UNDERLINE) != 0) {
447              ((GeneralPath JavaDoc) g).append(getUnderlineShape(), false);
448         }
449         if ((decorationType & DECORATION_STRIKETHROUGH) != 0) {
450              ((GeneralPath JavaDoc) g).append(getStrikethroughShape(), false);
451         }
452         if ((decorationType & DECORATION_OVERLINE) != 0) {
453              ((GeneralPath JavaDoc) g).append(getOverlineShape(), false);
454         }
455         return g;
456     }
457
458     /**
459      * Returns the rectangular bounds of the completed glyph layout.
460      */

461     public Rectangle2D JavaDoc getBounds2D() {
462         syncLayout();
463         return gv.getBounds2D(aci);
464     }
465
466     /**
467      * Returns the rectangular bounds of the completed glyph layout,
468      * inclusive of "decoration" (underline, overline, etc.)
469      */

470     public Rectangle2D JavaDoc getGeometricBounds() {
471         syncLayout();
472         Rectangle2D JavaDoc gvB, decB;
473         gvB = gv.getGeometricBounds();
474         decB = getDecorationOutline(DECORATION_ALL).getBounds2D();
475         return gvB.createUnion(decB);
476     }
477
478     /**
479      * Returns the position to used when drawing a text run after this one.
480      * It takes into account the text path layout if there is one.
481      */

482     public Point2D JavaDoc getTextPathAdvance() {
483         syncLayout();
484         if (textPath != null) {
485             return textPathAdvance;
486         } else {
487             return getAdvance2D();
488         }
489     }
490
491
492     /**
493      * Returns the index of the first glyph that has the specified char index.
494      *
495      * @param charIndex The original index of the character in the text node's
496      * text string.
497      * @return The index of the matching glyph in this layout's glyph vector,
498      * or -1 if a matching glyph could not be found.
499      */

500     public int getGlyphIndex(int charIndex) {
501         int numGlyphs = getGlyphCount();
502         int j=0;
503         for (int i = 0; i < numGlyphs; i++) {
504             int count = getCharacterCount(i, i);
505             for (int n=0; n<count; n++) {
506                 int glyphCharIndex = charMap[j++];
507                 if (charIndex == glyphCharIndex)
508                     return i;
509                 if (j >= charMap.length)
510                     return -1;
511             }
512         }
513         return -1;
514     }
515
516     /**
517      * Returns the index of the last glyph that has the specified char index.
518      *
519      * @param charIndex The original index of the character in the text node's
520      * text string.
521      * @return The index of the matching glyph in this layout's glyph vector,
522      * or -1 if a matching glyph could not be found.
523      */

524     public int getLastGlyphIndex(int charIndex) {
525         int numGlyphs = getGlyphCount();
526         int j=charMap.length-1;
527         for (int i = numGlyphs-1; i >= 0; --i) {
528             int count = getCharacterCount(i, i);
529             for (int n=0; n<count; n++) {
530                 int glyphCharIndex = charMap[j--];
531                 if (charIndex == glyphCharIndex) return i;
532                 if (j < 0) return -1;
533             }
534         }
535         return -1;
536     }
537
538
539     /**
540      * Return the angle value according to the orientation
541      * of the character.
542      */

543     public double getComputedOrientationAngle(int index){
544
545         if ( isGlyphOrientationAuto() ){
546             if (isVertical()) {
547                 char ch = aci.setIndex(index);
548                 if (isLatinChar(ch))
549                     return 90.0;
550                 else
551                     return 0.0;
552             }
553             return 0.0;
554         }
555         else{
556             return getGlyphOrientationAngle();
557         }
558     }
559
560    /**
561      * Returns a Shape which encloses the currently selected glyphs
562      * as specified by the character indices.
563      *
564      * @param beginCharIndex the index of the first char in the
565      * contiguous selection.
566      * @param endCharIndex the index of the last char in the
567      * contiguous selection.
568      * @return The highlight shape or null if the spacified char range
569      * does not overlap with the chars in this layout. */

570     public Shape JavaDoc getHighlightShape(int beginCharIndex, int endCharIndex) {
571         syncLayout();
572
573         if (beginCharIndex > endCharIndex) {
574             int temp = beginCharIndex;
575             beginCharIndex = endCharIndex;
576             endCharIndex = temp;
577         }
578         GeneralPath JavaDoc shape = null;
579         int numGlyphs = getGlyphCount();
580
581         Point2D.Float JavaDoc [] topPts = new Point2D.Float JavaDoc[2*numGlyphs];
582         Point2D.Float JavaDoc [] botPts = new Point2D.Float JavaDoc[2*numGlyphs];
583
584         int ptIdx = 0;
585
586         int currentChar = 0;
587         for (int i = 0; i < numGlyphs; i++) {
588             int glyphCharIndex = charMap[currentChar];
589             if ((glyphCharIndex >= beginCharIndex) &&
590                 (glyphCharIndex <= endCharIndex) &&
591                 gv.isGlyphVisible(i)) {
592                     
593                 Shape JavaDoc gbounds = gv.getGlyphLogicalBounds(i);
594                 if (gbounds != null) {
595                     // We got something...
596
if (shape == null)
597                         shape = new GeneralPath JavaDoc();
598
599                     // We are pretty dumb here we assume that we always
600
// get back polygons with four sides to them if
601
// isn't met we are SOL.
602
float [] pts = new float[6];
603                     int count = 0;
604                     int type = -1;
605
606                     PathIterator JavaDoc pi = gbounds.getPathIterator(null);
607                     Point2D.Float JavaDoc firstPt = null;
608
609                     while (!pi.isDone()) {
610                         type = pi.currentSegment(pts);
611                         if ((type == PathIterator.SEG_MOVETO) ||
612                             (type == PathIterator.SEG_LINETO)) {
613                             // LINETO or MOVETO
614
if (count > 4) break; // too many lines...
615
if (count == 4) {
616                                 // make sure we are just closing it..
617
if ((firstPt == null) ||
618                                     (firstPt.x != pts[0]) ||
619                                     (firstPt.y != pts[1]))
620                                     break;
621                             } else {
622                                 Point2D.Float JavaDoc pt;
623                                 pt = new Point2D.Float JavaDoc(pts[0], pts[1]);
624                                 if (count == 0) firstPt = pt;
625                                 // Use sides of rectangle...
626
switch (count) {
627                                 case 0: botPts[ptIdx] = pt; break;
628                                 case 1: topPts[ptIdx] = pt; break;
629                                 case 2: topPts[ptIdx+1] = pt; break;
630                                 case 3: botPts[ptIdx+1] = pt; break;
631                                 }
632                             }
633                         } else if (type == PathIterator.SEG_CLOSE) {
634                                 // Close in the wrong spot?
635
if ((count < 4) || (count > 5)) break;
636                         } else {
637                             // QUADTO or CUBETO
638
break;
639                         }
640
641                         count++;
642                         pi.next();
643                     }
644                     if (pi.isDone()) {
645                         // Sucessfully Expressed as a quadralateral...
646
if ((botPts[ptIdx]!=null) &&
647                             ((topPts[ptIdx].x != topPts[ptIdx+1].x) ||
648                              (topPts[ptIdx].y != topPts[ptIdx+1].y)))
649                             // box isn't empty so use it's points...
650
ptIdx += 2;
651                     } else {
652                         // System.out.println("Type: " + type +
653
// " count: " + count);
654
// Wasn't a quadralateral so just add it don't try
655
// and merge it...
656
addPtsToPath(shape, topPts, botPts, ptIdx);
657                         ptIdx = 0;
658                         shape.append(gbounds, false);
659                     }
660                 }
661             }
662             currentChar += getCharacterCount(i, i);
663             if (currentChar >= charMap.length)
664                 currentChar = charMap.length-1;
665         }
666         addPtsToPath(shape, topPts, botPts, ptIdx);
667
668         return shape;
669     }
670
671     public static final float eps = 0.00001f;
672     public static boolean epsEQ(double a, double b) {
673         return ((a+eps > b) && (a-eps < b));
674     }
675
676     public static int makeConvexHull(Point2D.Float JavaDoc [] pts, int numPts) {
677         // Sort the Pts in X...
678
Point2D.Float JavaDoc tmp;
679         // System.out.print("Sorting...");
680
for (int i=1; i<numPts; i++) {
681             // Simple bubble sort (numPts should be small so shouldn't
682
// be too bad.).
683
if ((pts[i].x < pts[i-1].x) ||
684                 ((pts[i].x == pts[i-1].x) && (pts[i].y < pts[i-1].y))) {
685                 tmp = pts[i];
686                 pts[i] = pts[i-1];
687                 pts[i-1] = tmp;
688                 i=0;
689                 continue;
690             }
691         }
692
693         // System.out.println("Sorted");
694

695         Point2D.Float JavaDoc pt0 = pts[0];
696         Point2D.Float JavaDoc pt1 = pts[numPts-1];
697         Point2D.Float JavaDoc dxdy = new Point2D.Float JavaDoc(pt1.x-pt0.x, pt1.y-pt0.y);
698         float soln, c = dxdy.y*pt0.x-dxdy.x*pt0.y;
699
700         Point2D.Float JavaDoc [] topList = new Point2D.Float JavaDoc[numPts];
701         Point2D.Float JavaDoc [] botList = new Point2D.Float JavaDoc[numPts];
702         botList[0] = topList[0] = pts[0];
703         int nTopPts=1;
704         int nBotPts=1;
705         for (int i=1; i<numPts-1; i++) {
706             Point2D.Float JavaDoc pt = pts[i];
707             soln = dxdy.x*pt.y-dxdy.y*pt.x+c;
708             if (soln < 0) {
709                 // Below line goes into bot pt list...
710
while (nBotPts >= 2) {
711                     pt0 = botList[nBotPts-2];
712                     pt1 = botList[nBotPts-1];
713                     float dx = pt1.x-pt0.x;
714                     float dy = pt1.y-pt0.y;
715                     float c0 = dy*pt0.x-dx*pt0.y;
716                     soln = dx*pt.y-dy*pt.x+c0;
717                     if (soln > eps) // Left turn add and we are done..
718
break;
719                     if (soln > -eps) {
720                         // On line take lowest Y of two and keep going
721
if (pt1.y < pt.y) pt = pt1;
722                         nBotPts--;
723                         break;
724                     }
725                     // right turn drop prev pt;
726
nBotPts--;
727                 }
728                 botList[nBotPts++] = pt;
729             } else {
730                 // Above line goes into top pt list...
731
while (nTopPts >= 2) {
732                     pt0 = topList[nTopPts-2];
733                     pt1 = topList[nTopPts-1];
734                     float dx = pt1.x-pt0.x;
735                     float dy = pt1.y-pt0.y;
736                     float c0 = dy*pt0.x-dx*pt0.y;
737                     soln = dx*pt.y-dy*pt.x+c0;
738                     if (soln < -eps) // Right turn add and check next point.
739
break;
740                     if (soln < eps) {
741                         // On line take greatest Y of two and keep going
742
if (pt1.y > pt.y) pt = pt1;
743                         nTopPts--;
744                         break;
745                     }
746                     // left turn drop prev pt;
747
nTopPts--;
748                 }
749                 topList[nTopPts++] = pt;
750             }
751         }
752
753         // Check last point in both sets...
754
Point2D.Float JavaDoc pt = pts[numPts-1];
755         while (nBotPts >= 2) {
756             pt0 = botList[nBotPts-2];
757             pt1 = botList[nBotPts-1];
758             float dx = pt1.x-pt0.x;
759             float dy = pt1.y-pt0.y;
760             float c0 = dy*pt0.x-dx*pt0.y;
761             soln = dx*pt.y-dy*pt.x+c0;
762             if (soln > eps)
763                 // Left turn add and we are done..
764
break;
765             if (soln > -eps) {
766                 // On line take lowest Y of two and keep going
767
if (pt1.y >= pt.y) nBotPts--;
768                 break;
769             }
770             // right turn drop prev pt;
771
nBotPts--;
772         }
773
774         while (nTopPts >= 2) {
775             pt0 = topList[nTopPts-2];
776             pt1 = topList[nTopPts-1];
777             float dx = pt1.x-pt0.x;
778             float dy = pt1.y-pt0.y;
779             float c0 = dy*pt0.x-dx*pt0.y;
780             soln = dx*pt.y-dy*pt.x+c0;
781             if (soln < -eps)
782                 // Right turn done...
783
break;
784             if (soln < eps) {
785                 // On line take lowest Y of two and keep going
786
if (pt1.y <= pt.y) nTopPts--;
787                 break;
788             }
789             // left turn drop prev pt;
790
nTopPts--;
791         }
792
793         int i=0;
794         for (; i<nTopPts; i++)
795             pts[i] = topList[i];
796
797         // We always include the 'last' point as it is always on convex hull.
798
pts[i++] = pts[numPts-1];
799
800         // don't include botList[0] since it is the same as topList[0].
801
for (int n=nBotPts-1; n>0; n--, i++)
802             pts[i] = botList[n];
803
804         // System.out.println("CHull has " + i + " pts");
805
return i;
806     }
807
808     public static void addPtsToPath(GeneralPath JavaDoc shape,
809                                      Point2D.Float JavaDoc [] topPts,
810                                      Point2D.Float JavaDoc [] botPts,
811                                      int numPts) {
812         if (numPts < 2) return;
813         if (numPts == 2) {
814             shape.moveTo(topPts[0].x, topPts[0].y);
815             shape.lineTo(topPts[1].x, topPts[1].y);
816             shape.lineTo(botPts[1].x, botPts[1].y);
817             shape.lineTo(botPts[0].x, botPts[0].y);
818             shape.lineTo(topPts[0].x, topPts[0].y);
819             return;
820         }
821
822         // Here we 'connect the dots' the best way we know how...
823
// What I do is construct a convex hull between adjacent
824
// character boxes, then I union that into the shape. this
825
// does a good job of bridging between adjacent characters,
826
// but still closely tracking to text boxes. The use of the
827
// Area class is fairly heavy weight but it seems to keep up
828
// in this instanace (probably because all the shapes are very
829
// simple polygons).
830
Point2D.Float JavaDoc [] boxes = new Point2D.Float JavaDoc[8];
831         Point2D.Float JavaDoc [] chull = new Point2D.Float JavaDoc[8];
832         boxes[4] = topPts[0];
833         boxes[5] = topPts[1];
834         boxes[6] = botPts[1];
835         boxes[7] = botPts[0];
836         Area JavaDoc []areas = new Area JavaDoc[numPts/2];
837         int nAreas =0;
838         for (int i=2; i<numPts; i+=2) {
839             boxes[0] = boxes[4];
840             boxes[1] = boxes[5];
841             boxes[2] = boxes[6];
842             boxes[3] = boxes[7];
843             boxes[4] = topPts[i];
844             boxes[5] = topPts[i+1];
845             boxes[6] = botPts[i+1];
846             boxes[7] = botPts[i];
847
848             float delta,sz,dist;
849             delta = boxes[2].x-boxes[0].x;
850             dist = delta*delta;
851             delta = boxes[2].y-boxes[0].y;
852             dist += delta*delta;
853             sz = (float)Math.sqrt(dist);
854
855             delta = boxes[6].x-boxes[4].x;
856             dist = delta*delta;
857             delta = boxes[6].y-boxes[4].y;
858             dist += delta*delta;
859             sz += (float)Math.sqrt(dist);
860
861             delta = ((boxes[0].x+boxes[1].x+boxes[2].x+boxes[3].x)-
862                      (boxes[4].x+boxes[5].x+boxes[6].x+boxes[7].x))/4;
863             dist = delta*delta;
864             delta = ((boxes[0].y+boxes[1].y+boxes[2].y+boxes[3].y)-
865                      (boxes[4].y+boxes[5].y+boxes[6].y+boxes[7].y))/4;
866             dist += delta*delta;
867             dist = (float)Math.sqrt(dist);
868             // Note here that dist is the distance between center
869
// points, and sz is the sum of the length of the
870
// diagonals of the letter boxes. In normal cases one
871
// would expect dist to be approximately equal to sz/2.
872
// So here we merge if the two characters are within four
873
// character widths of each other. If they are farther
874
// apart than that chances are it's a 'line break' or
875
// something similar where we will get better results
876
// merging seperately, and anyways with this much space
877
// between them the extra outline shouldn't hurt..
878
GeneralPath JavaDoc gp = new GeneralPath JavaDoc();
879             if (dist < sz) {
880                 // Close enough to merge with previous char...
881
System.arraycopy(boxes, 0, chull, 0, 8);
882                 int npts = makeConvexHull(chull, 8);
883                 gp.moveTo(chull[0].x, chull[0].y);
884                 for(int n=1; n<npts; n++)
885                     gp.lineTo(chull[n].x, chull[n].y);
886                 gp.closePath();
887             } else {
888                 // Merge all previous areas
889
mergeAreas(shape, areas, nAreas);
890                 nAreas = 0; // Start fresh...
891

892                 // Then just add box (add the previous char box if first pts)
893
if (i==2) {
894                     gp.moveTo(boxes[0].x, boxes[0].y);
895                     gp.lineTo(boxes[1].x, boxes[1].y);
896                     gp.lineTo(boxes[2].x, boxes[2].y);
897                     gp.lineTo(boxes[3].x, boxes[3].y);
898                     gp.closePath();
899                     shape.append(gp, false);
900                     gp.reset();
901                 }
902                 gp.moveTo(boxes[4].x, boxes[4].y);
903                 gp.lineTo(boxes[5].x, boxes[5].y);
904                 gp.lineTo(boxes[6].x, boxes[6].y);
905                 gp.lineTo(boxes[7].x, boxes[7].y);
906                 gp.closePath();
907             }
908             areas[nAreas++] = new Area JavaDoc(gp);
909         }
910
911         mergeAreas(shape, areas, nAreas);
912     }
913
914     public static void mergeAreas(GeneralPath JavaDoc shape,
915                                   Area JavaDoc []shapes, int nShapes) {
916         // Merge areas hierarchically, this means that while there are
917
// the same number of Area.add calls (n-1) the great majority
918
// of them are very simple combinations. This helps to speed
919
// things up a tad...
920
while (nShapes > 1) {
921             int n=0;
922             for (int i=1; i<nShapes;i+=2) {
923                 shapes[i-1].add(shapes[i]);
924                 shapes[n++] = shapes[i-1];
925                 shapes[i] = null;
926             }
927
928             // make sure we include the last one if odd.
929
if ((nShapes&0x1) == 1)
930                 shapes[n-1].add(shapes[nShapes-1]);
931             nShapes = nShapes/2;
932         }
933         if (nShapes == 1)
934             shape.append(shapes[0], false);
935     }
936
937     /**
938      * Perform hit testing for coordinate at x, y.
939      *
940      * @param x the x coordinate of the point to be tested.
941      * @param y the y coordinate of the point to be tested.
942      *
943      * @return a TextHit object encapsulating the character index for
944      * successful hits and whether the hit is on the character
945      * leading edge.
946      */

947     public TextHit hitTestChar(float x, float y) {
948         syncLayout();
949
950         TextHit textHit = null;
951
952         int currentChar = 0;
953         for (int i = 0; i < gv.getNumGlyphs(); i++) {
954             Shape JavaDoc gbounds = gv.getGlyphLogicalBounds(i);
955             if (gbounds != null) {
956                 Rectangle2D JavaDoc gbounds2d = gbounds.getBounds2D();
957                 // System.out.println("Hit Test: [" + x + ", " + y + "] - " +
958
// gbounds2d);
959
if (gbounds.contains(x, y)) {
960                     boolean isRightHalf =
961                         (x > (gbounds2d.getX()+(gbounds2d.getWidth()/2d)));
962                     boolean isLeadingEdge = !isRightHalf;
963                     int charIndex = charMap[currentChar];
964                     textHit = new TextHit(charIndex, isLeadingEdge);
965                     return textHit;
966                 }
967             }
968             currentChar += getCharacterCount(i, i);
969             if (currentChar >= charMap.length)
970                 currentChar = charMap.length-1;
971         }
972         return textHit;
973     }
974
975 //protected
976

977     /**
978      * Returns the GVTFont to use when rendering the specified
979      * character iterator. This should already be set as an attribute
980      * on the aci.
981      *
982      * @return The GVTFont to use.
983      */

984     protected GVTFont getFont() {
985         aci.first();
986         GVTFont gvtFont = (GVTFont)aci.getAttributes().get(GVT_FONT);
987
988         if (gvtFont != null)
989             return gvtFont;
990
991         // shouldn't get here
992
return new AWTGVTFont(aci.getAttributes());
993     }
994
995     /**
996      * Returns a shape describing the overline decoration for a given ACI.
997      */

998     protected Shape JavaDoc getOverlineShape() {
999         double y = metrics.getOverlineOffset();
1000        float overlineThickness = metrics.getOverlineThickness();
1001
1002        // need to move the overline a bit lower,
1003
// not sure if this is correct behaviour or not
1004
y += overlineThickness;
1005
1006        // Not certain what should be done here...
1007
aci.first();
1008        Float JavaDoc dy = (Float JavaDoc) aci.getAttribute(DY);
1009        if (dy != null)
1010            y += dy.floatValue();
1011
1012        Stroke JavaDoc overlineStroke =
1013            new BasicStroke JavaDoc(overlineThickness);
1014        Rectangle2D JavaDoc logicalBounds = gv.getLogicalBounds();
1015
1016        return overlineStroke.createStrokedShape(
1017                           new java.awt.geom.Line2D.Double(
1018                           logicalBounds.getMinX() + overlineThickness/2.0, offset.getY()+y,
1019                           logicalBounds.getMaxX() - overlineThickness/2.0, offset.getY()+y));
1020    }
1021
1022    /**
1023     * Returns a shape describing the strikethrough line for a given ACI.
1024     */

1025    protected Shape JavaDoc getUnderlineShape() {
1026
1027        double y = metrics.getUnderlineOffset();
1028        float underlineThickness = metrics.getUnderlineThickness();
1029
1030        // need to move the underline a bit lower,
1031
// not sure if this is correct behaviour or not
1032
y += underlineThickness*1.5;
1033
1034        BasicStroke JavaDoc underlineStroke =
1035            new BasicStroke JavaDoc(underlineThickness);
1036
1037        // Not certain what should be done here...
1038
aci.first();
1039        Float JavaDoc dy = (Float JavaDoc) aci.getAttribute(DY);
1040        if (dy != null)
1041            y += dy.floatValue();
1042
1043        Rectangle2D JavaDoc logicalBounds = gv.getLogicalBounds();
1044
1045        return underlineStroke.createStrokedShape(
1046                           new java.awt.geom.Line2D.Double(
1047                           logicalBounds.getMinX() + underlineThickness/2.0, offset.getY()+y,
1048                           logicalBounds.getMaxX() - underlineThickness/2.0, offset.getY()+y));
1049    }
1050
1051    /**
1052     * Returns a shape describing the strikethrough line for a given ACI.
1053     */

1054    protected Shape JavaDoc getStrikethroughShape() {
1055        double y = metrics.getStrikethroughOffset();
1056        float strikethroughThickness = metrics.getStrikethroughThickness();
1057
1058        Stroke JavaDoc strikethroughStroke =
1059            new BasicStroke JavaDoc(strikethroughThickness);
1060
1061        // Not certain what should be done here...
1062
aci.first();
1063        Float JavaDoc dy = (Float JavaDoc) aci.getAttribute(DY);
1064        if (dy != null)
1065            y += dy.floatValue();
1066
1067        Rectangle2D JavaDoc logicalBounds = gv.getLogicalBounds();
1068        return strikethroughStroke.createStrokedShape(
1069                           new java.awt.geom.Line2D.Double(
1070                           logicalBounds.getMinX() + strikethroughThickness/2.0, offset.getY()+y,
1071                           logicalBounds.getMaxX() - strikethroughThickness/2.0, offset.getY()+y));
1072    }
1073
1074    /**
1075     * Explicitly lays out each of the glyphs in the glyph
1076     * vector. This will handle any glyph position adjustments such as
1077     * dx, dy and baseline offsets. It will also handle vertical
1078     * layouts.
1079     */

1080    protected void doExplicitGlyphLayout() {
1081
1082        this.gv.performDefaultLayout();
1083
1084        float baselineAscent
1085            = vertical ?
1086            (float) gv.getLogicalBounds().getWidth() :
1087            (metrics.getAscent() + Math.abs(metrics.getDescent()));
1088
1089        int numGlyphs = gv.getNumGlyphs();
1090        // System.out.println("NumGlyphs: " + numGlyphs);
1091

1092        float[] gp = gv.getGlyphPositions(0, numGlyphs+1, null);
1093        float verticalFirstOffset = 0f;
1094        float horizontalFirstOffset = 0f;
1095
1096        boolean glyphOrientationAuto = isGlyphOrientationAuto();
1097        int glyphOrientationAngle = 0;
1098        if (!glyphOrientationAuto) {
1099            glyphOrientationAngle = getGlyphOrientationAngle();
1100        }
1101        int i=0;
1102        int aciStart = aci.getBeginIndex();
1103        int aciIndex = 0;
1104        char ch = aci.first();
1105        int runLimit = aciIndex+aciStart;
1106
1107        Float JavaDoc x=null, y=null, dx=null, dy=null, rotation=null;
1108        Object JavaDoc baseline=null;
1109
1110        float shift_x_pos = 0;
1111        float shift_y_pos = 0;
1112        float curr_x_pos = (float)offset.getX();
1113        float curr_y_pos = (float)offset.getY();
1114
1115        Point2D.Float JavaDoc pos = new Point2D.Float JavaDoc();
1116        boolean hasArabicTransparent = false;
1117
1118        while (i < numGlyphs) {
1119            //System.out.println("limit: " + runLimit + ", " + aciIndex);
1120
if (aciIndex+aciStart >= runLimit) {
1121                runLimit = aci.getRunLimit(runAtts);
1122                x = (Float JavaDoc) aci.getAttribute(X);
1123                y = (Float JavaDoc) aci.getAttribute(Y);
1124                dx = (Float JavaDoc) aci.getAttribute(DX);
1125                dy = (Float JavaDoc) aci.getAttribute(DY);
1126                rotation = (Float JavaDoc) aci.getAttribute(ROTATION);
1127                baseline = aci.getAttribute(BASELINE_SHIFT);
1128            }
1129
1130            GVTGlyphMetrics gm = gv.getGlyphMetrics(i);
1131
1132            if (textPath == null) {
1133                // Don't adjust the location of the first
1134
// glyph when doing layout on a text path.
1135
if (i==0) {
1136                    if (isVertical()) {
1137                        if (glyphOrientationAuto) {
1138                            if (isLatinChar(ch)) {
1139                                // it will be rotated 90
1140
verticalFirstOffset = 0f;
1141                            } else {
1142                                // it won't be rotated
1143
verticalFirstOffset =
1144                                    (float)gm.getBounds2D().getHeight();
1145                            }
1146                        } else {
1147                            if (glyphOrientationAngle == 0) {
1148                                verticalFirstOffset =
1149                                    (float)gm.getBounds2D().getHeight();
1150                            } else {
1151                                // 90, 180, 270
1152
verticalFirstOffset = 0f;
1153                            }
1154                        }
1155                    } else {
1156                        if ((glyphOrientationAngle == 270)) {
1157                            horizontalFirstOffset =
1158                                (float)gm.getBounds2D().getHeight();
1159                        } else {
1160                            // 0, 90, 180
1161
horizontalFirstOffset = 0;
1162                        }
1163                    }
1164                } else {
1165                    if (glyphOrientationAuto &&
1166                        (verticalFirstOffset == 0f)
1167                        && !isLatinChar(ch)) {
1168                        verticalFirstOffset =
1169                            (float)gm.getBounds2D().getHeight();
1170                    }
1171                }
1172            }
1173            // ox and oy are origin adjustments for each glyph,
1174
// computed on the basis of baseline-shifts, etc.
1175
float ox = 0f;
1176            float oy = 0f;
1177            float glyphOrientationRotation = 0f;
1178            float glyphRotation = 0f;
1179
1180
1181            if (ch != CharacterIterator.DONE) {
1182                if (vertical) {
1183                    if (glyphOrientationAuto) {
1184                        if (isLatinChar(ch)) {
1185                            // If character is Latin, then rotate by
1186
// 90 degrees
1187
glyphOrientationRotation = (float) (Math.PI / 2f);
1188                        } else {
1189                            glyphOrientationRotation = 0f;
1190                        }
1191                    } else {
1192                        glyphOrientationRotation = (float)Math.toRadians(glyphOrientationAngle);
1193                    }
1194                    if (textPath != null) {
1195                        // if vertical and on a path, any x's are ignored
1196
x = null;
1197                    }
1198                } else {
1199                    glyphOrientationRotation = (float)Math.toRadians(glyphOrientationAngle);
1200                    if (textPath != null) {
1201                        // if horizontal and on a path, any y's are ignored
1202
y = null;
1203                    }
1204                }
1205
1206                // calculate the total rotation for this glyph
1207
if (rotation == null || rotation.isNaN()) {
1208                    glyphRotation = glyphOrientationRotation;
1209                } else {
1210                    glyphRotation = (rotation.floatValue() +
1211                                     glyphOrientationRotation);
1212                }
1213
1214                if ((x != null) && !x.isNaN()) {
1215                    if (i == 0)
1216                        shift_x_pos = (float)(x.floatValue()-offset.getX());
1217                    curr_x_pos = x.floatValue()-shift_x_pos;
1218                }
1219                if (dx != null && !dx.isNaN()) {
1220                    curr_x_pos += dx.floatValue();
1221                }
1222
1223                if ((y != null) && !y.isNaN()) {
1224                    if (i == 0)
1225                        shift_y_pos = (float)(y.floatValue()-offset.getY());
1226                    curr_y_pos = y.floatValue()-shift_y_pos;
1227                }
1228                if (dy != null && !dy.isNaN()) {
1229                    curr_y_pos += dy.floatValue();
1230                } else if (i > 0) {
1231                    curr_y_pos += gp[i*2 + 1]-gp[i*2 - 1];
1232                }
1233
1234                float baselineAdjust = 0f;
1235                if (baseline != null) {
1236                    if (baseline instanceof Integer JavaDoc) {
1237                        if (baseline==TextAttribute.SUPERSCRIPT_SUPER) {
1238                            baselineAdjust = baselineAscent*0.5f;
1239                        } else if (baseline==TextAttribute.SUPERSCRIPT_SUB) {
1240                            baselineAdjust = -baselineAscent*0.5f;
1241                        }
1242                    } else if (baseline instanceof Float JavaDoc) {
1243                        baselineAdjust = ((Float JavaDoc) baseline).floatValue();
1244                    }
1245                    if (vertical) {
1246                        ox = baselineAdjust;
1247                    } else {
1248                        oy = -baselineAdjust;
1249                    }
1250                }
1251
1252                if (vertical) {
1253                    // offset due to rotation of first character
1254
oy += verticalFirstOffset;
1255
1256                    if (glyphOrientationAuto) {
1257                        if (isLatinChar(ch)) {
1258                            ox += metrics.getStrikethroughOffset();
1259                        } else {
1260                            Rectangle2D JavaDoc glyphBounds
1261                                = gv.getGlyphVisualBounds(i).getBounds2D();
1262                            ox -= (float)((glyphBounds.getMaxX() - gp[2*i]) -
1263                                          glyphBounds.getWidth()/2);
1264                        }
1265                    } else {
1266                        // center the character if it's not auto orient
1267
Rectangle2D JavaDoc glyphBounds
1268                            = gv.getGlyphVisualBounds(i).getBounds2D();
1269                        if (glyphOrientationAngle == 0) {
1270                            ox -= (float)((glyphBounds.getMaxX() - gp[2*i]) -
1271                                          glyphBounds.getWidth()/2);
1272                        } else if (glyphOrientationAngle == 180) {
1273                            ox += (float)((glyphBounds.getMaxX() - gp[2*i]) -
1274                                          glyphBounds.getWidth()/2);
1275                        } else if (glyphOrientationAngle == 90) {
1276                            ox += metrics.getStrikethroughOffset();
1277                        } else { // 270
1278
ox -= metrics.getStrikethroughOffset();
1279                        }
1280                    }
1281                } else {
1282                    ox += horizontalFirstOffset;
1283                    if (glyphOrientationAngle == 90) {
1284                        oy -= gm.getHorizontalAdvance();
1285                    } else if (glyphOrientationAngle == 180) {
1286                        oy -= metrics.getAscent();
1287                    }
1288                }
1289            }
1290
1291            // set the new glyph position
1292
pos.x = curr_x_pos+ox;
1293            pos.y = curr_y_pos+oy;
1294            gv.setGlyphPosition(i, pos);
1295
1296            // calculte the position of the next glyph
1297
if (ArabicTextHandler.arabicCharTransparent(ch)) {
1298                hasArabicTransparent = true;
1299            } else {
1300                // Apply the advance if the current char is not transparent
1301
if (vertical) {
1302                    float advanceY = 0;
1303                    if (glyphOrientationAuto) {
1304                        if (isLatinChar(ch)) {
1305                            advanceY = gm.getHorizontalAdvance();
1306                        } else {
1307                            advanceY = gm.getVerticalAdvance();
1308                        }
1309                    } else {
1310                        if ((glyphOrientationAngle == 0) ||
1311                            (glyphOrientationAngle == 180)) {
1312                            advanceY = gm.getVerticalAdvance();
1313                        } else if (glyphOrientationAngle == 90) {
1314                            advanceY = gm.getHorizontalAdvance();
1315                        } else { // 270
1316
advanceY = gm.getHorizontalAdvance();
1317                            // need to translate so that the spacing
1318
// between chars is correct
1319
gv.setGlyphTransform
1320                                (i, AffineTransform.getTranslateInstance
1321                                 (0, advanceY));
1322                        }
1323                    }
1324                    curr_y_pos += advanceY;
1325                } else {
1326                    float advanceX = 0;
1327                    if (glyphOrientationAngle == 0) {
1328                        advanceX = gm.getHorizontalAdvance();
1329                    } else if (glyphOrientationAngle == 180) {
1330                        advanceX = gm.getHorizontalAdvance();
1331                        // need to translate so that the spacing
1332
// between chars is correct
1333
gv.setGlyphTransform
1334                            (i, AffineTransform.getTranslateInstance
1335                             (advanceX, 0));
1336                    } else {
1337                        // 90, 270
1338
advanceX = gm.getVerticalAdvance();
1339                    }
1340                    curr_x_pos += advanceX;
1341                }
1342            }
1343
1344            // rotate the glyph
1345
if (!epsEQ(glyphRotation,0)) {
1346                AffineTransform JavaDoc glyphTransform = gv.getGlyphTransform(i);
1347                if (glyphTransform == null) {
1348                    glyphTransform = new AffineTransform JavaDoc();
1349                }
1350                AffineTransform JavaDoc rotAt;
1351                // Make the 90Deg rotations slightly 'snap to'.
1352
// Also use explicit matrix to avoid round-off.
1353
if (epsEQ(glyphRotation, Math.PI/2)) {
1354                    rotAt = new AffineTransform JavaDoc(0, 1, -1, 0, 0, 0);
1355                } else if (epsEQ(glyphRotation, Math.PI)) {
1356                    rotAt = new AffineTransform JavaDoc(-1, 0, 0, -1, 0, 0);
1357                } else if (epsEQ(glyphRotation, 3*Math.PI/2)) {
1358                    rotAt = new AffineTransform JavaDoc(0, -1, 1, 0, 0, 0);
1359                } else {
1360                    rotAt = AffineTransform.getRotateInstance(glyphRotation);
1361                }
1362                glyphTransform.concatenate(rotAt);
1363                gv.setGlyphTransform(i, glyphTransform);
1364            }
1365
1366            aciIndex += gv.getCharacterCount(i,i);
1367            if (aciIndex >= charMap.length)
1368                aciIndex = charMap.length-1;
1369            ch = aci.setIndex(aciIndex+aciStart);
1370            i++;
1371        }
1372        // Update last glyph pos
1373
pos.x = curr_x_pos;
1374        pos.y = curr_y_pos;
1375        gv.setGlyphPosition(i, pos);
1376
1377        advance = new Point2D.Float JavaDoc((float)(curr_x_pos - offset.getX()),
1378                                    (float)(curr_y_pos - offset.getY()));
1379
1380
1381        // Do a last pass positioning the transparent/mark glyphs on the
1382
// base glyphs.
1383
if (hasArabicTransparent) {
1384            ch = aci.first();
1385            aciIndex = 0;
1386            i=0;
1387            int transparentStart = -1;
1388            while (i < numGlyphs) {
1389                if (ArabicTextHandler.arabicCharTransparent(ch)) {
1390                    if (transparentStart == -1) transparentStart = i;
1391                } else {
1392                    if (transparentStart != -1) {
1393                        Point2D JavaDoc loc = gv.getGlyphPosition(i);
1394                        GVTGlyphMetrics gm = gv.getGlyphMetrics(i);
1395                        int tyS=0, txS=0;
1396                        float advX=0, advY=0;
1397                        if (vertical) {
1398                            if (glyphOrientationAuto ||
1399                                (glyphOrientationAngle == 90))
1400                                advY = gm.getHorizontalAdvance();
1401                            else if (glyphOrientationAngle == 270)
1402                                advY = 0;
1403                            else if (glyphOrientationAngle == 0)
1404                                advX = gm.getHorizontalAdvance();
1405                            else // 180
1406
advX = -gm.getHorizontalAdvance();
1407                        } else {
1408                            if (glyphOrientationAngle == 0)
1409                                advX = gm.getHorizontalAdvance();
1410                            else if (glyphOrientationAngle == 90)
1411                                advY = gm.getHorizontalAdvance();
1412                            else if (glyphOrientationAngle == 180)
1413                                advX = 0;
1414                            else // 270
1415
advY = -gm.getHorizontalAdvance();
1416                        }
1417                        float baseX = (float)(loc.getX()+advX);
1418                        float baseY = (float)(loc.getY()+advY);
1419                        for (int j=transparentStart; j<i; j++) {
1420                            Point2D JavaDoc locT = gv.getGlyphPosition(j);
1421                            GVTGlyphMetrics gmT = gv.getGlyphMetrics(j);
1422                            float locX = (float)locT.getX();
1423                            float locY = (float)locT.getY();
1424                            float tx=0, ty=0;
1425                            float advT = gmT.getHorizontalAdvance();
1426                            if (vertical) {
1427                                if (glyphOrientationAuto ||
1428                                    (glyphOrientationAngle == 90))
1429                                    locY = baseY-advT;
1430                                else if (glyphOrientationAngle == 270)
1431                                    locY = baseY+advT;
1432                                else if (glyphOrientationAngle == 0)
1433                                    locX = baseX-advT;
1434                                else // 180deg
1435
locX = baseX+advT;
1436                            } else {
1437                                if (glyphOrientationAngle == 0)
1438                                    locX = baseX-advT;
1439                                else if (glyphOrientationAngle == 90)
1440                                    locY = baseY-advT;
1441                                else if (glyphOrientationAngle == 180)
1442                                    locX = baseX+advT;
1443                                else // 270
1444
locY = baseY+advT;
1445                            }
1446                            
1447                            locT = new Point2D.Double JavaDoc(locX, locY);
1448                            gv.setGlyphPosition(j, locT);
1449                            if ((txS != 0) || (tyS != 0)) {
1450                                AffineTransform JavaDoc at;
1451                                at = AffineTransform.getTranslateInstance
1452                                    (tx,ty);
1453                                at.concatenate(gv.getGlyphTransform(i));
1454                                gv.setGlyphTransform(i, at);
1455                            }
1456                        }
1457                        transparentStart = -1;
1458                    }
1459                }
1460                aciIndex += gv.getCharacterCount(i,i);
1461                if (aciIndex >= charMap.length)
1462                    aciIndex = charMap.length-1;
1463                ch = aci.setIndex(aciIndex+aciStart);
1464                i++;
1465            }
1466            
1467        }
1468
1469
1470        layoutApplied = true;
1471        spacingApplied = false;
1472        glyphAdvances = null;
1473        pathApplied = false;
1474    }
1475
1476    /**
1477     * Does any spacing adjustments that may have been specified.
1478     */

1479    protected void adjustTextSpacing() {
1480
1481        if (spacingApplied)
1482            // Nothing to do...
1483
return;
1484
1485        if (!layoutApplied)
1486            // Must have clean layout to do spacing...
1487
doExplicitGlyphLayout();
1488
1489        aci.first();
1490        Boolean JavaDoc customSpacing = (Boolean JavaDoc) aci.getAttribute(
1491               GVTAttributedCharacterIterator.TextAttribute.CUSTOM_SPACING);
1492        if ((customSpacing != null) && customSpacing.booleanValue()) {
1493            advance = doSpacing
1494                ((Float JavaDoc) aci.getAttribute
1495                 (GVTAttributedCharacterIterator.TextAttribute.KERNING),
1496                 (Float JavaDoc) aci.getAttribute
1497                 (GVTAttributedCharacterIterator.TextAttribute.LETTER_SPACING),
1498                 (Float JavaDoc) aci.getAttribute
1499                 (GVTAttributedCharacterIterator.TextAttribute.WORD_SPACING));
1500            // Basic layout is now messed up...
1501
layoutApplied = false;
1502        }
1503
1504        // This will clear layoutApplied if it mucks with the current
1505
// character positions.
1506
applyStretchTransform(!adjSpacing);
1507
1508        spacingApplied = true;
1509        pathApplied = false;
1510    }
1511
1512    /**
1513     * Performs any spacing adjustments required and returns the new advance
1514     * value.
1515     *
1516     * @param kern The kerning adjustment to apply to the space
1517     * between each char.
1518     * @param letterSpacing The amount of spacing required between each char.
1519     * @param wordSpacing The amount of spacing required between each word. */

1520    protected Point2D JavaDoc doSpacing(Float JavaDoc kern,
1521                                Float JavaDoc letterSpacing,
1522                                Float JavaDoc wordSpacing) {
1523        boolean autoKern = true;
1524        boolean doWordSpacing = false;
1525        boolean doLetterSpacing = false;
1526        float kernVal = 0f;
1527        float letterSpacingVal = 0f;
1528
1529        if ((kern != null) && (!kern.isNaN())) {
1530            kernVal = kern.floatValue();
1531            autoKern = false;
1532            //System.out.println("KERNING: "+kernVal);
1533
}
1534        if ((letterSpacing != null) && (!letterSpacing.isNaN())) {
1535            letterSpacingVal = letterSpacing.floatValue();
1536            doLetterSpacing = true;
1537            //System.out.println("LETTER-SPACING: "+letterSpacingVal);
1538
}
1539        if ((wordSpacing != null) && (!wordSpacing.isNaN())) {
1540            doWordSpacing = true;
1541        }
1542
1543        int numGlyphs = gv.getNumGlyphs();
1544
1545        float dx = 0f;
1546        float dy = 0f;
1547        Point2D JavaDoc newPositions[] = new Point2D JavaDoc[numGlyphs+1];
1548        Point2D JavaDoc prevPos = gv.getGlyphPosition(0);
1549        int prevCode = gv.getGlyphCode(0);
1550        float x = (float) prevPos.getX();
1551        float y = (float) prevPos.getY();
1552
1553        Point2D JavaDoc lastCharAdvance
1554            = new Point2D.Double JavaDoc(advance.getX() - (gv.getGlyphPosition(numGlyphs-1).getX() - x),
1555                                 advance.getY() - (gv.getGlyphPosition(numGlyphs-1).getY() - y));
1556
1557        try {
1558            GVTFont font = gv.getFont();
1559            // do letter spacing first
1560
if ((numGlyphs > 1) && (doLetterSpacing || !autoKern)) {
1561                for (int i=1; i<=numGlyphs; ++i) {
1562                    Point2D JavaDoc gpos = gv.getGlyphPosition(i);
1563                    int currCode;
1564                    currCode = (i == numGlyphs)?-1:gv.getGlyphCode(i);
1565                    dx = (float)gpos.getX()-(float)prevPos.getX();
1566                    dy = (float)gpos.getY()-(float)prevPos.getY();
1567                    if (autoKern) {
1568                        if (vertical) dy += letterSpacingVal;
1569                        else dx += letterSpacingVal;
1570                    } else {
1571                        // apply explicit kerning adjustments,
1572
// removing any auto-kern values
1573
if (vertical) {
1574                            float vKern = 0;
1575                            if (currCode != -1)
1576                                vKern = font.getVKern(prevCode, currCode);
1577                            dy += kernVal - vKern + letterSpacingVal;
1578                        } else {
1579                            float hKern = 0;
1580                            if (currCode != -1)
1581                                hKern = font.getHKern(prevCode, currCode);
1582                            dx += kernVal - hKern + letterSpacingVal;
1583                        }
1584                    }
1585                    x += dx;
1586                    y += dy;
1587                    newPositions[i] = new Point2D.Float JavaDoc(x, y);
1588                    prevPos = gpos;
1589                    prevCode = currCode;
1590                }
1591
1592                for (int i=1; i<=numGlyphs; ++i) { // assign the new positions
1593
if (newPositions[i] != null) {
1594                        gv.setGlyphPosition(i, newPositions[i]);
1595                    }
1596                }
1597            }
1598
1599             // adjust the advance of the last character
1600
if (vertical) {
1601                lastCharAdvance.setLocation
1602                    (lastCharAdvance.getX(),
1603                     lastCharAdvance.getY() + kernVal + letterSpacingVal);
1604            } else {
1605                lastCharAdvance.setLocation
1606                    (lastCharAdvance.getX() + kernVal + letterSpacingVal,
1607                     lastCharAdvance.getY());
1608            }
1609
1610            // now do word spacing
1611
dx = 0f;
1612            dy = 0f;
1613            prevPos = gv.getGlyphPosition(0);
1614            x = (float) prevPos.getX();
1615            y = (float) prevPos.getY();
1616
1617            if ((numGlyphs > 1) && (doWordSpacing)) {
1618                for (int i = 1; i < numGlyphs; i++) {
1619                    Point2D JavaDoc gpos = gv.getGlyphPosition(i);
1620                    dx = (float)gpos.getX()-(float)prevPos.getX();
1621                    dy = (float)gpos.getY()-(float)prevPos.getY();
1622                    boolean inWS = false;
1623                    // while this is whitespace, increment
1624
int beginWS = i;
1625                    int endWS = i;
1626                    GVTGlyphMetrics gm = gv.getGlyphMetrics(i);
1627
1628                    // BUG: gm.isWhitespace() fails for latin SPACE glyph!
1629
while ((gm.getBounds2D().getWidth()<0.01d) || gm.isWhitespace()) {
1630                        if (!inWS) inWS = true;
1631                        if (i == numGlyphs-1) {
1632                            // white space at the end
1633
break;
1634                        }
1635                        ++i;
1636                        ++endWS;
1637                        gpos = gv.getGlyphPosition(i);
1638                        gm = gv.getGlyphMetrics(i);
1639                    }
1640
1641                    if ( inWS ) { // apply wordSpacing
1642
int nWS = endWS-beginWS;
1643                        float px = (float) prevPos.getX();
1644                        float py = (float) prevPos.getY();
1645                        dx = (float) (gpos.getX() - px)/(nWS+1);
1646                        dy = (float) (gpos.getY() - py)/(nWS+1);
1647                        if (vertical) {
1648                            dy += wordSpacing.floatValue()/(nWS+1);
1649                        } else {
1650                            dx += wordSpacing.floatValue()/(nWS+1);
1651                        }
1652                        for (int j=beginWS; j<=endWS; ++j) {
1653                            x += dx;
1654                            y += dy;
1655                            newPositions[j] = new Point2D.Float JavaDoc(x, y);
1656                        }
1657                    } else {
1658                        dx = (float) (gpos.getX()-prevPos.getX());
1659                        dy = (float) (gpos.getY()-prevPos.getY());
1660                        x += dx;
1661                        y += dy;
1662                        newPositions[i] = new Point2D.Float JavaDoc(x, y);
1663                    }
1664                    prevPos = gpos;
1665                }
1666                Point2D JavaDoc gPos = gv.getGlyphPosition(numGlyphs);
1667                x += (float) (gPos.getX()-prevPos.getX());
1668                y += (float) (gPos.getY()-prevPos.getY());
1669                newPositions[numGlyphs] = new Point2D.Float JavaDoc(x, y);
1670
1671                for (int i=1; i<=numGlyphs; ++i) { // assign the new positions
1672
if (newPositions[i] != null) {
1673                        gv.setGlyphPosition(i, newPositions[i]);
1674                    }
1675                }
1676            }
1677
1678        } catch (Exception JavaDoc e) {
1679            e.printStackTrace();
1680        }
1681
1682        // calculate the new advance
1683
double advX = gv.getGlyphPosition(numGlyphs-1).getX()
1684                     - gv.getGlyphPosition(0).getX();
1685        double advY = gv.getGlyphPosition(numGlyphs-1).getY()
1686                     - gv.getGlyphPosition(0).getY();
1687        Point2D JavaDoc newAdvance = new Point2D.Double JavaDoc(advX + lastCharAdvance.getX(),
1688                                                advY + lastCharAdvance.getY());
1689        return newAdvance;
1690    }
1691
1692    /**
1693     * Stretches the text so that it becomes the specified length.
1694     *
1695     * @param stretchGlyphs if true xScale, yScale will be applied to
1696     * each glyphs transform.
1697     */

1698    protected void applyStretchTransform(boolean stretchGlyphs) {
1699        if ((xScale == 1) && (yScale==1))
1700            return;
1701
1702        AffineTransform JavaDoc scaleAT =
1703            AffineTransform.getScaleInstance(xScale, yScale);
1704
1705        int numGlyphs = gv.getNumGlyphs();
1706        float [] gp = gv.getGlyphPositions(0, numGlyphs+1, null);
1707
1708        float initX = gp[0];
1709        float initY = gp[1];
1710        float dx = 0f;
1711        float dy = 0f;
1712        Point2D.Float JavaDoc pos = new Point2D.Float JavaDoc();
1713        for (int i = 0; i <= numGlyphs; i++) {
1714            dx = gp[2*i] -initX;
1715            dy = gp[2*i+1]-initY;
1716            pos.x = initX+dx*xScale;
1717            pos.y = initY+dy*yScale;
1718            gv.setGlyphPosition(i, pos);
1719
1720            if ((stretchGlyphs) && (i != numGlyphs)) {
1721                // stretch the glyph
1722
AffineTransform JavaDoc glyphTransform = gv.getGlyphTransform(i);
1723                if (glyphTransform != null) {
1724                    glyphTransform.preConcatenate(scaleAT);
1725                    gv.setGlyphTransform(i, glyphTransform);
1726                } else {
1727                    gv.setGlyphTransform (i, scaleAT);
1728                }
1729            }
1730        }
1731
1732        advance = new Point2D.Float JavaDoc((float)(advance.getX()*xScale),
1733                                    (float)(advance.getY()*yScale));
1734        // Basic layout is now messed up...
1735
layoutApplied = false;
1736    }
1737
1738    /**
1739     * If this layout is on a text path, positions the characters
1740     * along the path.
1741     */

1742    protected void doPathLayout() {
1743        if (pathApplied)
1744            return;
1745
1746        if (!spacingApplied)
1747            // This will layout the text if needed.
1748
adjustTextSpacing();
1749
1750        getGlyphAdvances();
1751
1752        // if doesn't have an attached text path, just return
1753
if (textPath == null) {
1754            // We applied the empty path (i.e. do nothing).
1755
pathApplied = true;
1756            return;
1757        }
1758
1759
1760        boolean horizontal = !isVertical();
1761
1762        boolean glyphOrientationAuto = isGlyphOrientationAuto();
1763        int glyphOrientationAngle = 0;
1764        if (!glyphOrientationAuto) {
1765            glyphOrientationAngle = getGlyphOrientationAngle();
1766        }
1767
1768        float pathLength = textPath.lengthOfPath();
1769        float startOffset = textPath.getStartOffset();
1770        int numGlyphs = gv.getNumGlyphs();
1771
1772        // make sure all glyphs visible again, this maybe just a change in
1773
// offset so they may have been made invisible in a previous
1774
// pathLayout call
1775
for (int i = 0; i < numGlyphs; i++) {
1776            gv.setGlyphVisible(i, true);
1777        }
1778
1779        // calculate the total length of the glyphs, this will become be
1780
// the length along the path that is used by the text
1781
float glyphsLength;
1782        if (horizontal) {
1783            glyphsLength = (float) gv.getLogicalBounds().getWidth();
1784        } else {
1785            glyphsLength = (float) gv.getLogicalBounds().getHeight();
1786        }
1787
1788        // check that pathLength and glyphsLength are not 0
1789
if (pathLength == 0f || glyphsLength == 0f) {
1790            // We applied the empty path.
1791
pathApplied = true;
1792            textPathAdvance = advance;
1793            return;
1794        }
1795
1796        // the current start point of the character on the path
1797
// calculate the offset of the first glyph the offset will be
1798
// 0 if the glyph is on the path (ie. not adjusted by a dy or
1799
// dx)
1800
Point2D JavaDoc firstGlyphPosition = gv.getGlyphPosition(0);
1801        float glyphOffset = 0; // offset perpendicular to path
1802
float currentPosition;
1803        if (horizontal) {
1804            glyphOffset = (float)(firstGlyphPosition.getY());
1805            currentPosition = (float)(firstGlyphPosition.getX() + startOffset);
1806        } else {
1807            glyphOffset = (float)(firstGlyphPosition.getX());
1808            currentPosition = (float)(firstGlyphPosition.getY() + startOffset);
1809        }
1810
1811        char ch = aci.first();
1812        int start = aci.getBeginIndex();
1813        int currentChar = 0;
1814        int lastGlyphDrawn = -1;
1815        float lastGlyphAdvance = 0;
1816        // iterate through the GlyphVector placing each glyph
1817
for (int i = 0; i < numGlyphs; i++) {
1818
1819            Point2D JavaDoc currentGlyphPosition = gv.getGlyphPosition(i);
1820
1821            // calculate the advance and offset for the next glyph, do it
1822
// now before we modify the current glyph position
1823

1824            float glyphAdvance = 0; // along path
1825
float nextGlyphOffset = 0; // perpendicular to path eg dy or dx
1826
if (i < gv.getNumGlyphs()-1) {
1827
1828                Point2D JavaDoc nextGlyphPosition = gv.getGlyphPosition(i+1);
1829                if (horizontal) {
1830                    glyphAdvance = (float)(nextGlyphPosition.getX() -
1831                                              currentGlyphPosition.getX());
1832                    nextGlyphOffset = (float)(nextGlyphPosition.getY() -
1833                                              currentGlyphPosition.getY());
1834                } else {
1835                    glyphAdvance = (float)(nextGlyphPosition.getY() -
1836                                              currentGlyphPosition.getY());
1837                    nextGlyphOffset = (float)(nextGlyphPosition.getX() -
1838                                              currentGlyphPosition.getX());
1839                }
1840            } else {
1841                // last glyph, use the glyph metrics
1842
GVTGlyphMetrics gm = gv.getGlyphMetrics(i);
1843                if (horizontal) {
1844                    if ((glyphOrientationAngle == 0) ||
1845                        (glyphOrientationAngle == 180)) {
1846                        glyphAdvance = gm.getHorizontalAdvance();
1847                    } else { // 90 || 270
1848
glyphAdvance = gm.getVerticalAdvance();
1849                    }
1850                } else {
1851                    if (glyphOrientationAuto) {
1852                        if (isLatinChar(ch)) {
1853                            glyphAdvance = gm.getHorizontalAdvance();
1854                        } else {
1855                            glyphAdvance = gm.getVerticalAdvance();
1856                        }
1857                    } else {
1858                        if ((glyphOrientationAngle == 0) ||
1859                            (glyphOrientationAngle == 180)) {
1860                            glyphAdvance = gm.getVerticalAdvance();
1861                        } else { // 90 || 270
1862
glyphAdvance = gm.getHorizontalAdvance();
1863                        }
1864                    }
1865                }
1866            }
1867
1868            // calculate the center line position for the glyph
1869
Rectangle2D JavaDoc glyphBounds = gv.getGlyphOutline(i).getBounds2D();
1870            float glyphWidth = (float) glyphBounds.getWidth();
1871            float glyphHeight = (float) glyphBounds.getHeight();
1872
1873            float charMidPos;
1874            if (horizontal) {
1875                charMidPos = currentPosition + glyphWidth / 2f;
1876            } else {
1877                charMidPos = currentPosition + glyphHeight / 2f;
1878                /*
1879                if (glyphOrientationAuto) {
1880                    if (isLatinChar(ch)) {
1881                        charMidPos = currentPosition + glyphWidth / 2f;
1882                    } else {
1883                        charMidPos = currentPosition + glyphHeight / 2f;
1884                    }
1885                } else {
1886                    if ((glyphOrientationAngle == 0) ||
1887                        (glyphOrientationAngle == 180)) {
1888                        charMidPos = currentPosition + glyphHeight / 2f;
1889                    } else {
1890                        charMidPos = currentPosition + glyphWidth / 2f;
1891                    }
1892                }
1893                */

1894            }
1895
1896            // Calculate the actual point to place the glyph around
1897
Point2D JavaDoc charMidPoint = textPath.pointAtLength(charMidPos);
1898
1899            // Check if the glyph is actually on the path
1900
if (charMidPoint != null) {
1901
1902                // Calculate the normal to the path (midline of glyph)
1903
float angle = textPath.angleAtLength(charMidPos);
1904
1905                // Define the transform of the glyph
1906
AffineTransform JavaDoc glyphPathTransform = new AffineTransform JavaDoc();
1907
1908                // rotate midline of glyph to be normal to path
1909
if (horizontal) {
1910                    glyphPathTransform.rotate(angle);
1911                } else {
1912                    glyphPathTransform.rotate(angle-(Math.PI/2));
1913                }
1914
1915                // re-apply any offset eg from tspan, or spacing adjust
1916
if (horizontal) {
1917                    glyphPathTransform.translate(0, glyphOffset);
1918                } else {
1919                    glyphPathTransform.translate(glyphOffset, 0);
1920                }
1921
1922                // translate glyph backwards so we rotate about the
1923
// center of the glyph
1924
if (horizontal) {
1925                    if (glyphOrientationAngle == 270) {
1926                        glyphPathTransform.translate(glyphWidth / 2f, 0f);
1927                    } else {
1928                        // 0 || 90 || 180
1929
glyphPathTransform.translate(-glyphWidth / 2f, 0f);
1930                    }
1931                } else {
1932                    if (glyphOrientationAuto) {
1933                        if (isLatinChar(ch)) {
1934                           glyphPathTransform.translate(0f, -glyphHeight/2f);
1935                        } else {
1936                            glyphPathTransform.translate(0f, glyphHeight/2f);
1937                        }
1938                    } else {
1939                        if (glyphOrientationAngle == 0) {
1940                            glyphPathTransform.translate(0, glyphHeight / 2f);
1941                        } else {
1942                            // 90 || 180 || 270
1943
glyphPathTransform.translate(0, -glyphHeight / 2f);
1944                        }
1945                    }
1946                }
1947
1948                // set the new glyph position and transform
1949
AffineTransform JavaDoc glyphTransform = gv.getGlyphTransform(i);
1950                if (glyphTransform != null) {
1951                    glyphPathTransform.concatenate(glyphTransform);
1952                }
1953
1954                gv.setGlyphTransform(i, glyphPathTransform);
1955                gv.setGlyphPosition (i, charMidPoint);
1956                // keep track of the last glyph drawn to make calculating the
1957
// textPathAdvance value easier later
1958
lastGlyphDrawn = i;
1959                lastGlyphAdvance = glyphAdvance;
1960
1961            } else {
1962                // not on path so don't render
1963
gv.setGlyphVisible(i, false);
1964            }
1965            currentPosition += glyphAdvance;
1966            glyphOffset += nextGlyphOffset;
1967            currentChar += gv.getCharacterCount(i,i);
1968            if (currentChar >= charMap.length)
1969                currentChar = charMap.length-1;
1970            ch = aci.setIndex(currentChar+start);
1971        }
1972
1973        // store the position where a following glyph should be drawn,
1974
// note: this will only be used if the following text layout is not
1975
// on a text path
1976
if (lastGlyphDrawn > -1) {
1977            Point2D JavaDoc lastGlyphPos = gv.getGlyphPosition(lastGlyphDrawn);
1978            if (horizontal) {
1979                textPathAdvance = new Point2D.Double JavaDoc
1980                    (lastGlyphPos.getX()+lastGlyphAdvance,
1981                     lastGlyphPos.getY());
1982            } else {
1983                textPathAdvance = new Point2D.Double JavaDoc
1984                    (lastGlyphPos.getX(),
1985                     lastGlyphPos.getY()+lastGlyphAdvance);
1986            }
1987        } else {
1988            textPathAdvance = new Point2D.Double JavaDoc(0,0);
1989        }
1990
1991        // The default layout is junk now...
1992
layoutApplied = false;
1993        // The spacing stuff is junk now.
1994
spacingApplied = false;
1995        pathApplied = true;
1996    }
1997
1998    /**
1999     * Returns true if the specified character is within one of the Latin
2000     * unicode character blocks.
2001     *
2002     * @param c The char to test.
2003     *
2004     * @return True if c is latin.
2005     */

2006    protected boolean isLatinChar(char c) {
2007
2008        Character.UnicodeBlock JavaDoc block = Character.UnicodeBlock.of(c);
2009
2010        if (block == Character.UnicodeBlock.BASIC_LATIN ||
2011            block == Character.UnicodeBlock.LATIN_1_SUPPLEMENT ||
2012            block == Character.UnicodeBlock.LATIN_EXTENDED_ADDITIONAL ||
2013            block == Character.UnicodeBlock.LATIN_EXTENDED_A ||
2014            block == Character.UnicodeBlock.LATIN_EXTENDED_B ||
2015            block == Character.UnicodeBlock.ARABIC ||
2016            block == Character.UnicodeBlock.ARABIC_PRESENTATION_FORMS_A ||
2017            block == Character.UnicodeBlock.ARABIC_PRESENTATION_FORMS_B) {
2018            return true;
2019        }
2020        return false;
2021    }
2022
2023    /**
2024     * Returns whether or not the vertical glyph orientation value is "auto".
2025     */

2026    protected boolean isGlyphOrientationAuto() {
2027        if (!isVertical()) return false;
2028        aci.first();
2029        Integer JavaDoc vOrient = (Integer JavaDoc)aci.getAttribute(VERTICAL_ORIENTATION);
2030        if (vOrient != null) {
2031            return (vOrient == ORIENTATION_AUTO);
2032        }
2033        return true;
2034    }
2035
2036    /**
2037     * Returns the value of the vertical glyph orientation angle. This will be
2038     * one of 0, 90, 180 or 270.
2039     */

2040    protected int getGlyphOrientationAngle() {
2041
2042        int glyphOrientationAngle = 0;
2043
2044        aci.first();
2045        Float JavaDoc angle;
2046
2047        if (isVertical()) {
2048            angle = (Float JavaDoc)aci.getAttribute(VERTICAL_ORIENTATION_ANGLE);
2049        } else {
2050            angle = (Float JavaDoc)aci.getAttribute(HORIZONTAL_ORIENTATION_ANGLE);
2051        }
2052
2053        if (angle != null) {
2054            glyphOrientationAngle = (int)angle.floatValue();
2055        }
2056
2057        // if not one of 0, 90, 180 or 270, round to nearest value
2058
if ((glyphOrientationAngle != 0) || (glyphOrientationAngle != 90) ||
2059            (glyphOrientationAngle != 180) || (glyphOrientationAngle != 270)) {
2060
2061            while (glyphOrientationAngle < 0) {
2062                glyphOrientationAngle += 360;
2063            }
2064
2065            while (glyphOrientationAngle >= 360) {
2066                glyphOrientationAngle -= 360;
2067            }
2068
2069            if ((glyphOrientationAngle <= 45) ||
2070                (glyphOrientationAngle > 315)) {
2071                glyphOrientationAngle = 0;
2072            } else if ((glyphOrientationAngle > 45) &&
2073                       (glyphOrientationAngle <= 135)) {
2074                glyphOrientationAngle = 90;
2075            } else if ((glyphOrientationAngle > 135) &&
2076                       (glyphOrientationAngle <= 225)) {
2077                glyphOrientationAngle = 180;
2078            } else {
2079                glyphOrientationAngle = 270;
2080            }
2081        }
2082        return glyphOrientationAngle;
2083    }
2084
2085    /**
2086     * Return true is the character index is represented by glyphs
2087     * in this layout.
2088     *
2089     * @param index index of the character in the ACI.
2090     * @return true if the layout represents that character.
2091     */

2092    public boolean hasCharacterIndex(int index){
2093
2094        for (int n=0; n<charMap.length; n++) {
2095            if (index == charMap[n])
2096                return true;
2097        }
2098        return false;
2099    }
2100
2101    /**
2102     * Return true if this text run represents
2103     * an alt glyph.
2104     */

2105    public boolean isAltGlyph(){
2106        return this.isAltGlyph;
2107    }
2108}
2109
Popular Tags