1 /*
2  * Copyright (c) 1998, 2017, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package sun.font;
27 
28 import java.awt.Font;
29 import java.awt.Graphics2D;
30 import java.awt.Point;
31 import java.awt.Rectangle;
32 import static java.awt.RenderingHints.*;
33 import java.awt.Shape;
34 import java.awt.font.FontRenderContext;
35 import java.awt.font.GlyphMetrics;
36 import java.awt.font.GlyphJustificationInfo;
37 import java.awt.font.GlyphVector;
38 import java.awt.font.LineMetrics;
39 import java.awt.font.TextAttribute;
40 import java.awt.geom.AffineTransform;
41 import java.awt.geom.GeneralPath;
42 import java.awt.geom.NoninvertibleTransformException;
43 import java.awt.geom.PathIterator;
44 import java.awt.geom.Point2D;
45 import java.awt.geom.Rectangle2D;
46 import java.lang.ref.SoftReference;
47 import java.text.CharacterIterator;
48 
49 import sun.awt.SunHints;
50 import sun.java2d.loops.FontInfo;
51 
52 /**
53  * Standard implementation of GlyphVector used by Font, GlyphList, and
54  * SunGraphics2D.
55  *
56  * The main issues involve the semantics of the various transforms
57  * (font, glyph, device) and their effect on rendering and metrics.
58  *
59  * Very, very unfortunately, the translation component of the font
60  * transform affects where the text gets rendered.  It offsets the
61  * rendering origin.  None of the other metrics of the glyphvector
62  * are affected, making them inconsistent with the rendering behavior.
63  * I think the translation component of the font would be better
64  * interpreted as the translation component of a per-glyph transform,
65  * but I don't know if this is possible to change.
66  *
67  * After the font transform is applied, the glyph transform is
68  * applied.  This makes glyph transforms relative to font transforms,
69  * if the font transform changes, the glyph transform will have the
70  * same (relative) effect on the outline of the glyph.  The outline
71  * and logical bounds are passed through the glyph transform before
72  * being returned.  The glyph metrics ignore the glyph transform, but
73  * provide the outline bounds and the advance vector of the glyph (the
74  * latter will be rotated if the font is rotated).  The default layout
75  * places each glyph at the end of the advance vector of the previous
76  * glyph, and since the glyph transform translates the advance vector,
77  * this means a glyph transform affects the positions of all
78  * subsequent glyphs if defaultLayout is called after setting a glyph
79  * transform.  In the glyph info array, the bounds are the outline
80  * bounds including the glyph transform, and the positions are as
81  * computed, and the advances are the deltas between the positions.
82  *
83  * (There's a bug in the logical bounds of a rotated glyph for
84  * composite fonts, it's not to spec (in 1.4.0, 1.4.1, 1.4.2).  The
85  * problem is that the rotated composite doesn't handle the multiple
86  * ascents and descents properly in both x and y.  You end up with
87  * a rotated advance vector but an unrotated ascent and descent.)
88  *
89  * Finally, the whole thing is transformed by the device transform to
90  * position it on the page.
91  *
92  * Another bug: The glyph outline seems to ignore fractional point
93  * size information, but the images (and advances) don't ignore it.
94  *
95  * Small fonts drawn at large magnification have odd advances when
96  * fractional metrics is off-- that's because the advances depend on
97  * the frc.  When the frc is scaled appropriately, the advances are
98  * fine.  FM or a large frc (high numbers) make the advances right.
99  *
100  * The buffer aa flag doesn't affect rendering, the glyph vector
101  * renders as AA if aa is set in its frc, and as non-aa if aa is not
102  * set in its frc.
103  *
104  * font rotation, baseline, vertical etc.
105  *
106  * Font rotation and baseline Line metrics should be measured along a
107  * unit vector pi/4 cc from the baseline vector.  For 'horizontal'
108  * fonts the baseline vector is the x vector passed through the font
109  * transform (ignoring translation), for 'vertical' it is the y
110  * vector.  This definition makes ascent, descent, etc independent of
111  * shear, so shearing can be used to simulate italic. This means no
112  * fonts have 'negative ascents' or 'zero ascents' etc.
113  *
114  * Having a coordinate system with orthogonal axes where one is
115  * parallel to the baseline means we could use rectangles and interpret
116  * them in terms of this coordinate system.  Unfortunately there
117  * is support for rotated fonts in the jdk already so maintaining
118  * the semantics of existing code (getlogical bounds, etc) might
119  * be difficult.
120  *
121  * A font transform transforms both the baseline and all the glyphs
122  * in the font, so it does not rotate the glyph w.r.t the baseline.
123  * If you do want to rotate individual glyphs, you need to apply a
124  * glyph transform.  If performDefaultLayout is called after this,
125  * the transformed glyph advances will affect the glyph positions.
126  *
127  * useful additions
128  * - select vertical metrics - glyphs are rotated pi/4 cc and vertical
129  * metrics are used to align them to the baseline.
130  * - define baseline for font (glyph rotation not linked to baseline)
131  * - define extra space (delta between each glyph along baseline)
132  * - define offset (delta from 'true' baseline, impacts ascent and
133  * descent as these are still computed from true basline and pinned
134  * to zero, used in superscript).
135  */
136 public class StandardGlyphVector extends GlyphVector {
137     private Font font;
138     private FontRenderContext frc;
139     private int[] glyphs; // always
140     private int[] userGlyphs; // used to return glyphs to the client.
141     private float[] positions; // only if not default advances
142     private int[] charIndices;  // only if interesting
143     private int flags; // indicates whether positions, charIndices is interesting
144 
145     private static final int UNINITIALIZED_FLAGS = -1;
146 
147     // transforms information
148     private GlyphTransformInfo gti; // information about per-glyph transforms
149 
150     // !!! can we get rid of any of this extra stuff?
151     private AffineTransform ftx;   // font transform without translation
152     private AffineTransform dtx;   // device transform used for strike calculations, no translation
153     private AffineTransform invdtx; // inverse of dtx or null if dtx is identity
154     private AffineTransform frctx; // font render context transform, wish we could just share it
155     private Font2D font2D;         // basic strike-independent stuff
156     private SoftReference<GlyphStrike> fsref;   // font strike reference for glyphs with no per-glyph transform
157 
158     /////////////////////////////
159     // Constructors and Factory methods
160     /////////////////////////////
161 
StandardGlyphVector(Font font, String str, FontRenderContext frc)162     public StandardGlyphVector(Font font, String str, FontRenderContext frc) {
163         init(font, str.toCharArray(), 0, str.length(), frc, UNINITIALIZED_FLAGS);
164     }
165 
StandardGlyphVector(Font font, char[] text, FontRenderContext frc)166     public StandardGlyphVector(Font font, char[] text, FontRenderContext frc) {
167         init(font, text, 0, text.length, frc, UNINITIALIZED_FLAGS);
168     }
169 
StandardGlyphVector(Font font, char[] text, int start, int count, FontRenderContext frc)170     public StandardGlyphVector(Font font, char[] text, int start, int count,
171                                FontRenderContext frc) {
172         init(font, text, start, count, frc, UNINITIALIZED_FLAGS);
173     }
174 
getTracking(Font font)175     private float getTracking(Font font) {
176         if (font.hasLayoutAttributes()) {
177             AttributeValues values = ((AttributeMap)font.getAttributes()).getValues();
178             return values.getTracking();
179         }
180         return 0;
181     }
182 
183      // used by GlyphLayout to construct a glyphvector
StandardGlyphVector(Font font, FontRenderContext frc, int[] glyphs, float[] positions, int[] indices, int flags)184     public StandardGlyphVector(Font font, FontRenderContext frc, int[] glyphs, float[] positions,
185                                int[] indices, int flags) {
186         initGlyphVector(font, frc, glyphs, positions, indices, flags);
187 
188         // this code should go into layout
189         float track = getTracking(font);
190         if (track != 0) {
191             track *= font.getSize2D();
192             Point2D.Float trackPt = new Point2D.Float(track, 0); // advance delta
193             if (font.isTransformed()) {
194                 AffineTransform at = font.getTransform();
195                 at.deltaTransform(trackPt, trackPt);
196             }
197 
198             // how do we know its a base glyph
199             // for now, it is if the natural advance of the glyph is non-zero
200             Font2D f2d = FontUtilities.getFont2D(font);
201             FontStrike strike = f2d.getStrike(font, frc);
202 
203             float[] deltas = { trackPt.x, trackPt.y };
204             for (int j = 0; j < deltas.length; ++j) {
205                 float inc = deltas[j];
206                 if (inc != 0) {
207                     float delta = 0;
208                     for (int i = j, n = 0; n < glyphs.length; i += 2) {
209                         if (strike.getGlyphAdvance(glyphs[n++]) != 0) { // might be an inadequate test
210                             positions[i] += delta;
211                             delta += inc;
212                         }
213                     }
214                     positions[positions.length-2+j] += delta;
215                 }
216             }
217         }
218     }
219 
initGlyphVector(Font font, FontRenderContext frc, int[] glyphs, float[] positions, int[] indices, int flags)220     public void initGlyphVector(Font font, FontRenderContext frc, int[] glyphs, float[] positions,
221                                 int[] indices, int flags) {
222         this.font = font;
223         this.frc = frc;
224         this.glyphs = glyphs;
225         this.userGlyphs = glyphs; // no need to check
226         this.positions = positions;
227         this.charIndices = indices;
228         this.flags = flags;
229 
230         initFontData();
231     }
232 
StandardGlyphVector(Font font, CharacterIterator iter, FontRenderContext frc)233     public StandardGlyphVector(Font font, CharacterIterator iter, FontRenderContext frc) {
234         int offset = iter.getBeginIndex();
235         char[] text = new char [iter.getEndIndex() - offset];
236         for(char c = iter.first();
237             c != CharacterIterator.DONE;
238             c = iter.next()) {
239             text[iter.getIndex() - offset] = c;
240         }
241         init(font, text, 0, text.length, frc, UNINITIALIZED_FLAGS);
242     }
243 
StandardGlyphVector(Font font, int[] glyphs, FontRenderContext frc)244     public StandardGlyphVector(Font font, int[] glyphs, FontRenderContext frc) {
245         // !!! find callers of this
246         // should be able to fully init from raw data, e.g. charmap, flags too.
247         this.font = font;
248         this.frc = frc;
249         this.flags = UNINITIALIZED_FLAGS;
250 
251         initFontData();
252         this.userGlyphs = glyphs;
253         this.glyphs = getValidatedGlyphs(this.userGlyphs);
254     }
255 
256     /* This is called from the rendering loop. FontInfo is supplied
257      * because a GV caches a strike and glyph images suitable for its FRC.
258      * LCD text isn't currently supported on all surfaces, in which case
259      * standard AA must be used. This is most likely to occur when LCD text
260      * is requested and the surface is some non-standard type or hardward
261      * surface for which there are no accelerated loops.
262      * We can detect this as being AA=="ON" in the FontInfo and AA!="ON"
263      * and AA!="GASP" in the FRC - since this only occurs for LCD text we don't
264      * need to check any more precisely what value is in the FRC.
265      */
getStandardGV(GlyphVector gv, FontInfo info)266     public static StandardGlyphVector getStandardGV(GlyphVector gv,
267                                                     FontInfo info) {
268         if (info.aaHint == SunHints.INTVAL_TEXT_ANTIALIAS_ON) {
269             Object aaHint = gv.getFontRenderContext().getAntiAliasingHint();
270             if (aaHint != VALUE_TEXT_ANTIALIAS_ON &&
271                 aaHint != VALUE_TEXT_ANTIALIAS_GASP) {
272                 /* We need to create a new GV with AA==ON for rendering */
273                 FontRenderContext frc = gv.getFontRenderContext();
274                 frc = new FontRenderContext(frc.getTransform(),
275                                             VALUE_TEXT_ANTIALIAS_ON,
276                                             frc.getFractionalMetricsHint());
277                 return new StandardGlyphVector(gv, frc);
278             }
279         }
280         if (gv instanceof StandardGlyphVector) {
281             return (StandardGlyphVector)gv;
282         }
283         return new StandardGlyphVector(gv, gv.getFontRenderContext());
284     }
285 
286     /////////////////////////////
287     // GlyphVector API
288     /////////////////////////////
289 
getFont()290     public Font getFont() {
291         return this.font;
292     }
293 
getFontRenderContext()294     public FontRenderContext getFontRenderContext() {
295         return this.frc;
296     }
297 
performDefaultLayout()298     public void performDefaultLayout() {
299         positions = null;
300         if (getTracking(font) == 0) {
301             clearFlags(FLAG_HAS_POSITION_ADJUSTMENTS);
302         }
303     }
304 
getNumGlyphs()305     public int getNumGlyphs() {
306         return glyphs.length;
307     }
308 
getGlyphCode(int glyphIndex)309     public int getGlyphCode(int glyphIndex) {
310         return userGlyphs[glyphIndex];
311     }
312 
getGlyphCodes(int start, int count, int[] result)313     public int[] getGlyphCodes(int start, int count, int[] result) {
314         if (count < 0) {
315             throw new IllegalArgumentException("count = " + count);
316         }
317         if (start < 0) {
318             throw new IndexOutOfBoundsException("start = " + start);
319         }
320         if (start > glyphs.length - count) { // watch out for overflow if index + count overlarge
321             throw new IndexOutOfBoundsException("start + count = " + (start + count));
322         }
323 
324         if (result == null) {
325             result = new int[count];
326         }
327 
328         // if arraycopy were faster, we wouldn't code this
329         for (int i = 0; i < count; ++i) {
330             result[i] = userGlyphs[i + start];
331         }
332 
333         return result;
334     }
335 
getGlyphCharIndex(int ix)336     public int getGlyphCharIndex(int ix) {
337         if (ix < 0 && ix >= glyphs.length) {
338             throw new IndexOutOfBoundsException("" + ix);
339         }
340         if (charIndices == null) {
341             if ((getLayoutFlags() & FLAG_RUN_RTL) != 0) {
342                 return glyphs.length - 1 - ix;
343             }
344             return ix;
345         }
346         return charIndices[ix];
347     }
348 
getGlyphCharIndices(int start, int count, int[] result)349     public int[] getGlyphCharIndices(int start, int count, int[] result) {
350         if (start < 0 || count < 0 || (count > glyphs.length - start)) {
351             throw new IndexOutOfBoundsException("" + start + ", " + count);
352         }
353         if (result == null) {
354             result = new int[count];
355         }
356         if (charIndices == null) {
357             if ((getLayoutFlags() & FLAG_RUN_RTL) != 0) {
358                 for (int i = 0, n = glyphs.length - 1 - start;
359                      i < count; ++i, --n) {
360                          result[i] = n;
361                      }
362             } else {
363                 for (int i = 0, n = start; i < count; ++i, ++n) {
364                     result[i] = n;
365                 }
366             }
367         } else {
368             for (int i = 0; i < count; ++i) {
369                 result[i] = charIndices[i + start];
370             }
371         }
372         return result;
373     }
374 
375     // !!! not cached, assume TextLayout will cache if necessary
376     // !!! reexamine for per-glyph-transforms
377     // !!! revisit for text-on-a-path, vertical
getLogicalBounds()378     public Rectangle2D getLogicalBounds() {
379         setFRCTX();
380         initPositions();
381 
382         LineMetrics lm = font.getLineMetrics("", frc);
383 
384         float minX, minY, maxX, maxY;
385         // horiz only for now...
386         minX = 0;
387         minY = -lm.getAscent();
388         maxX = 0;
389         maxY = lm.getDescent() + lm.getLeading();
390         if (glyphs.length > 0) {
391             maxX = positions[positions.length - 2];
392         }
393 
394         return new Rectangle2D.Float(minX, minY, maxX - minX, maxY - minY);
395     }
396 
397     // !!! not cached, assume TextLayout will cache if necessary
getVisualBounds()398     public Rectangle2D getVisualBounds() {
399         Rectangle2D result = null;
400         for (int i = 0; i < glyphs.length; ++i) {
401             Rectangle2D glyphVB = getGlyphVisualBounds(i).getBounds2D();
402             if (!glyphVB.isEmpty()) {
403                 if (result == null) {
404                     result = glyphVB;
405                 } else {
406                     Rectangle2D.union(result, glyphVB, result);
407                 }
408             }
409         }
410         if (result == null) {
411             result = new Rectangle2D.Float(0, 0, 0, 0);
412         }
413         return result;
414     }
415 
416     // !!! not cached, assume TextLayout will cache if necessary
417     // !!! fontStrike needs a method for this
getPixelBounds(FontRenderContext renderFRC, float x, float y)418     public Rectangle getPixelBounds(FontRenderContext renderFRC, float x, float y) {
419       return getGlyphsPixelBounds(renderFRC, x, y, 0, glyphs.length);
420     }
421 
getOutline()422     public Shape getOutline() {
423         return getGlyphsOutline(0, glyphs.length, 0, 0);
424     }
425 
getOutline(float x, float y)426     public Shape getOutline(float x, float y) {
427         return getGlyphsOutline(0, glyphs.length, x, y);
428     }
429 
430     // relative to gv origin
getGlyphOutline(int ix)431     public Shape getGlyphOutline(int ix) {
432         return getGlyphsOutline(ix, 1, 0, 0);
433     }
434 
435     // relative to gv origin offset by x, y
getGlyphOutline(int ix, float x, float y)436     public Shape getGlyphOutline(int ix, float x, float y) {
437         return getGlyphsOutline(ix, 1, x, y);
438     }
439 
getGlyphPosition(int ix)440     public Point2D getGlyphPosition(int ix) {
441         initPositions();
442 
443         ix *= 2;
444         return new Point2D.Float(positions[ix], positions[ix + 1]);
445     }
446 
setGlyphPosition(int ix, Point2D pos)447     public void setGlyphPosition(int ix, Point2D pos) {
448         if (ix < 0 || ix > glyphs.length) {
449             throw new IndexOutOfBoundsException("ix = " + ix);
450         }
451 
452         initPositions();
453 
454         int ix2 = ix << 1;
455         positions[ix2] = (float)pos.getX();
456         positions[ix2 + 1] = (float)pos.getY();
457 
458         if (ix < glyphs.length) {
459             clearCaches(ix);
460         }
461         addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);
462     }
463 
getGlyphTransform(int ix)464     public AffineTransform getGlyphTransform(int ix) {
465         if (ix < 0 || ix >= glyphs.length) {
466             throw new IndexOutOfBoundsException("ix = " + ix);
467         }
468         if (gti != null) {
469             return gti.getGlyphTransform(ix);
470         }
471         return null; // spec'd as returning null
472     }
473 
setGlyphTransform(int ix, AffineTransform newTX)474     public void setGlyphTransform(int ix, AffineTransform newTX) {
475         if (ix < 0 || ix >= glyphs.length) {
476             throw new IndexOutOfBoundsException("ix = " + ix);
477         }
478 
479         if (gti == null) {
480             if (newTX == null || newTX.isIdentity()) {
481                 return;
482             }
483             gti = new GlyphTransformInfo(this);
484         }
485         gti.setGlyphTransform(ix, newTX); // sets flags
486         if (gti.transformCount() == 0) {
487             gti = null;
488         }
489     }
490 
getLayoutFlags()491     public int getLayoutFlags() {
492         if (flags == UNINITIALIZED_FLAGS) {
493             flags = 0;
494 
495             if (charIndices != null && glyphs.length > 1) {
496                 boolean ltr = true;
497                 boolean rtl = true;
498 
499                 int rtlix = charIndices.length; // rtl index
500                 for (int i = 0; i < charIndices.length && (ltr || rtl); ++i) {
501                     int cx = charIndices[i];
502 
503                     ltr = ltr && (cx == i);
504                     rtl = rtl && (cx == --rtlix);
505                 }
506 
507                 if (rtl) flags |= FLAG_RUN_RTL;
508                 if (!rtl && !ltr) flags |= FLAG_COMPLEX_GLYPHS;
509             }
510         }
511 
512         return flags;
513     }
514 
getGlyphPositions(int start, int count, float[] result)515     public float[] getGlyphPositions(int start, int count, float[] result) {
516         if (count < 0) {
517             throw new IllegalArgumentException("count = " + count);
518         }
519         if (start < 0) {
520             throw new IndexOutOfBoundsException("start = " + start);
521         }
522         if (start > glyphs.length + 1 - count) { // watch for overflow
523             throw new IndexOutOfBoundsException("start + count = " + (start + count));
524         }
525 
526         return internalGetGlyphPositions(start, count, 0, result);
527     }
528 
getGlyphLogicalBounds(int ix)529     public Shape getGlyphLogicalBounds(int ix) {
530         if (ix < 0 || ix >= glyphs.length) {
531             throw new IndexOutOfBoundsException("ix = " + ix);
532         }
533 
534         Shape[] lbcache;
535         if (lbcacheRef == null || (lbcache = lbcacheRef.get()) == null) {
536             lbcache = new Shape[glyphs.length];
537             lbcacheRef = new SoftReference<>(lbcache);
538         }
539 
540         Shape result = lbcache[ix];
541         if (result == null) {
542             setFRCTX();
543             initPositions();
544 
545             // !!! ought to return a rectangle2d for simple cases, though the following works for all
546 
547             // get the position, the tx offset, and the x,y advance and x,y adl.  The
548             // shape is the box formed by adv (width) and adl (height) offset by
549             // the position plus the tx offset minus the ascent.
550 
551             ADL adl = new ADL();
552             GlyphStrike gs = getGlyphStrike(ix);
553             gs.getADL(adl);
554 
555             Point2D.Float adv = gs.strike.getGlyphMetrics(glyphs[ix]);
556 
557             float wx = adv.x;
558             float wy = adv.y;
559             float hx = adl.descentX + adl.leadingX + adl.ascentX;
560             float hy = adl.descentY + adl.leadingY + adl.ascentY;
561             float x = positions[ix*2] + gs.dx - adl.ascentX;
562             float y = positions[ix*2+1] + gs.dy - adl.ascentY;
563 
564             GeneralPath gp = new GeneralPath();
565             gp.moveTo(x, y);
566             gp.lineTo(x + wx, y + wy);
567             gp.lineTo(x + wx + hx, y + wy + hy);
568             gp.lineTo(x + hx, y + hy);
569             gp.closePath();
570 
571             result = new DelegatingShape(gp);
572             lbcache[ix] = result;
573         }
574 
575         return result;
576     }
577     private SoftReference<Shape[]> lbcacheRef;
578 
getGlyphVisualBounds(int ix)579     public Shape getGlyphVisualBounds(int ix) {
580         if (ix < 0 || ix >= glyphs.length) {
581             throw new IndexOutOfBoundsException("ix = " + ix);
582         }
583 
584         Shape[] vbcache;
585         if (vbcacheRef == null || (vbcache = vbcacheRef.get()) == null) {
586             vbcache = new Shape[glyphs.length];
587             vbcacheRef = new SoftReference<>(vbcache);
588         }
589 
590         Shape result = vbcache[ix];
591         if (result == null) {
592             result = new DelegatingShape(getGlyphOutlineBounds(ix));
593             vbcache[ix] = result;
594         }
595 
596         return result;
597     }
598     private SoftReference<Shape[]> vbcacheRef;
599 
getGlyphPixelBounds(int index, FontRenderContext renderFRC, float x, float y)600     public Rectangle getGlyphPixelBounds(int index, FontRenderContext renderFRC, float x, float y) {
601       return getGlyphsPixelBounds(renderFRC, x, y, index, 1);
602     }
603 
getGlyphMetrics(int ix)604     public GlyphMetrics getGlyphMetrics(int ix) {
605         if (ix < 0 || ix >= glyphs.length) {
606             throw new IndexOutOfBoundsException("ix = " + ix);
607         }
608 
609         Rectangle2D vb = getGlyphVisualBounds(ix).getBounds2D();
610         Point2D pt = getGlyphPosition(ix);
611         vb.setRect(vb.getMinX() - pt.getX(),
612                    vb.getMinY() - pt.getY(),
613                    vb.getWidth(),
614                    vb.getHeight());
615         Point2D.Float adv =
616             getGlyphStrike(ix).strike.getGlyphMetrics(glyphs[ix]);
617         GlyphMetrics gm = new GlyphMetrics(true, adv.x, adv.y,
618                                            vb,
619                                            GlyphMetrics.STANDARD);
620         return gm;
621     }
622 
getGlyphJustificationInfo(int ix)623     public GlyphJustificationInfo getGlyphJustificationInfo(int ix) {
624         if (ix < 0 || ix >= glyphs.length) {
625             throw new IndexOutOfBoundsException("ix = " + ix);
626         }
627 
628         // currently we don't have enough information to do this right.  should
629         // get info from the font and use real OT/GX justification.  Right now
630         // sun/font/ExtendedTextSourceLabel assigns one of three infos
631         // based on whether the char is kanji, space, or other.
632 
633         return null;
634     }
635 
equals(GlyphVector rhs)636     public boolean equals(GlyphVector rhs) {
637         if (this == rhs) {
638             return true;
639         }
640         if (rhs == null) {
641             return false;
642         }
643 
644         try {
645             StandardGlyphVector other = (StandardGlyphVector)rhs;
646 
647             if (glyphs.length != other.glyphs.length) {
648                 return false;
649             }
650 
651             for (int i = 0; i < glyphs.length; ++i) {
652                 if (glyphs[i] != other.glyphs[i]) {
653                     return false;
654                 }
655             }
656 
657             if (!font.equals(other.font)) {
658                 return false;
659             }
660 
661             if (!frc.equals(other.frc)) {
662                 return false;
663             }
664 
665             if ((other.positions == null) != (positions == null)) {
666                 if (positions == null) {
667                     initPositions();
668                 } else {
669                     other.initPositions();
670                 }
671             }
672 
673             if (positions != null) {
674                 for (int i = 0; i < positions.length; ++i) {
675                     if (positions[i] != other.positions[i]) {
676                         return false;
677                     }
678                 }
679             }
680 
681             if (gti == null) {
682                 return other.gti == null;
683             } else {
684                 return gti.equals(other.gti);
685             }
686         }
687         catch (ClassCastException e) {
688             // assume they are different simply by virtue of the class difference
689 
690             return false;
691         }
692     }
693 
694     /**
695      * As a concrete subclass of Object that implements equality, this must
696      * implement hashCode.
697      */
hashCode()698     public int hashCode() {
699         return font.hashCode() ^ glyphs.length;
700     }
701 
702     /**
703      * Since we implement equality comparisons for GlyphVector, we implement
704      * the inherited Object.equals(Object) as well.  GlyphVector should do
705      * this, and define two glyphvectors as not equal if the classes differ.
706      */
equals(Object rhs)707     public boolean equals(Object rhs) {
708         try {
709             return equals((GlyphVector)rhs);
710         }
711         catch (ClassCastException e) {
712             return false;
713         }
714     }
715 
716     /**
717      * Sometimes I wish java had covariant return types...
718      */
copy()719     public StandardGlyphVector copy() {
720         return (StandardGlyphVector)clone();
721     }
722 
723     /**
724      * As a concrete subclass of GlyphVector, this must implement clone.
725      */
clone()726     public Object clone() {
727         // positions, gti are mutable so we have to clone them
728         // font2d can be shared
729         // fsref is a cache and can be shared
730         try {
731             StandardGlyphVector result = (StandardGlyphVector)super.clone();
732 
733             result.clearCaches();
734 
735             if (positions != null) {
736                 result.positions = positions.clone();
737             }
738 
739             if (gti != null) {
740                 result.gti = new GlyphTransformInfo(result, gti);
741             }
742 
743             return result;
744         }
745         catch (CloneNotSupportedException e) {
746         }
747 
748         return this;
749     }
750 
751     //////////////////////
752     // StandardGlyphVector new public methods
753     /////////////////////
754 
755     /*
756      * Set a multiple glyph positions at one time.  GlyphVector only
757      * provides API to set a single glyph at a time.
758      */
setGlyphPositions(float[] srcPositions, int srcStart, int start, int count)759     public void setGlyphPositions(float[] srcPositions, int srcStart,
760                                   int start, int count) {
761         if (count < 0) {
762             throw new IllegalArgumentException("count = " + count);
763         }
764 
765         initPositions();
766         for (int i = start * 2, e = i + count * 2, p = srcStart; i < e; ++i, ++p) {
767             positions[i] = srcPositions[p];
768         }
769 
770         clearCaches();
771         addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);
772     }
773 
774     /**
775      * Set all the glyph positions, including the 'after last glyph' position.
776      * The srcPositions array must be of length (numGlyphs + 1) * 2.
777      */
setGlyphPositions(float[] srcPositions)778     public void setGlyphPositions(float[] srcPositions) {
779         int requiredLength = glyphs.length * 2 + 2;
780         if (srcPositions.length != requiredLength) {
781             throw new IllegalArgumentException("srcPositions.length != " + requiredLength);
782         }
783 
784         positions = srcPositions.clone();
785 
786         clearCaches();
787         addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);
788     }
789 
790     /**
791      * This is a convenience overload that gets all the glyph positions, which
792      * is what you usually want to do if you're getting more than one.
793      * !!! should I bother taking result parameter?
794      */
getGlyphPositions(float[] result)795     public float[] getGlyphPositions(float[] result) {
796         return internalGetGlyphPositions(0, glyphs.length + 1, 0, result);
797     }
798 
799     /**
800      * Get transform information for the requested range of glyphs.
801      * If no glyphs have a transform, return null.
802      * If a glyph has no transform (or is the identity transform) its entry in the result array will be null.
803      * If the passed-in result is null an array will be allocated for the caller.
804      * Each transform instance in the result array will unique, and independent of the GlyphVector's transform.
805      */
getGlyphTransforms(int start, int count, AffineTransform[] result)806     public AffineTransform[] getGlyphTransforms(int start, int count, AffineTransform[] result) {
807         if (start < 0 || count < 0 || start + count > glyphs.length) {
808             throw new IllegalArgumentException("start: " + start + " count: " + count);
809         }
810 
811         if (gti == null) {
812             return null;
813         }
814 
815         if (result == null) {
816             result = new AffineTransform[count];
817         }
818 
819         for (int i = 0; i < count; ++i, ++start) {
820             result[i] = gti.getGlyphTransform(start);
821         }
822 
823         return result;
824     }
825 
826     /**
827      * Convenience overload for getGlyphTransforms(int, int, AffineTransform[], int);
828      */
getGlyphTransforms()829     public AffineTransform[] getGlyphTransforms() {
830         return getGlyphTransforms(0, glyphs.length, null);
831     }
832 
833     /**
834      * Set a number of glyph transforms.
835      * Original transforms are unchanged.  The array may contain nulls, and also may
836      * contain multiple references to the same transform instance.
837      */
setGlyphTransforms(AffineTransform[] srcTransforms, int srcStart, int start, int count)838     public void setGlyphTransforms(AffineTransform[] srcTransforms, int srcStart, int start, int count) {
839         for (int i = start, e = start + count; i < e; ++i) {
840             setGlyphTransform(i, srcTransforms[srcStart + i]);
841         }
842     }
843 
844     /**
845      * Convenience overload of setGlyphTransforms(AffineTransform[], int, int, int).
846      */
setGlyphTransforms(AffineTransform[] srcTransforms)847     public void setGlyphTransforms(AffineTransform[] srcTransforms) {
848         setGlyphTransforms(srcTransforms, 0, 0, glyphs.length);
849     }
850 
851     /**
852      * For each glyph return posx, posy, advx, advy, visx, visy, visw, vish.
853      */
getGlyphInfo()854     public float[] getGlyphInfo() {
855         setFRCTX();
856         initPositions();
857         float[] result = new float[glyphs.length * 8];
858         for (int i = 0, n = 0; i < glyphs.length; ++i, n += 8) {
859             float x = positions[i*2];
860             float y = positions[i*2+1];
861             result[n] = x;
862             result[n+1] = y;
863 
864             int glyphID = glyphs[i];
865             GlyphStrike s = getGlyphStrike(i);
866             Point2D.Float adv = s.strike.getGlyphMetrics(glyphID);
867             result[n+2] = adv.x;
868             result[n+3] = adv.y;
869 
870             Rectangle2D vb = getGlyphVisualBounds(i).getBounds2D();
871             result[n+4] = (float)(vb.getMinX());
872             result[n+5] = (float)(vb.getMinY());
873             result[n+6] = (float)(vb.getWidth());
874             result[n+7] = (float)(vb.getHeight());
875         }
876         return result;
877     }
878 
879     //////////////////////
880     // StandardGlyphVector package private methods
881     /////////////////////
882 
883     // used by glyphlist to determine if it needs to allocate/size positions array
884     // gti always uses positions because the gtx might have translation.  We also
885     // need positions if the rendering dtx is different from the frctx.
886 
needsPositions(double[] devTX)887     boolean needsPositions(double[] devTX) {
888         return gti != null ||
889             (getLayoutFlags() & FLAG_HAS_POSITION_ADJUSTMENTS) != 0 ||
890             !matchTX(devTX, frctx);
891     }
892 
893     // used by glyphList to get strong refs to font strikes for duration of rendering call
894     // if devTX matches current devTX, we're ready to go
895     // if we don't have multiple transforms, we're already ok
896 
897     // !!! I'm not sure fontInfo works so well for glyphvector, since we have to be able to handle
898     // the multiple-strikes case
899 
900     /*
901      * GlyphList calls this to set up its images data.  First it calls needsPositions,
902      * passing the devTX, to see if it should provide us a positions array to fill.
903      * It only doesn't need them if we're a simple glyph vector whose frctx matches the
904      * devtx.
905      * Then it calls setupGlyphImages.  If we need positions, we make sure we have our
906      * default positions based on the frctx first. Then we set the devTX, and use
907      * strikes based on it to generate the images.  Finally, we fill in the positions
908      * array.
909      * If we have transforms, we delegate to gti.  It depends on our having first
910      * initialized the positions and devTX.
911      */
setupGlyphImages(long[] images, float[] positions, double[] devTX)912     Object setupGlyphImages(long[] images, float[] positions, double[] devTX) {
913         initPositions(); // FIRST ensure we have positions based on our frctx
914         setRenderTransform(devTX); // THEN make sure we are using the desired devTX
915 
916         if (gti != null) {
917             return gti.setupGlyphImages(images, positions, dtx);
918         }
919 
920         GlyphStrike gs = getDefaultStrike();
921         gs.strike.getGlyphImagePtrs(glyphs, images, glyphs.length);
922 
923         if (positions != null) {
924             if (dtx.isIdentity()) {
925                 System.arraycopy(this.positions, 0, positions, 0, glyphs.length * 2);
926             } else {
927                 dtx.transform(this.positions, 0, positions, 0, glyphs.length);
928             }
929         }
930 
931         return gs;
932     }
933 
934     //////////////////////
935     // StandardGlyphVector private methods
936     /////////////////////
937 
938     // We keep translation in our frctx since getPixelBounds uses it.  But
939     // GlyphList pulls out the translation and applies it separately, so
940     // we strip it out when we set the dtx.  Basically nothing uses the
941     // translation except getPixelBounds.
942 
943     // called by needsPositions, setRenderTransform
matchTX(double[] lhs, AffineTransform rhs)944     private static boolean matchTX(double[] lhs, AffineTransform rhs) {
945         return
946             lhs[0] == rhs.getScaleX() &&
947             lhs[1] == rhs.getShearY() &&
948             lhs[2] == rhs.getShearX() &&
949             lhs[3] == rhs.getScaleY();
950     }
951 
952     // returns new tx if old one has translation, otherwise returns old one
getNonTranslateTX(AffineTransform tx)953     private static AffineTransform getNonTranslateTX(AffineTransform tx) {
954         if (tx.getTranslateX() != 0 || tx.getTranslateY() != 0) {
955             tx = new AffineTransform(tx.getScaleX(), tx.getShearY(),
956                                      tx.getShearX(), tx.getScaleY(),
957                                      0, 0);
958         }
959         return tx;
960     }
961 
equalNonTranslateTX(AffineTransform lhs, AffineTransform rhs)962     private static boolean equalNonTranslateTX(AffineTransform lhs, AffineTransform rhs) {
963         return lhs.getScaleX() == rhs.getScaleX() &&
964             lhs.getShearY() == rhs.getShearY() &&
965             lhs.getShearX() == rhs.getShearX() &&
966             lhs.getScaleY() == rhs.getScaleY();
967     }
968 
969     // called by setupGlyphImages (after needsPositions, so redundant match check?)
setRenderTransform(double[] devTX)970     private void setRenderTransform(double[] devTX) {
971         assert(devTX.length == 4);
972         if (!matchTX(devTX, dtx)) {
973             resetDTX(new AffineTransform(devTX)); // no translation since devTX len == 4.
974         }
975     }
976 
977     // called by getGlyphsPixelBounds
setDTX(AffineTransform tx)978     private void setDTX(AffineTransform tx) {
979         if (!equalNonTranslateTX(dtx, tx)) {
980             resetDTX(getNonTranslateTX(tx));
981         }
982     }
983 
984     // called by most functions
setFRCTX()985     private void setFRCTX() {
986         if (!equalNonTranslateTX(frctx, dtx)) {
987             resetDTX(getNonTranslateTX(frctx));
988         }
989     }
990 
991     /**
992      * Change the dtx for the strike refs we use.  Keeps a reference to the at.  At
993      * must not contain translation.
994      * Called by setRenderTransform, setDTX, initFontData.
995      */
resetDTX(AffineTransform at)996     private void resetDTX(AffineTransform at) {
997         fsref = null;
998         dtx = at;
999         invdtx = null;
1000         if (!dtx.isIdentity()) {
1001             try {
1002                 invdtx = dtx.createInverse();
1003             }
1004             catch (NoninvertibleTransformException e) {
1005                 // we needn't care for rendering
1006             }
1007         }
1008         if (gti != null) {
1009             gti.strikesRef = null;
1010         }
1011     }
1012 
1013     /**
1014      * Utility used by getStandardGV.
1015      * Constructs a StandardGlyphVector from a generic glyph vector.
1016      * Do not call this from new contexts without considering the comment
1017      * about "userGlyphs".
1018      */
StandardGlyphVector(GlyphVector gv, FontRenderContext frc)1019     private StandardGlyphVector(GlyphVector gv, FontRenderContext frc) {
1020         this.font = gv.getFont();
1021         this.frc = frc;
1022         initFontData();
1023 
1024         int nGlyphs = gv.getNumGlyphs();
1025         this.userGlyphs = gv.getGlyphCodes(0, nGlyphs, null);
1026         if (gv instanceof StandardGlyphVector) {
1027             /* userGlyphs will be OK because this is a private constructor
1028              * and the returned instance is used only for rendering.
1029              * It's not constructable by user code, nor returned to the
1030              * application. So we know "userGlyphs" are valid as having
1031              * been either already validated or are the result of layout.
1032              */
1033             this.glyphs = userGlyphs;
1034         } else {
1035             this.glyphs = getValidatedGlyphs(this.userGlyphs);
1036         }
1037         this.flags = gv.getLayoutFlags() & FLAG_MASK;
1038 
1039         if ((flags & FLAG_HAS_POSITION_ADJUSTMENTS) != 0) {
1040             this.positions = gv.getGlyphPositions(0, nGlyphs + 1, null);
1041         }
1042 
1043         if ((flags & FLAG_COMPLEX_GLYPHS) != 0) {
1044             this.charIndices = gv.getGlyphCharIndices(0, nGlyphs, null);
1045         }
1046 
1047         if ((flags & FLAG_HAS_TRANSFORMS) != 0) {
1048             AffineTransform[] txs = new AffineTransform[nGlyphs]; // worst case
1049             for (int i = 0; i < nGlyphs; ++i) {
1050                 txs[i] = gv.getGlyphTransform(i); // gv doesn't have getGlyphsTransforms
1051             }
1052 
1053             setGlyphTransforms(txs);
1054         }
1055     }
1056 
1057     /* Before asking the Font we see if the glyph code is
1058      * FFFE or FFFF which are special values that we should be internally
1059      * ready to handle as meaning invisible glyphs. The Font would report
1060      * those as the missing glyph.
1061      */
getValidatedGlyphs(int[] oglyphs)1062     int[] getValidatedGlyphs(int[] oglyphs) {
1063         int len = oglyphs.length;
1064         int[] vglyphs = new int[len];
1065         for (int i=0; i<len; i++) {
1066             if (oglyphs[i] == 0xFFFE || oglyphs[i] == 0xFFFF) {
1067                 vglyphs[i] = oglyphs[i];
1068             } else {
1069                 vglyphs[i] = font2D.getValidatedGlyphCode(oglyphs[i]);
1070             }
1071         }
1072         return vglyphs;
1073     }
1074 
1075     // utility used by constructors
init(Font font, char[] text, int start, int count, FontRenderContext frc, int flags)1076     private void init(Font font, char[] text, int start, int count,
1077                       FontRenderContext frc, int flags) {
1078 
1079         if (start < 0 || count < 0 || start + count > text.length) {
1080             throw new ArrayIndexOutOfBoundsException("start or count out of bounds");
1081         }
1082 
1083         this.font = font;
1084         this.frc = frc;
1085         this.flags = flags;
1086 
1087         if (getTracking(font) != 0) {
1088             addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);
1089         }
1090 
1091         // !!! change mapper interface?
1092         if (start != 0) {
1093             char[] temp = new char[count];
1094             System.arraycopy(text, start, temp, 0, count);
1095             text = temp;
1096         }
1097 
1098         initFontData(); // sets up font2D
1099 
1100         // !!! no layout for now, should add checks
1101         // !!! need to support creating a StandardGlyphVector from a TextMeasurer's info...
1102         glyphs = new int[count]; // hmmm
1103         /* Glyphs obtained here are already validated by the font */
1104         userGlyphs = glyphs;
1105         font2D.getMapper().charsToGlyphs(count, text, glyphs);
1106     }
1107 
initFontData()1108     private void initFontData() {
1109         font2D = FontUtilities.getFont2D(font);
1110         if (font2D instanceof FontSubstitution) {
1111            font2D = ((FontSubstitution)font2D).getCompositeFont2D();
1112         }
1113         float s = font.getSize2D();
1114         if (font.isTransformed()) {
1115             ftx = font.getTransform();
1116             if (ftx.getTranslateX() != 0 || ftx.getTranslateY() != 0) {
1117                 addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);
1118             }
1119             ftx.setTransform(ftx.getScaleX(), ftx.getShearY(), ftx.getShearX(), ftx.getScaleY(), 0, 0);
1120             ftx.scale(s, s);
1121         } else {
1122             ftx = AffineTransform.getScaleInstance(s, s);
1123         }
1124 
1125         frctx = frc.getTransform();
1126         resetDTX(getNonTranslateTX(frctx));
1127     }
1128 
1129     /**
1130      * Copy glyph position data into a result array starting at the indicated
1131      * offset in the array.  If the passed-in result array is null, a new
1132      * array will be allocated and returned.
1133      *
1134      * This is an internal method and does no extra argument checking.
1135      *
1136      * @param start the index of the first glyph to get
1137      * @param count the number of glyphs to get
1138      * @param offset the offset into result at which to put the data
1139      * @param result an array to hold the x,y positions
1140      * @return the modified position array
1141      */
internalGetGlyphPositions(int start, int count, int offset, float[] result)1142     private float[] internalGetGlyphPositions(int start, int count, int offset, float[] result) {
1143         if (result == null) {
1144             result = new float[offset + count * 2];
1145         }
1146 
1147         initPositions();
1148 
1149         // System.arraycopy is slow for stuff like this
1150         for (int i = offset, e = offset + count * 2, p = start * 2; i < e; ++i, ++p) {
1151             result[i] = positions[p];
1152         }
1153 
1154         return result;
1155     }
1156 
getGlyphOutlineBounds(int ix)1157     private Rectangle2D getGlyphOutlineBounds(int ix) {
1158         setFRCTX();
1159         initPositions();
1160         return getGlyphStrike(ix).getGlyphOutlineBounds(glyphs[ix], positions[ix*2], positions[ix*2+1]);
1161     }
1162 
1163     /**
1164      * Used by getOutline, getGlyphsOutline
1165      */
getGlyphsOutline(int start, int count, float x, float y)1166     private Shape getGlyphsOutline(int start, int count, float x, float y) {
1167         setFRCTX();
1168         initPositions();
1169 
1170         GeneralPath result = new GeneralPath(GeneralPath.WIND_NON_ZERO);
1171         for (int i = start, e = start + count, n = start * 2; i < e; ++i, n += 2) {
1172             float px = x + positions[n];
1173             float py = y + positions[n+1];
1174 
1175             getGlyphStrike(i).appendGlyphOutline(glyphs[i], result, px, py);
1176         }
1177 
1178         return result;
1179     }
1180 
getGlyphsPixelBounds(FontRenderContext frc, float x, float y, int start, int count)1181     private Rectangle getGlyphsPixelBounds(FontRenderContext frc, float x, float y, int start, int count) {
1182         initPositions(); // FIRST ensure we have positions based on our frctx
1183 
1184         AffineTransform tx = null;
1185         if (frc == null || frc.equals(this.frc)) {
1186             tx = frctx;
1187         } else {
1188             tx = frc.getTransform();
1189         }
1190         setDTX(tx); // need to get the right strikes, but we use tx itself to translate the points
1191 
1192         if (gti != null) {
1193             return gti.getGlyphsPixelBounds(tx, x, y, start, count);
1194         }
1195 
1196         FontStrike fs = getDefaultStrike().strike;
1197         Rectangle result = null;
1198         Rectangle r = new Rectangle();
1199         Point2D.Float pt = new Point.Float();
1200         int n = start * 2;
1201         while (--count >= 0) {
1202             pt.x = x + positions[n++];
1203             pt.y = y + positions[n++];
1204             tx.transform(pt, pt);
1205             fs.getGlyphImageBounds(glyphs[start++], pt, r);
1206             if (!r.isEmpty()) {
1207                 if (result == null) {
1208                     result = new Rectangle(r);
1209                 } else {
1210                     result.add(r);
1211                 }
1212             }
1213         }
1214         return result != null ? result : r;
1215     }
1216 
clearCaches(int ix)1217     private void clearCaches(int ix) {
1218         if (lbcacheRef != null) {
1219             Shape[] lbcache = lbcacheRef.get();
1220             if (lbcache != null) {
1221                 lbcache[ix] = null;
1222             }
1223         }
1224 
1225         if (vbcacheRef != null) {
1226             Shape[] vbcache = vbcacheRef.get();
1227             if (vbcache != null) {
1228                 vbcache[ix] = null;
1229             }
1230         }
1231     }
1232 
clearCaches()1233     private void clearCaches() {
1234         lbcacheRef = null;
1235         vbcacheRef = null;
1236     }
1237 
1238     // internal use only for possible future extension
1239 
1240     /**
1241      * A flag used with getLayoutFlags that indicates whether this {@code GlyphVector} uses
1242      * a vertical baseline.
1243      */
1244     public static final int FLAG_USES_VERTICAL_BASELINE = 128;
1245 
1246     /**
1247      * A flag used with getLayoutFlags that indicates whether this {@code GlyphVector} uses
1248      * vertical glyph metrics.  A {@code GlyphVector} can use vertical metrics on a
1249      * horizontal line, or vice versa.
1250      */
1251     public static final int FLAG_USES_VERTICAL_METRICS = 256;
1252 
1253     /**
1254      * A flag used with getLayoutFlags that indicates whether this {@code GlyphVector} uses
1255      * the 'alternate orientation.'  Glyphs have a default orientation given a
1256      * particular baseline and metrics orientation, this is the orientation appropriate
1257      * for left-to-right text.  For example, the letter 'A' can have four orientations,
1258      * with the point at 12, 3, 6, or 9 'o clock.  The following table shows where the
1259      * point displays for different values of vertical baseline (vb), vertical
1260      * metrics (vm) and alternate orientation (fo):<br>
1261      * <blockquote>
1262      * vb vm ao
1263      * -- -- --  --
1264      *  f  f  f  12   ^  horizontal metrics on horizontal lines
1265      *  f  f  t   6   v
1266      *  f  t  f   9   <  vertical metrics on horizontal lines
1267      *  f  t  t   3   >
1268      *  t  f  f   3   >  horizontal metrics on vertical lines
1269      *  t  f  t   9   <
1270      *  t  t  f  12   ^  vertical metrics on vertical lines
1271      *  t  t  t   6   v
1272      * </blockquote>
1273      */
1274     public static final int FLAG_USES_ALTERNATE_ORIENTATION = 512;
1275 
1276 
1277     /**
1278      * Ensure that the positions array exists and holds position data.
1279      * If the array is null, this allocates it and sets default positions.
1280      */
initPositions()1281     private void initPositions() {
1282         if (positions == null) {
1283             setFRCTX();
1284 
1285             positions = new float[glyphs.length * 2 + 2];
1286 
1287             Point2D.Float trackPt = null;
1288             float track = getTracking(font);
1289             if (track != 0) {
1290                 track *= font.getSize2D();
1291                 trackPt = new Point2D.Float(track, 0); // advance delta
1292             }
1293 
1294             Point2D.Float pt = new Point2D.Float(0, 0);
1295             if (font.isTransformed()) {
1296                 AffineTransform at = font.getTransform();
1297                 at.transform(pt, pt);
1298                 positions[0] = pt.x;
1299                 positions[1] = pt.y;
1300 
1301                 if (trackPt != null) {
1302                     at.deltaTransform(trackPt, trackPt);
1303                 }
1304             }
1305             for (int i = 0, n = 2; i < glyphs.length; ++i, n += 2) {
1306                 getGlyphStrike(i).addDefaultGlyphAdvance(glyphs[i], pt);
1307                 if (trackPt != null) {
1308                     pt.x += trackPt.x;
1309                     pt.y += trackPt.y;
1310                 }
1311                 positions[n] = pt.x;
1312                 positions[n+1] = pt.y;
1313             }
1314         }
1315     }
1316 
1317     /**
1318      * OR newFlags with existing flags.  First computes existing flags if needed.
1319      */
addFlags(int newflags)1320     private void addFlags(int newflags) {
1321         flags = getLayoutFlags() | newflags;
1322     }
1323 
1324     /**
1325      * AND the complement of clearedFlags with existing flags.  First computes existing flags if needed.
1326      */
clearFlags(int clearedFlags)1327     private void clearFlags(int clearedFlags) {
1328         flags = getLayoutFlags() & ~clearedFlags;
1329     }
1330 
1331     // general utility methods
1332 
1333     // encapsulate the test to check whether we have per-glyph transforms
getGlyphStrike(int ix)1334     private GlyphStrike getGlyphStrike(int ix) {
1335         if (gti == null) {
1336             return getDefaultStrike();
1337         } else {
1338             return gti.getStrike(ix);
1339         }
1340     }
1341 
1342     // encapsulate access to cached default glyph strike
getDefaultStrike()1343     private GlyphStrike getDefaultStrike() {
1344         GlyphStrike gs = null;
1345         if (fsref != null) {
1346             gs = fsref.get();
1347         }
1348         if (gs == null) {
1349             gs = GlyphStrike.create(this, dtx, null);
1350             fsref = new SoftReference<>(gs);
1351         }
1352         return gs;
1353     }
1354 
1355 
1356     /////////////////////
1357     // Internal utility classes
1358     /////////////////////
1359 
1360     // !!! I have this as a separate class instead of just inside SGV,
1361     // but I previously didn't bother.  Now I'm trying this again.
1362     // Probably still not worth it, but I'd like to keep sgv's small in the common case.
1363 
1364     static final class GlyphTransformInfo {
1365         StandardGlyphVector sgv;  // reference back to glyph vector - yuck
1366         int[] indices;            // index into unique strikes
1367         double[] transforms;      // six doubles per unique transform, because AT is a pain to manipulate
1368         SoftReference<GlyphStrike[]> strikesRef; // ref to unique strikes, one per transform
1369         boolean haveAllStrikes;   // true if the strike array has been filled by getStrikes().
1370 
1371         // used when first setting a transform
GlyphTransformInfo(StandardGlyphVector sgv)1372         GlyphTransformInfo(StandardGlyphVector sgv) {
1373             this.sgv = sgv;
1374         }
1375 
1376         // used when cloning a glyph vector, need to set back link
GlyphTransformInfo(StandardGlyphVector sgv, GlyphTransformInfo rhs)1377         GlyphTransformInfo(StandardGlyphVector sgv, GlyphTransformInfo rhs) {
1378             this.sgv = sgv;
1379 
1380             this.indices = rhs.indices == null ? null : rhs.indices.clone();
1381             this.transforms = rhs.transforms == null ? null : rhs.transforms.clone();
1382             this.strikesRef = null; // can't share cache, so rather than clone, we just null out
1383         }
1384 
1385         // used in sgv equality
equals(GlyphTransformInfo rhs)1386         public boolean equals(GlyphTransformInfo rhs) {
1387             if (rhs == null) {
1388                 return false;
1389             }
1390             if (rhs == this) {
1391                 return true;
1392             }
1393             if (this.indices.length != rhs.indices.length) {
1394                 return false;
1395             }
1396             if (this.transforms.length != rhs.transforms.length) {
1397                 return false;
1398             }
1399 
1400             // slow since we end up processing the same transforms multiple
1401             // times, but since transforms can be in any order, we either do
1402             // this or create a mapping.  Equality tests aren't common so
1403             // leave it like this.
1404             for (int i = 0; i < this.indices.length; ++i) {
1405                 int tix = this.indices[i];
1406                 int rix = rhs.indices[i];
1407                 if ((tix == 0) != (rix == 0)) {
1408                     return false;
1409                 }
1410                 if (tix != 0) {
1411                     tix *= 6;
1412                     rix *= 6;
1413                     for (int j = 6; j > 0; --j) {
1414                         if (this.indices[--tix] != rhs.indices[--rix]) {
1415                             return false;
1416                         }
1417                     }
1418                 }
1419             }
1420             return true;
1421         }
1422 
1423         // implements sgv.setGlyphTransform
setGlyphTransform(int glyphIndex, AffineTransform newTX)1424         void setGlyphTransform(int glyphIndex, AffineTransform newTX) {
1425 
1426             // we store all the glyph transforms as a double array, and for each glyph there
1427             // is an entry in the txIndices array indicating which transform to use.  0 means
1428             // there's no transform, 1 means use the first transform (the 6 doubles at offset
1429             // 0), 2 means use the second transform (the 6 doubles at offset 6), etc.
1430             //
1431             // Since this can be called multiple times, and since the number of transforms
1432             // affects the time it takes to construct the glyphs, we try to keep the arrays as
1433             // compact as possible, by removing transforms that are no longer used, and reusing
1434             // transforms where we already have them.
1435 
1436             double[] temp = new double[6];
1437             boolean isIdentity = true;
1438             if (newTX == null || newTX.isIdentity()) {
1439                 // Fill in temp
1440                 temp[0] = temp[3] = 1.0;
1441             }
1442             else {
1443                 isIdentity = false;
1444                 newTX.getMatrix(temp);
1445             }
1446 
1447             if (indices == null) {
1448                 if (isIdentity) { // no change
1449                     return;
1450                 }
1451 
1452                 indices = new int[sgv.glyphs.length];
1453                 indices[glyphIndex] = 1;
1454                 transforms = temp;
1455             } else {
1456                 boolean addSlot = false; // assume we're not growing
1457                 int newIndex = -1;
1458                 if (isIdentity) {
1459                     newIndex = 0; // might shrink
1460                 } else {
1461                     addSlot = true; // assume no match
1462                     int i;
1463                 loop:
1464                     for (i = 0; i < transforms.length; i += 6) {
1465                         for (int j = 0; j < 6; ++j) {
1466                             if (transforms[i + j] != temp[j]) {
1467                                 continue loop;
1468                             }
1469                         }
1470                         addSlot = false;
1471                         break;
1472                     }
1473                     newIndex = i / 6 + 1; // if no match, end of list
1474                 }
1475 
1476                 // if we're using the same transform, nothing to do
1477                 int oldIndex = indices[glyphIndex];
1478                 if (newIndex != oldIndex) {
1479                     // see if we are removing last use of the old slot
1480                     boolean removeSlot = false;
1481                     if (oldIndex != 0) {
1482                         removeSlot = true;
1483                         for (int i = 0; i < indices.length; ++i) {
1484                             if (indices[i] == oldIndex && i != glyphIndex) {
1485                                 removeSlot = false;
1486                                 break;
1487                             }
1488                         }
1489                     }
1490 
1491                     if (removeSlot && addSlot) { // reuse old slot with new transform
1492                         newIndex = oldIndex;
1493                         System.arraycopy(temp, 0, transforms, (newIndex - 1) * 6, 6);
1494                     } else if (removeSlot) {
1495                         if (transforms.length == 6) { // removing last one, so clear arrays
1496                             indices = null;
1497                             transforms = null;
1498 
1499                             sgv.clearCaches(glyphIndex);
1500                             sgv.clearFlags(FLAG_HAS_TRANSFORMS);
1501                             strikesRef = null;
1502 
1503                             return;
1504                         }
1505 
1506                         double[] ttemp = new double[transforms.length - 6];
1507                         System.arraycopy(transforms, 0, ttemp, 0, (oldIndex - 1) * 6);
1508                         System.arraycopy(transforms, oldIndex * 6, ttemp, (oldIndex - 1) * 6,
1509                                          transforms.length - oldIndex * 6);
1510                         transforms = ttemp;
1511 
1512                         // clean up indices
1513                         for (int i = 0; i < indices.length; ++i) {
1514                             if (indices[i] > oldIndex) { // ignore == oldIndex, it's going away
1515                                 indices[i] -= 1;
1516                             }
1517                         }
1518                         if (newIndex > oldIndex) { // don't forget to decrement this too if we need to
1519                             --newIndex;
1520                         }
1521                     } else if (addSlot) {
1522                         double[] ttemp = new double[transforms.length + 6];
1523                         System.arraycopy(transforms, 0, ttemp, 0, transforms.length);
1524                         System.arraycopy(temp, 0, ttemp, transforms.length, 6);
1525                         transforms = ttemp;
1526                     }
1527 
1528                     indices[glyphIndex] = newIndex;
1529                 }
1530             }
1531 
1532             sgv.clearCaches(glyphIndex);
1533             sgv.addFlags(FLAG_HAS_TRANSFORMS);
1534             strikesRef = null;
1535         }
1536 
1537         // implements sgv.getGlyphTransform
getGlyphTransform(int ix)1538         AffineTransform getGlyphTransform(int ix) {
1539             int index = indices[ix];
1540             if (index == 0) {
1541                 return null;
1542             }
1543 
1544             int x = (index - 1) * 6;
1545             return new AffineTransform(transforms[x + 0],
1546                                        transforms[x + 1],
1547                                        transforms[x + 2],
1548                                        transforms[x + 3],
1549                                        transforms[x + 4],
1550                                        transforms[x + 5]);
1551         }
1552 
transformCount()1553         int transformCount() {
1554             if (transforms == null) {
1555                 return 0;
1556             }
1557             return transforms.length / 6;
1558         }
1559 
1560         /**
1561          * The strike cache works like this.
1562          *
1563          * -Each glyph is thought of as having a transform, usually identity.
1564          * -Each request for a strike is based on a device transform, either the
1565          * one in the frc or the rendering transform.
1566          * -For general info, strikes are held with soft references.
1567          * -When rendering, strikes must be held with hard references for the
1568          * duration of the rendering call.  GlyphList will have to hold this
1569          * info along with the image and position info, but toss the strike info
1570          * when done.
1571          * -Build the strike cache as needed.  If the dev transform we want to use
1572          * has changed from the last time it is built, the cache is flushed by
1573          * the caller before these methods are called.
1574          *
1575          * Use a tx that doesn't include translation components of dst tx.
1576          */
setupGlyphImages(long[] images, float[] positions, AffineTransform tx)1577         Object setupGlyphImages(long[] images, float[] positions, AffineTransform tx) {
1578             int len = sgv.glyphs.length;
1579 
1580             GlyphStrike[] sl = getAllStrikes();
1581             for (int i = 0; i < len; ++i) {
1582                 GlyphStrike gs = sl[indices[i]];
1583                 int glyphID = sgv.glyphs[i];
1584                 images[i] = gs.strike.getGlyphImagePtr(glyphID);
1585 
1586                 gs.getGlyphPosition(glyphID, i*2, sgv.positions, positions);
1587             }
1588             tx.transform(positions, 0, positions, 0, len);
1589 
1590             return sl;
1591         }
1592 
getGlyphsPixelBounds(AffineTransform tx, float x, float y, int start, int count)1593         Rectangle getGlyphsPixelBounds(AffineTransform tx, float x, float y, int start, int count) {
1594             Rectangle result = null;
1595             Rectangle r = new Rectangle();
1596             Point2D.Float pt = new Point.Float();
1597             int n = start * 2;
1598             while (--count >= 0) {
1599                 GlyphStrike gs = getStrike(start);
1600                 pt.x = x + sgv.positions[n++] + gs.dx;
1601                 pt.y = y + sgv.positions[n++] + gs.dy;
1602                 tx.transform(pt, pt);
1603                 gs.strike.getGlyphImageBounds(sgv.glyphs[start++], pt, r);
1604                 if (!r.isEmpty()) {
1605                     if (result == null) {
1606                         result = new Rectangle(r);
1607                     } else {
1608                         result.add(r);
1609                     }
1610                 }
1611             }
1612             return result != null ? result : r;
1613         }
1614 
getStrike(int glyphIndex)1615         GlyphStrike getStrike(int glyphIndex) {
1616             if (indices != null) {
1617                 GlyphStrike[] strikes = getStrikeArray();
1618                 return getStrikeAtIndex(strikes, indices[glyphIndex]);
1619             }
1620             return sgv.getDefaultStrike();
1621         }
1622 
getAllStrikes()1623         private GlyphStrike[] getAllStrikes() {
1624             if (indices == null) {
1625                 return null;
1626             }
1627 
1628             GlyphStrike[] strikes = getStrikeArray();
1629             if (!haveAllStrikes) {
1630                 for (int i = 0; i < strikes.length; ++i) {
1631                     getStrikeAtIndex(strikes, i);
1632                 }
1633                 haveAllStrikes = true;
1634             }
1635 
1636             return strikes;
1637         }
1638 
getStrikeArray()1639         private GlyphStrike[] getStrikeArray() {
1640             GlyphStrike[] strikes = null;
1641             if (strikesRef != null) {
1642                 strikes = strikesRef.get();
1643             }
1644             if (strikes == null) {
1645                 haveAllStrikes = false;
1646                 strikes = new GlyphStrike[transformCount() + 1];
1647                 strikesRef = new SoftReference<>(strikes);
1648             }
1649 
1650             return strikes;
1651         }
1652 
getStrikeAtIndex(GlyphStrike[] strikes, int strikeIndex)1653         private GlyphStrike getStrikeAtIndex(GlyphStrike[] strikes, int strikeIndex) {
1654             GlyphStrike strike = strikes[strikeIndex];
1655             if (strike == null) {
1656                 if (strikeIndex == 0) {
1657                     strike = sgv.getDefaultStrike();
1658                 } else {
1659                     int ix = (strikeIndex - 1) * 6;
1660                     AffineTransform gtx = new AffineTransform(transforms[ix],
1661                                                               transforms[ix+1],
1662                                                               transforms[ix+2],
1663                                                               transforms[ix+3],
1664                                                               transforms[ix+4],
1665                                                               transforms[ix+5]);
1666 
1667                     strike = GlyphStrike.create(sgv, sgv.dtx, gtx);
1668                 }
1669                 strikes[strikeIndex] = strike;
1670             }
1671             return strike;
1672         }
1673     }
1674 
1675     // This adjusts the metrics by the translation components of the glyph
1676     // transform.  It is done here since the translation is not known by the
1677     // strike.
1678     // It adjusts the position of the image and the advance.
1679 
1680     public static final class GlyphStrike {
1681         StandardGlyphVector sgv;
1682         FontStrike strike; // hard reference
1683         float dx;
1684         float dy;
1685 
create(StandardGlyphVector sgv, AffineTransform dtx, AffineTransform gtx)1686         static GlyphStrike create(StandardGlyphVector sgv, AffineTransform dtx, AffineTransform gtx) {
1687             float dx = 0;
1688             float dy = 0;
1689 
1690             AffineTransform tx = sgv.ftx;
1691             if (!dtx.isIdentity() || gtx != null) {
1692                 tx = new AffineTransform(sgv.ftx);
1693                 if (gtx != null) {
1694                     tx.preConcatenate(gtx);
1695                     dx = (float)tx.getTranslateX(); // uses ftx then gtx to get translation
1696                     dy = (float)tx.getTranslateY();
1697                 }
1698                 if (!dtx.isIdentity()) {
1699                     tx.preConcatenate(dtx);
1700                 }
1701             }
1702 
1703             int ptSize = 1; // only matters for 'gasp' case.
1704             Object aaHint = sgv.frc.getAntiAliasingHint();
1705             if (aaHint == VALUE_TEXT_ANTIALIAS_GASP) {
1706                 /* Must pass in the calculated point size for rendering.
1707                  * If the glyph tx is anything other than identity or a
1708                  *  simple translate, calculate the transformed point size.
1709                  */
1710                 if (!tx.isIdentity() &&
1711                     (tx.getType() & ~AffineTransform.TYPE_TRANSLATION) != 0) {
1712                     double shearx = tx.getShearX();
1713                     if (shearx != 0) {
1714                         double scaley = tx.getScaleY();
1715                         ptSize =
1716                             (int)Math.sqrt(shearx * shearx + scaley * scaley);
1717                     } else {
1718                         ptSize = (int)(Math.abs(tx.getScaleY()));
1719                     }
1720                 }
1721             }
1722             int aa = FontStrikeDesc.getAAHintIntVal(aaHint,sgv.font2D, ptSize);
1723             int fm = FontStrikeDesc.getFMHintIntVal
1724                 (sgv.frc.getFractionalMetricsHint());
1725             FontStrikeDesc desc = new FontStrikeDesc(dtx,
1726                                                      tx,
1727                                                      sgv.font.getStyle(),
1728                                                      aa, fm);
1729             // Get the strike via the handle. Shouldn't matter
1730             // if we've invalidated the font but its an extra precaution.
1731         // do we want the CompFont from CFont here ?
1732         Font2D f2d = sgv.font2D;
1733         if (f2d instanceof FontSubstitution) {
1734            f2d = ((FontSubstitution)f2d).getCompositeFont2D();
1735         }
1736             FontStrike strike = f2d.handle.font2D.getStrike(desc);  // !!! getStrike(desc, false)
1737 
1738             return new GlyphStrike(sgv, strike, dx, dy);
1739         }
1740 
GlyphStrike(StandardGlyphVector sgv, FontStrike strike, float dx, float dy)1741         private GlyphStrike(StandardGlyphVector sgv, FontStrike strike, float dx, float dy) {
1742             this.sgv = sgv;
1743             this.strike = strike;
1744             this.dx = dx;
1745             this.dy = dy;
1746         }
1747 
getADL(ADL result)1748         void getADL(ADL result) {
1749             StrikeMetrics sm = strike.getFontMetrics();
1750             Point2D.Float delta = null;
1751             if (sgv.font.isTransformed()) {
1752                 delta = new Point2D.Float();
1753                 delta.x = (float)sgv.font.getTransform().getTranslateX();
1754                 delta.y = (float)sgv.font.getTransform().getTranslateY();
1755             }
1756 
1757             result.ascentX = -sm.ascentX;
1758             result.ascentY = -sm.ascentY;
1759             result.descentX = sm.descentX;
1760             result.descentY = sm.descentY;
1761             result.leadingX = sm.leadingX;
1762             result.leadingY = sm.leadingY;
1763         }
1764 
getGlyphPosition(int glyphID, int ix, float[] positions, float[] result)1765         void getGlyphPosition(int glyphID, int ix, float[] positions, float[] result) {
1766             result[ix] = positions[ix] + dx;
1767             ++ix;
1768             result[ix] = positions[ix] + dy;
1769         }
1770 
addDefaultGlyphAdvance(int glyphID, Point2D.Float result)1771         void addDefaultGlyphAdvance(int glyphID, Point2D.Float result) {
1772             // !!! change this API?  Creates unnecessary garbage.  Also the name doesn't quite fit.
1773             // strike.addGlyphAdvance(Point2D.Float adv);  // hey, whaddya know, matches my api :-)
1774             Point2D.Float adv = strike.getGlyphMetrics(glyphID);
1775             result.x += adv.x + dx;
1776             result.y += adv.y + dy;
1777         }
1778 
getGlyphOutlineBounds(int glyphID, float x, float y)1779         Rectangle2D getGlyphOutlineBounds(int glyphID, float x, float y) {
1780             Rectangle2D result = null;
1781             if (sgv.invdtx == null) {
1782                 result = new Rectangle2D.Float();
1783                 result.setRect(strike.getGlyphOutlineBounds(glyphID)); // don't mutate cached rect
1784             } else {
1785                 GeneralPath gp = strike.getGlyphOutline(glyphID, 0, 0);
1786                 gp.transform(sgv.invdtx);
1787                 result = gp.getBounds2D();
1788             }
1789             /* Since x is the logical advance of the glyph to this point.
1790              * Because of the way that Rectangle.union is specified, this
1791              * means that subsequent unioning of a rect including that
1792              * will be affected, even if the glyph is empty. So skip such
1793              * cases. This alone isn't a complete solution since x==0
1794              * may also not be what is wanted. The code that does the
1795              * unioning also needs to be aware to ignore empty glyphs.
1796              */
1797             if (!result.isEmpty()) {
1798                 result.setRect(result.getMinX() + x + dx,
1799                                result.getMinY() + y + dy,
1800                                result.getWidth(), result.getHeight());
1801             }
1802             return result;
1803         }
1804 
appendGlyphOutline(int glyphID, GeneralPath result, float x, float y)1805         void appendGlyphOutline(int glyphID, GeneralPath result, float x, float y) {
1806             // !!! fontStrike needs a method for this.  For that matter, GeneralPath does.
1807             GeneralPath gp = null;
1808             if (sgv.invdtx == null) {
1809                 gp = strike.getGlyphOutline(glyphID, x + dx, y + dy);
1810             } else {
1811                 gp = strike.getGlyphOutline(glyphID, 0, 0);
1812                 gp.transform(sgv.invdtx);
1813                 gp.transform(AffineTransform.getTranslateInstance(x + dx, y + dy));
1814             }
1815             PathIterator iterator = gp.getPathIterator(null);
1816             result.append(iterator, false);
1817         }
1818     }
1819 
toString()1820     public String toString() {
1821         return appendString(null).toString();
1822     }
1823 
appendString(StringBuffer buf)1824     StringBuffer appendString(StringBuffer buf) {
1825         if (buf == null) {
1826             buf = new StringBuffer();
1827         }
1828         try {
1829             buf.append("SGV{font: ");
1830             buf.append(font.toString());
1831             buf.append(", frc: ");
1832             buf.append(frc.toString());
1833             buf.append(", glyphs: (");
1834             buf.append(glyphs.length);
1835             buf.append(")[");
1836             for (int i = 0; i < glyphs.length; ++i) {
1837                 if (i > 0) {
1838                     buf.append(", ");
1839                 }
1840                 buf.append(Integer.toHexString(glyphs[i]));
1841             }
1842             buf.append("]");
1843             if (positions != null) {
1844                 buf.append(", positions: (");
1845                 buf.append(positions.length);
1846                 buf.append(")[");
1847                 for (int i = 0; i < positions.length; i += 2) {
1848                     if (i > 0) {
1849                         buf.append(", ");
1850                     }
1851                     buf.append(positions[i]);
1852                     buf.append("@");
1853                     buf.append(positions[i+1]);
1854                 }
1855                 buf.append("]");
1856             }
1857             if (charIndices != null) {
1858                 buf.append(", indices: (");
1859                 buf.append(charIndices.length);
1860                 buf.append(")[");
1861                 for (int i = 0; i < charIndices.length; ++i) {
1862                     if (i > 0) {
1863                         buf.append(", ");
1864                     }
1865                     buf.append(charIndices[i]);
1866                 }
1867                 buf.append("]");
1868             }
1869             buf.append(", flags:");
1870             if (getLayoutFlags() == 0) {
1871                 buf.append(" default");
1872             } else {
1873                 if ((flags & FLAG_HAS_TRANSFORMS) != 0) {
1874                     buf.append(" tx");
1875                 }
1876                 if ((flags & FLAG_HAS_POSITION_ADJUSTMENTS) != 0) {
1877                     buf.append(" pos");
1878                 }
1879                 if ((flags & FLAG_RUN_RTL) != 0) {
1880                     buf.append(" rtl");
1881                 }
1882                 if ((flags & FLAG_COMPLEX_GLYPHS) != 0) {
1883                     buf.append(" complex");
1884                 }
1885             }
1886         }
1887         catch(Exception e) {
1888             buf.append(' ').append(e.getMessage());
1889         }
1890         buf.append('}');
1891 
1892         return buf;
1893     }
1894 
1895     static class ADL {
1896         public float ascentX;
1897         public float ascentY;
1898         public float descentX;
1899         public float descentY;
1900         public float leadingX;
1901         public float leadingY;
1902 
toString()1903         public String toString() {
1904             return toStringBuffer(null).toString();
1905         }
1906 
toStringBuffer(StringBuffer result)1907         protected StringBuffer toStringBuffer(StringBuffer result) {
1908             if (result == null) {
1909                 result = new StringBuffer();
1910             }
1911             result.append("ax: ");
1912             result.append(ascentX);
1913             result.append(" ay: ");
1914             result.append(ascentY);
1915             result.append(" dx: ");
1916             result.append(descentX);
1917             result.append(" dy: ");
1918             result.append(descentY);
1919             result.append(" lx: ");
1920             result.append(leadingX);
1921             result.append(" ly: ");
1922             result.append(leadingY);
1923 
1924             return result;
1925         }
1926     }
1927 }
1928