History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/sun/font/StandardGlyphVector.java (1 of 18352 files). Show all

Unified · Split

java/pkg/sun/font/StandardGlyphVector.java+1935−0
@@ -0,0 +1,1935 @@
1+/*
2+ * Copyright (c) 1998, 2005, 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 fsref; // font strike reference for glyphs with no per-glyph transform
157+
158+ /////////////////////////////
159+ // Constructors and Factory methods
160+ /////////////////////////////
161+
162+ public StandardGlyphVector(Font font, String str, FontRenderContext frc) {
163+ init(font, str.toCharArray(), 0, str.length(), frc, UNINITIALIZED_FLAGS);
164+ }
165+
166+ public StandardGlyphVector(Font font, char[] text, FontRenderContext frc) {
167+ init(font, text, 0, text.length, frc, UNINITIALIZED_FLAGS);
168+ }
169+
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+
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
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+
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+
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+
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+ */
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+
290+ public Font getFont() {
291+ return this.font;
292+ }
293+
294+ public FontRenderContext getFontRenderContext() {
295+ return this.frc;
296+ }
297+
298+ public void performDefaultLayout() {
299+ positions = null;
300+ if (getTracking(font) == 0) {
301+ clearFlags(FLAG_HAS_POSITION_ADJUSTMENTS);
302+ }
303+ }
304+
305+ public int getNumGlyphs() {
306+ return glyphs.length;
307+ }
308+
309+ public int getGlyphCode(int glyphIndex) {
310+ return userGlyphs[glyphIndex];
311+ }
312+
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+
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+
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
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
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
418+ public Rectangle getPixelBounds(FontRenderContext renderFRC, float x, float y) {
419+ return getGlyphsPixelBounds(renderFRC, x, y, 0, glyphs.length);
420+ }
421+
422+ public Shape getOutline() {
423+ return getGlyphsOutline(0, glyphs.length, 0, 0);
424+ }
425+
426+ public Shape getOutline(float x, float y) {
427+ return getGlyphsOutline(0, glyphs.length, x, y);
428+ }
429+
430+ // relative to gv origin
431+ public Shape getGlyphOutline(int ix) {
432+ return getGlyphsOutline(ix, 1, 0, 0);
433+ }
434+
435+ // relative to gv origin offset by x, y
436+ public Shape getGlyphOutline(int ix, float x, float y) {
437+ return getGlyphsOutline(ix, 1, x, y);
438+ }
439+
440+ public Point2D getGlyphPosition(int ix) {
441+ initPositions();
442+
443+ ix *= 2;
444+ return new Point2D.Float(positions[ix], positions[ix + 1]);
445+ }
446+
447+ public void setGlyphPosition(int ix, Point2D pos) {
448+ initPositions();
449+
450+ int ix2 = ix << 1;
451+ positions[ix2] = (float)pos.getX();
452+ positions[ix2 + 1] = (float)pos.getY();
453+
454+ clearCaches(ix);
455+ addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);
456+ }
457+
458+ public AffineTransform getGlyphTransform(int ix) {
459+ if (ix < 0 || ix >= glyphs.length) {
460+ throw new IndexOutOfBoundsException("ix = " + ix);
461+ }
462+ if (gti != null) {
463+ return gti.getGlyphTransform(ix);
464+ }
465+ return null; // spec'd as returning null
466+ }
467+
468+ public void setGlyphTransform(int ix, AffineTransform newTX) {
469+ if (ix < 0 || ix >= glyphs.length) {
470+ throw new IndexOutOfBoundsException("ix = " + ix);
471+ }
472+
473+ if (gti == null) {
474+ if (newTX == null || newTX.isIdentity()) {
475+ return;
476+ }
477+ gti = new GlyphTransformInfo(this);
478+ }
479+ gti.setGlyphTransform(ix, newTX); // sets flags
480+ if (gti.transformCount() == 0) {
481+ gti = null;
482+ }
483+ }
484+
485+ public int getLayoutFlags() {
486+ if (flags == UNINITIALIZED_FLAGS) {
487+ flags = 0;
488+
489+ if (charIndices != null && glyphs.length > 1) {
490+ boolean ltr = true;
491+ boolean rtl = true;
492+
493+ int rtlix = charIndices.length; // rtl index
494+ for (int i = 0; i < charIndices.length && (ltr || rtl); ++i) {
495+ int cx = charIndices[i];
496+
497+ ltr = ltr && (cx == i);
498+ rtl = rtl && (cx == --rtlix);
499+ }
500+
501+ if (rtl) flags |= FLAG_RUN_RTL;
502+ if (!rtl && !ltr) flags |= FLAG_COMPLEX_GLYPHS;
503+ }
504+ }
505+
506+ return flags;
507+ }
508+
509+ public float[] getGlyphPositions(int start, int count, float[] result) {
510+ if (count < 0) {
511+ throw new IllegalArgumentException("count = " + count);
512+ }
513+ if (start < 0) {
514+ throw new IndexOutOfBoundsException("start = " + start);
515+ }
516+ if (start > glyphs.length + 1 - count) { // watch for overflow
517+ throw new IndexOutOfBoundsException("start + count = " + (start + count));
518+ }
519+
520+ return internalGetGlyphPositions(start, count, 0, result);
521+ }
522+
523+ public Shape getGlyphLogicalBounds(int ix) {
524+ if (ix < 0 || ix >= glyphs.length) {
525+ throw new IndexOutOfBoundsException("ix = " + ix);
526+ }
527+
528+ Shape[] lbcache;
529+ if (lbcacheRef == null || (lbcache = (Shape[])lbcacheRef.get()) == null) {
530+ lbcache = new Shape[glyphs.length];
531+ lbcacheRef = new SoftReference(lbcache);
532+ }
533+
534+ Shape result = lbcache[ix];
535+ if (result == null) {
536+ setFRCTX();
537+ initPositions();
538+
539+ // !!! ought to return a rectangle2d for simple cases, though the following works for all
540+
541+ // get the position, the tx offset, and the x,y advance and x,y adl. The
542+ // shape is the box formed by adv (width) and adl (height) offset by
543+ // the position plus the tx offset minus the ascent.
544+
545+ ADL adl = new ADL();
546+ GlyphStrike gs = getGlyphStrike(ix);
547+ gs.getADL(adl);
548+
549+ Point2D.Float adv = gs.strike.getGlyphMetrics(glyphs[ix]);
550+
551+ float wx = adv.x;
552+ float wy = adv.y;
553+ float hx = adl.descentX + adl.leadingX + adl.ascentX;
554+ float hy = adl.descentY + adl.leadingY + adl.ascentY;
555+ float x = positions[ix*2] + gs.dx - adl.ascentX;
556+ float y = positions[ix*2+1] + gs.dy - adl.ascentY;
557+
558+ GeneralPath gp = new GeneralPath();
559+ gp.moveTo(x, y);
560+ gp.lineTo(x + wx, y + wy);
561+ gp.lineTo(x + wx + hx, y + wy + hy);
562+ gp.lineTo(x + hx, y + hy);
563+ gp.closePath();
564+
565+ result = new DelegatingShape(gp);
566+ lbcache[ix] = result;
567+ }
568+
569+ return result;
570+ }
571+ private SoftReference lbcacheRef;
572+
573+ public Shape getGlyphVisualBounds(int ix) {
574+ if (ix < 0 || ix >= glyphs.length) {
575+ throw new IndexOutOfBoundsException("ix = " + ix);
576+ }
577+
578+ Shape[] vbcache;
579+ if (vbcacheRef == null || (vbcache = (Shape[])vbcacheRef.get()) == null) {
580+ vbcache = new Shape[glyphs.length];
581+ vbcacheRef = new SoftReference(vbcache);
582+ }
583+
584+ Shape result = vbcache[ix];
585+ if (result == null) {
586+ result = new DelegatingShape(getGlyphOutlineBounds(ix));
587+ vbcache[ix] = result;
588+ }
589+
590+ return result;
591+ }
592+ private SoftReference vbcacheRef;
593+
594+ public Rectangle getGlyphPixelBounds(int index, FontRenderContext renderFRC, float x, float y) {
595+ return getGlyphsPixelBounds(renderFRC, x, y, index, 1);
596+ }
597+
598+ public GlyphMetrics getGlyphMetrics(int ix) {
599+ if (ix < 0 || ix >= glyphs.length) {
600+ throw new IndexOutOfBoundsException("ix = " + ix);
601+ }
602+
603+ Rectangle2D vb = getGlyphVisualBounds(ix).getBounds2D();
604+ Point2D pt = getGlyphPosition(ix);
605+ vb.setRect(vb.getMinX() - pt.getX(),
606+ vb.getMinY() - pt.getY(),
607+ vb.getWidth(),
608+ vb.getHeight());
609+ Point2D.Float adv =
610+ getGlyphStrike(ix).strike.getGlyphMetrics(glyphs[ix]);
611+ GlyphMetrics gm = new GlyphMetrics(true, adv.x, adv.y,
612+ vb,
613+ GlyphMetrics.STANDARD);
614+ return gm;
615+ }
616+
617+ public GlyphJustificationInfo getGlyphJustificationInfo(int ix) {
618+ if (ix < 0 || ix >= glyphs.length) {
619+ throw new IndexOutOfBoundsException("ix = " + ix);
620+ }
621+
622+ // currently we don't have enough information to do this right. should
623+ // get info from the font and use real OT/GX justification. Right now
624+ // sun/font/ExtendedTextSourceLabel assigns one of three infos
625+ // based on whether the char is kanji, space, or other.
626+
627+ return null;
628+ }
629+
630+ public boolean equals(GlyphVector rhs) {
631+ if (this == rhs) {
632+ return true;
633+ }
634+ if (rhs == null) {
635+ return false;
636+ }
637+
638+ try {
639+ StandardGlyphVector other = (StandardGlyphVector)rhs;
640+
641+ if (glyphs.length != other.glyphs.length) {
642+ return false;
643+ }
644+
645+ for (int i = 0; i < glyphs.length; ++i) {
646+ if (glyphs[i] != other.glyphs[i]) {
647+ return false;
648+ }
649+ }
650+
651+ if (!font.equals(other.font)) {
652+ return false;
653+ }
654+
655+ if (!frc.equals(other.frc)) {
656+ return false;
657+ }
658+
659+ if ((other.positions == null) != (positions == null)) {
660+ if (positions == null) {
661+ initPositions();
662+ } else {
663+ other.initPositions();
664+ }
665+ }
666+
667+ if (positions != null) {
668+ for (int i = 0; i < positions.length; ++i) {
669+ if (positions[i] != other.positions[i]) {
670+ return false;
671+ }
672+ }
673+ }
674+
675+ if (gti == null) {
676+ return other.gti == null;
677+ } else {
678+ return gti.equals(other.gti);
679+ }
680+ }
681+ catch (ClassCastException e) {
682+ // assume they are different simply by virtue of the class difference
683+
684+ return false;
685+ }
686+ }
687+
688+ /**
689+ * As a concrete subclass of Object that implements equality, this must
690+ * implement hashCode.
691+ */
692+ public int hashCode() {
693+ return font.hashCode() ^ glyphs.length;
694+ }
695+
696+ /**
697+ * Since we implement equality comparisons for GlyphVector, we implement
698+ * the inherited Object.equals(Object) as well. GlyphVector should do
699+ * this, and define two glyphvectors as not equal if the classes differ.
700+ */
701+ public boolean equals(Object rhs) {
702+ try {
703+ return equals((GlyphVector)rhs);
704+ }
705+ catch (ClassCastException e) {
706+ return false;
707+ }
708+ }
709+
710+ /**
711+ * Sometimes I wish java had covariant return types...
712+ */
713+ public StandardGlyphVector copy() {
714+ return (StandardGlyphVector)clone();
715+ }
716+
717+ /**
718+ * As a concrete subclass of GlyphVector, this must implement clone.
719+ */
720+ public Object clone() {
721+ // positions, gti are mutable so we have to clone them
722+ // font2d can be shared
723+ // fsref is a cache and can be shared
724+ try {
725+ StandardGlyphVector result = (StandardGlyphVector)super.clone();
726+
727+ result.clearCaches();
728+
729+ if (positions != null) {
730+ result.positions = (float[])positions.clone();
731+ }
732+
733+ if (gti != null) {
734+ result.gti = new GlyphTransformInfo(result, gti);
735+ }
736+
737+ return result;
738+ }
739+ catch (CloneNotSupportedException e) {
740+ }
741+
742+ return this;
743+ }
744+
745+ //////////////////////
746+ // StandardGlyphVector new public methods
747+ /////////////////////
748+
749+ /*
750+ * Set a multiple glyph positions at one time. GlyphVector only
751+ * provides API to set a single glyph at a time.
752+ */
753+ public void setGlyphPositions(float[] srcPositions, int srcStart,
754+ int start, int count) {
755+ if (count < 0) {
756+ throw new IllegalArgumentException("count = " + count);
757+ }
758+
759+ initPositions();
760+ for (int i = start * 2, e = i + count * 2, p = srcStart; i < e; ++i, ++p) {
761+ positions[i] = srcPositions[p];
762+ }
763+
764+ clearCaches();
765+ addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);
766+ }
767+
768+ /**
769+ * Set all the glyph positions, including the 'after last glyph' position.
770+ * The srcPositions array must be of length (numGlyphs + 1) * 2.
771+ */
772+ public void setGlyphPositions(float[] srcPositions) {
773+ int requiredLength = glyphs.length * 2 + 2;
774+ if (srcPositions.length != requiredLength) {
775+ throw new IllegalArgumentException("srcPositions.length != " + requiredLength);
776+ }
777+
778+ positions = (float[])srcPositions.clone();
779+
780+ clearCaches();
781+ addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);
782+ }
783+
784+ /**
785+ * This is a convenience overload that gets all the glyph positions, which
786+ * is what you usually want to do if you're getting more than one.
787+ * !!! should I bother taking result parameter?
788+ */
789+ public float[] getGlyphPositions(float[] result) {
790+ return internalGetGlyphPositions(0, glyphs.length + 1, 0, result);
791+ }
792+
793+ /**
794+ * Get transform information for the requested range of glyphs.
795+ * If no glyphs have a transform, return null.
796+ * If a glyph has no transform (or is the identity transform) its entry in the result array will be null.
797+ * If the passed-in result is null an array will be allocated for the caller.
798+ * Each transform instance in the result array will unique, and independent of the GlyphVector's transform.
799+ */
800+ public AffineTransform[] getGlyphTransforms(int start, int count, AffineTransform[] result) {
801+ if (start < 0 || count < 0 || start + count > glyphs.length) {
802+ throw new IllegalArgumentException("start: " + start + " count: " + count);
803+ }
804+
805+ if (gti == null) {
806+ return null;
807+ }
808+
809+ if (result == null) {
810+ result = new AffineTransform[count];
811+ }
812+
813+ for (int i = 0; i < count; ++i, ++start) {
814+ result[i] = gti.getGlyphTransform(start);
815+ }
816+
817+ return result;
818+ }
819+
820+ /**
821+ * Convenience overload for getGlyphTransforms(int, int, AffineTransform[], int);
822+ */
823+ public AffineTransform[] getGlyphTransforms() {
824+ return getGlyphTransforms(0, glyphs.length, null);
825+ }
826+
827+ /**
828+ * Set a number of glyph transforms.
829+ * Original transforms are unchanged. The array may contain nulls, and also may
830+ * contain multiple references to the same transform instance.
831+ */
832+ public void setGlyphTransforms(AffineTransform[] srcTransforms, int srcStart, int start, int count) {
833+ for (int i = start, e = start + count; i < e; ++i) {
834+ setGlyphTransform(i, srcTransforms[srcStart + i]);
835+ }
836+ }
837+
838+ /**
839+ * Convenience overload of setGlyphTransforms(AffineTransform[], int, int, int).
840+ */
841+ public void setGlyphTransforms(AffineTransform[] srcTransforms) {
842+ setGlyphTransforms(srcTransforms, 0, 0, glyphs.length);
843+ }
844+
845+ /**
846+ * For each glyph return posx, posy, advx, advy, visx, visy, visw, vish.
847+ */
848+ public float[] getGlyphInfo() {
849+ setFRCTX();
850+ initPositions();
851+ float[] result = new float[glyphs.length * 8];
852+ for (int i = 0, n = 0; i < glyphs.length; ++i, n += 8) {
853+ float x = positions[i*2];
854+ float y = positions[i*2+1];
855+ result[n] = x;
856+ result[n+1] = y;
857+
858+ int glyphID = glyphs[i];
859+ GlyphStrike s = getGlyphStrike(i);
860+ Point2D.Float adv = s.strike.getGlyphMetrics(glyphID);
861+ result[n+2] = adv.x;
862+ result[n+3] = adv.y;
863+
864+ Rectangle2D vb = getGlyphVisualBounds(i).getBounds2D();
865+ result[n+4] = (float)(vb.getMinX());
866+ result[n+5] = (float)(vb.getMinY());
867+ result[n+6] = (float)(vb.getWidth());
868+ result[n+7] = (float)(vb.getHeight());
869+ }
870+ return result;
871+ }
872+
873+ /**
874+ * !!! not used currently, but might be by getPixelbounds?
875+ */
876+ public void pixellate(FontRenderContext renderFRC, Point2D loc, Point pxResult) {
877+ if (renderFRC == null) {
878+ renderFRC = frc;
879+ }
880+
881+ // it is a total pain that you have to copy the transform.
882+
883+ AffineTransform at = renderFRC.getTransform();
884+ at.transform(loc, loc);
885+ pxResult.x = (int)loc.getX(); // but must not behave oddly around zero
886+ pxResult.y = (int)loc.getY();
887+ loc.setLocation(pxResult.x, pxResult.y);
888+ try {
889+ at.inverseTransform(loc, loc);
890+ }
891+ catch (NoninvertibleTransformException e) {
892+ throw new IllegalArgumentException("must be able to invert frc transform");
893+ }
894+ }
895+
896+ //////////////////////
897+ // StandardGlyphVector package private methods
898+ /////////////////////
899+
900+ // used by glyphlist to determine if it needs to allocate/size positions array
901+ // gti always uses positions because the gtx might have translation. We also
902+ // need positions if the rendering dtx is different from the frctx.
903+
904+ boolean needsPositions(double[] devTX) {
905+ return gti != null ||
906+ (getLayoutFlags() & FLAG_HAS_POSITION_ADJUSTMENTS) != 0 ||
907+ !matchTX(devTX, frctx);
908+ }
909+
910+ // used by glyphList to get strong refs to font strikes for duration of rendering call
911+ // if devTX matches current devTX, we're ready to go
912+ // if we don't have multiple transforms, we're already ok
913+
914+ // !!! I'm not sure fontInfo works so well for glyphvector, since we have to be able to handle
915+ // the multiple-strikes case
916+
917+ /*
918+ * GlyphList calls this to set up its images data. First it calls needsPositions,
919+ * passing the devTX, to see if it should provide us a positions array to fill.
920+ * It only doesn't need them if we're a simple glyph vector whose frctx matches the
921+ * devtx.
922+ * Then it calls setupGlyphImages. If we need positions, we make sure we have our
923+ * default positions based on the frctx first. Then we set the devTX, and use
924+ * strikes based on it to generate the images. Finally, we fill in the positions
925+ * array.
926+ * If we have transforms, we delegate to gti. It depends on our having first
927+ * initialized the positions and devTX.
928+ */
929+ Object setupGlyphImages(long[] images, float[] positions, double[] devTX) {
930+ initPositions(); // FIRST ensure we have positions based on our frctx
931+ setRenderTransform(devTX); // THEN make sure we are using the desired devTX
932+
933+ if (gti != null) {
934+ return gti.setupGlyphImages(images, positions, dtx);
935+ }
936+
937+ GlyphStrike gs = getDefaultStrike();
938+ gs.strike.getGlyphImagePtrs(glyphs, images, glyphs.length);
939+
940+ if (positions != null) {
941+ if (dtx.isIdentity()) {
942+ System.arraycopy(this.positions, 0, positions, 0, glyphs.length * 2);
943+ } else {
944+ dtx.transform(this.positions, 0, positions, 0, glyphs.length);
945+ }
946+ }
947+
948+ return gs;
949+ }
950+
951+ //////////////////////
952+ // StandardGlyphVector private methods
953+ /////////////////////
954+
955+ // We keep translation in our frctx since getPixelBounds uses it. But
956+ // GlyphList pulls out the translation and applies it separately, so
957+ // we strip it out when we set the dtx. Basically nothing uses the
958+ // translation except getPixelBounds.
959+
960+ // called by needsPositions, setRenderTransform
961+ private static boolean matchTX(double[] lhs, AffineTransform rhs) {
962+ return
963+ lhs[0] == rhs.getScaleX() &&
964+ lhs[1] == rhs.getShearY() &&
965+ lhs[2] == rhs.getShearX() &&
966+ lhs[3] == rhs.getScaleY();
967+ }
968+
969+ // returns new tx if old one has translation, otherwise returns old one
970+ private static AffineTransform getNonTranslateTX(AffineTransform tx) {
971+ if (tx.getTranslateX() != 0 || tx.getTranslateY() != 0) {
972+ tx = new AffineTransform(tx.getScaleX(), tx.getShearY(),
973+ tx.getShearX(), tx.getScaleY(),
974+ 0, 0);
975+ }
976+ return tx;
977+ }
978+
979+ private static boolean equalNonTranslateTX(AffineTransform lhs, AffineTransform rhs) {
980+ return lhs.getScaleX() == rhs.getScaleX() &&
981+ lhs.getShearY() == rhs.getShearY() &&
982+ lhs.getShearX() == rhs.getShearX() &&
983+ lhs.getScaleY() == rhs.getScaleY();
984+ }
985+
986+ // called by setupGlyphImages (after needsPositions, so redundant match check?)
987+ private void setRenderTransform(double[] devTX) {
988+ assert(devTX.length == 4);
989+ if (!matchTX(devTX, dtx)) {
990+ resetDTX(new AffineTransform(devTX)); // no translation since devTX len == 4.
991+ }
992+ }
993+
994+ // called by getGlyphsPixelBounds
995+ private final void setDTX(AffineTransform tx) {
996+ if (!equalNonTranslateTX(dtx, tx)) {
997+ resetDTX(getNonTranslateTX(tx));
998+ }
999+ }
1000+
1001+ // called by most functions
1002+ private final void setFRCTX() {
1003+ if (!equalNonTranslateTX(frctx, dtx)) {
1004+ resetDTX(getNonTranslateTX(frctx));
1005+ }
1006+ }
1007+
1008+ /**
1009+ * Change the dtx for the strike refs we use. Keeps a reference to the at. At
1010+ * must not contain translation.
1011+ * Called by setRenderTransform, setDTX, initFontData.
1012+ */
1013+ private final void resetDTX(AffineTransform at) {
1014+ fsref = null;
1015+ dtx = at;
1016+ invdtx = null;
1017+ if (!dtx.isIdentity()) {
1018+ try {
1019+ invdtx = dtx.createInverse();
1020+ }
1021+ catch (NoninvertibleTransformException e) {
1022+ // we needn't care for rendering
1023+ }
1024+ }
1025+ if (gti != null) {
1026+ gti.strikesRef = null;
1027+ }
1028+ }
1029+
1030+ /**
1031+ * Utility used by getStandardGV.
1032+ * Constructs a StandardGlyphVector from a generic glyph vector.
1033+ * Do not call this from new contexts without considering the comment
1034+ * about "userGlyphs".
1035+ */
1036+ private StandardGlyphVector(GlyphVector gv, FontRenderContext frc) {
1037+ this.font = gv.getFont();
1038+ this.frc = frc;
1039+ initFontData();
1040+
1041+ int nGlyphs = gv.getNumGlyphs();
1042+ this.userGlyphs = gv.getGlyphCodes(0, nGlyphs, null);
1043+ if (gv instanceof StandardGlyphVector) {
1044+ /* userGlyphs will be OK because this is a private constructor
1045+ * and the returned instance is used only for rendering.
1046+ * It's not constructable by user code, nor returned to the
1047+ * application. So we know "userGlyphs" are valid as having
1048+ * been either already validated or are the result of layout.
1049+ */
1050+ this.glyphs = userGlyphs;
1051+ } else {
1052+ this.glyphs = getValidatedGlyphs(this.userGlyphs);
1053+ }
1054+ this.flags = gv.getLayoutFlags() & FLAG_MASK;
1055+
1056+ if ((flags & FLAG_HAS_POSITION_ADJUSTMENTS) != 0) {
1057+ this.positions = gv.getGlyphPositions(0, nGlyphs + 1, null);
1058+ }
1059+
1060+ if ((flags & FLAG_COMPLEX_GLYPHS) != 0) {
1061+ this.charIndices = gv.getGlyphCharIndices(0, nGlyphs, null);
1062+ }
1063+
1064+ if ((flags & FLAG_HAS_TRANSFORMS) != 0) {
1065+ AffineTransform[] txs = new AffineTransform[nGlyphs]; // worst case
1066+ for (int i = 0; i < nGlyphs; ++i) {
1067+ txs[i] = gv.getGlyphTransform(i); // gv doesn't have getGlyphsTransforms
1068+ }
1069+
1070+ setGlyphTransforms(txs);
1071+ }
1072+ }
1073+
1074+ /* Before asking the Font we see if the glyph code is
1075+ * FFFE or FFFF which are special values that we should be internally
1076+ * ready to handle as meaning invisible glyphs. The Font would report
1077+ * those as the missing glyph.
1078+ */
1079+ int[] getValidatedGlyphs(int[] oglyphs) {
1080+ int len = oglyphs.length;
1081+ int[] vglyphs = new int[len];
1082+ for (int i=0; i<len; i++) {
1083+ if (oglyphs[i] == 0xFFFE || oglyphs[i] == 0xFFFF) {
1084+ vglyphs[i] = oglyphs[i];
1085+ } else {
1086+ vglyphs[i] = font2D.getValidatedGlyphCode(oglyphs[i]);
1087+ }
1088+ }
1089+ return vglyphs;
1090+ }
1091+
1092+ // utility used by constructors
1093+ private void init(Font font, char[] text, int start, int count,
1094+ FontRenderContext frc, int flags) {
1095+
1096+ if (start < 0 || count < 0 || start + count > text.length) {
1097+ throw new ArrayIndexOutOfBoundsException("start or count out of bounds");
1098+ }
1099+
1100+ this.font = font;
1101+ this.frc = frc;
1102+ this.flags = flags;
1103+
1104+ if (getTracking(font) != 0) {
1105+ addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);
1106+ }
1107+
1108+ // !!! change mapper interface?
1109+ if (start != 0) {
1110+ char[] temp = new char[count];
1111+ System.arraycopy(text, start, temp, 0, count);
1112+ text = temp;
1113+ }
1114+
1115+ initFontData(); // sets up font2D
1116+
1117+ // !!! no layout for now, should add checks
1118+ // !!! need to support creating a StandardGlyphVector from a TextMeasurer's info...
1119+ glyphs = new int[count]; // hmmm
1120+ /* Glyphs obtained here are already validated by the font */
1121+ userGlyphs = glyphs;
1122+ font2D.getMapper().charsToGlyphs(count, text, glyphs);
1123+ }
1124+
1125+ private void initFontData() {
1126+ font2D = FontUtilities.getFont2D(font);
1127+ float s = font.getSize2D();
1128+ if (font.isTransformed()) {
1129+ ftx = font.getTransform();
1130+ if (ftx.getTranslateX() != 0 || ftx.getTranslateY() != 0) {
1131+ addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);
1132+ }
1133+ ftx.setTransform(ftx.getScaleX(), ftx.getShearY(), ftx.getShearX(), ftx.getScaleY(), 0, 0);
1134+ ftx.scale(s, s);
1135+ } else {
1136+ ftx = AffineTransform.getScaleInstance(s, s);
1137+ }
1138+
1139+ frctx = frc.getTransform();
1140+ resetDTX(getNonTranslateTX(frctx));
1141+ }
1142+
1143+ /**
1144+ * Copy glyph position data into a result array starting at the indicated
1145+ * offset in the array. If the passed-in result array is null, a new
1146+ * array will be allocated and returned.
1147+ *
1148+ * This is an internal method and does no extra argument checking.
1149+ *
1150+ * @param start the index of the first glyph to get
1151+ * @param count the number of glyphs to get
1152+ * @param offset the offset into result at which to put the data
1153+ * @param result an array to hold the x,y positions
1154+ * @return the modified position array
1155+ */
1156+ private float[] internalGetGlyphPositions(int start, int count, int offset, float[] result) {
1157+ if (result == null) {
1158+ result = new float[offset + count * 2];
1159+ }
1160+
1161+ initPositions();
1162+
1163+ // System.arraycopy is slow for stuff like this
1164+ for (int i = offset, e = offset + count * 2, p = start * 2; i < e; ++i, ++p) {
1165+ result[i] = positions[p];
1166+ }
1167+
1168+ return result;
1169+ }
1170+
1171+ private Rectangle2D getGlyphOutlineBounds(int ix) {
1172+ setFRCTX();
1173+ initPositions();
1174+ return getGlyphStrike(ix).getGlyphOutlineBounds(glyphs[ix], positions[ix*2], positions[ix*2+1]);
1175+ }
1176+
1177+ /**
1178+ * Used by getOutline, getGlyphsOutline
1179+ */
1180+ private Shape getGlyphsOutline(int start, int count, float x, float y) {
1181+ setFRCTX();
1182+ initPositions();
1183+
1184+ GeneralPath result = new GeneralPath(GeneralPath.WIND_NON_ZERO);
1185+ for (int i = start, e = start + count, n = start * 2; i < e; ++i, n += 2) {
1186+ float px = x + positions[n];
1187+ float py = y + positions[n+1];
1188+
1189+ getGlyphStrike(i).appendGlyphOutline(glyphs[i], result, px, py);
1190+ }
1191+
1192+ return result;
1193+ }
1194+
1195+ private Rectangle getGlyphsPixelBounds(FontRenderContext frc, float x, float y, int start, int count) {
1196+ initPositions(); // FIRST ensure we have positions based on our frctx
1197+
1198+ AffineTransform tx = null;
1199+ if (frc == null || frc.equals(this.frc)) {
1200+ tx = frctx;
1201+ } else {
1202+ tx = frc.getTransform();
1203+ }
1204+ setDTX(tx); // need to get the right strikes, but we use tx itself to translate the points
1205+
1206+ if (gti != null) {
1207+ return gti.getGlyphsPixelBounds(tx, x, y, start, count);
1208+ }
1209+
1210+ FontStrike fs = getDefaultStrike().strike;
1211+ Rectangle result = null;
1212+ Rectangle r = new Rectangle();
1213+ Point2D.Float pt = new Point.Float();
1214+ int n = start * 2;
1215+ while (--count >= 0) {
1216+ pt.x = x + positions[n++];
1217+ pt.y = y + positions[n++];
1218+ tx.transform(pt, pt);
1219+ fs.getGlyphImageBounds(glyphs[start++], pt, r);
1220+ if (!r.isEmpty()) {
1221+ if (result == null) {
1222+ result = new Rectangle(r);
1223+ } else {
1224+ result.add(r);
1225+ }
1226+ }
1227+ }
1228+ return result != null ? result : r;
1229+ }
1230+
1231+ private void clearCaches(int ix) {
1232+ if (lbcacheRef != null) {
1233+ Shape[] lbcache = (Shape[])lbcacheRef.get();
1234+ if (lbcache != null) {
1235+ lbcache[ix] = null;
1236+ }
1237+ }
1238+
1239+ if (vbcacheRef != null) {
1240+ Shape[] vbcache = (Shape[])vbcacheRef.get();
1241+ if (vbcache != null) {
1242+ vbcache[ix] = null;
1243+ }
1244+ }
1245+ }
1246+
1247+ private void clearCaches() {
1248+ lbcacheRef = null;
1249+ vbcacheRef = null;
1250+ }
1251+
1252+ // internal use only for possible future extension
1253+
1254+ /**
1255+ * A flag used with getLayoutFlags that indicates whether this <code>GlyphVector</code> uses
1256+ * a vertical baseline.
1257+ */
1258+ public static final int FLAG_USES_VERTICAL_BASELINE = 128;
1259+
1260+ /**
1261+ * A flag used with getLayoutFlags that indicates whether this <code>GlyphVector</code> uses
1262+ * vertical glyph metrics. A <code>GlyphVector</code> can use vertical metrics on a
1263+ * horizontal line, or vice versa.
1264+ */
1265+ public static final int FLAG_USES_VERTICAL_METRICS = 256;
1266+
1267+ /**
1268+ * A flag used with getLayoutFlags that indicates whether this <code>GlyphVector</code> uses
1269+ * the 'alternate orientation.' Glyphs have a default orientation given a
1270+ * particular baseline and metrics orientation, this is the orientation appropriate
1271+ * for left-to-right text. For example, the letter 'A' can have four orientations,
1272+ * with the point at 12, 3, 6, or 9 'o clock. The following table shows where the
1273+ * point displays for different values of vertical baseline (vb), vertical
1274+ * metrics (vm) and alternate orientation (fo):<br>
1275+ * <blockquote>
1276+ * vb vm ao
1277+ * -- -- -- --
1278+ * f f f 12 ^ horizontal metrics on horizontal lines
1279+ * f f t 6 v
1280+ * f t f 9 < vertical metrics on horizontal lines
1281+ * f t t 3 >
1282+ * t f f 3 > horizontal metrics on vertical lines
1283+ * t f t 9 <
1284+ * t t f 12 ^ vertical metrics on vertical lines
1285+ * t t t 6 v
1286+ * </blockquote>
1287+ */
1288+ public static final int FLAG_USES_ALTERNATE_ORIENTATION = 512;
1289+
1290+
1291+ /**
1292+ * Ensure that the positions array exists and holds position data.
1293+ * If the array is null, this allocates it and sets default positions.
1294+ */
1295+ private void initPositions() {
1296+ if (positions == null) {
1297+ setFRCTX();
1298+
1299+ positions = new float[glyphs.length * 2 + 2];
1300+
1301+ Point2D.Float trackPt = null;
1302+ float track = getTracking(font);
1303+ if (track != 0) {
1304+ track *= font.getSize2D();
1305+ trackPt = new Point2D.Float(track, 0); // advance delta
1306+ }
1307+
1308+ Point2D.Float pt = new Point2D.Float(0, 0);
1309+ if (font.isTransformed()) {
1310+ AffineTransform at = font.getTransform();
1311+ at.transform(pt, pt);
1312+ positions[0] = pt.x;
1313+ positions[1] = pt.y;
1314+
1315+ if (trackPt != null) {
1316+ at.deltaTransform(trackPt, trackPt);
1317+ }
1318+ }
1319+ for (int i = 0, n = 2; i < glyphs.length; ++i, n += 2) {
1320+ getGlyphStrike(i).addDefaultGlyphAdvance(glyphs[i], pt);
1321+ if (trackPt != null) {
1322+ pt.x += trackPt.x;
1323+ pt.y += trackPt.y;
1324+ }
1325+ positions[n] = pt.x;
1326+ positions[n+1] = pt.y;
1327+ }
1328+ }
1329+ }
1330+
1331+ /**
1332+ * OR newFlags with existing flags. First computes existing flags if needed.
1333+ */
1334+ private void addFlags(int newflags) {
1335+ flags = getLayoutFlags() | newflags;
1336+ }
1337+
1338+ /**
1339+ * AND the complement of clearedFlags with existing flags. First computes existing flags if needed.
1340+ */
1341+ private void clearFlags(int clearedFlags) {
1342+ flags = getLayoutFlags() & ~clearedFlags;
1343+ }
1344+
1345+ // general utility methods
1346+
1347+ // encapsulate the test to check whether we have per-glyph transforms
1348+ private GlyphStrike getGlyphStrike(int ix) {
1349+ if (gti == null) {
1350+ return getDefaultStrike();
1351+ } else {
1352+ return gti.getStrike(ix);
1353+ }
1354+ }
1355+
1356+ // encapsulate access to cached default glyph strike
1357+ private GlyphStrike getDefaultStrike() {
1358+ GlyphStrike gs = null;
1359+ if (fsref != null) {
1360+ gs = (GlyphStrike)fsref.get();
1361+ }
1362+ if (gs == null) {
1363+ gs = GlyphStrike.create(this, dtx, null);
1364+ fsref = new SoftReference(gs);
1365+ }
1366+ return gs;
1367+ }
1368+
1369+
1370+ /////////////////////
1371+ // Internal utility classes
1372+ /////////////////////
1373+
1374+ // !!! I have this as a separate class instead of just inside SGV,
1375+ // but I previously didn't bother. Now I'm trying this again.
1376+ // Probably still not worth it, but I'd like to keep sgv's small in the common case.
1377+
1378+ static final class GlyphTransformInfo {
1379+ StandardGlyphVector sgv; // reference back to glyph vector - yuck
1380+ int[] indices; // index into unique strikes
1381+ double[] transforms; // six doubles per unique transform, because AT is a pain to manipulate
1382+ SoftReference strikesRef; // ref to unique strikes, one per transform
1383+ boolean haveAllStrikes; // true if the strike array has been filled by getStrikes().
1384+
1385+ // used when first setting a transform
1386+ GlyphTransformInfo(StandardGlyphVector sgv) {
1387+ this.sgv = sgv;
1388+ }
1389+
1390+ // used when cloning a glyph vector, need to set back link
1391+ GlyphTransformInfo(StandardGlyphVector sgv, GlyphTransformInfo rhs) {
1392+ this.sgv = sgv;
1393+
1394+ this.indices = rhs.indices == null ? null : (int[])rhs.indices.clone();
1395+ this.transforms = rhs.transforms == null ? null : (double[])rhs.transforms.clone();
1396+ this.strikesRef = null; // can't share cache, so rather than clone, we just null out
1397+ }
1398+
1399+ // used in sgv equality
1400+ public boolean equals(GlyphTransformInfo rhs) {
1401+ if (rhs == null) {
1402+ return false;
1403+ }
1404+ if (rhs == this) {
1405+ return true;
1406+ }
1407+ if (this.indices.length != rhs.indices.length) {
1408+ return false;
1409+ }
1410+ if (this.transforms.length != rhs.transforms.length) {
1411+ return false;
1412+ }
1413+
1414+ // slow since we end up processing the same transforms multiple
1415+ // times, but since transforms can be in any order, we either do
1416+ // this or create a mapping. Equality tests aren't common so
1417+ // leave it like this.
1418+ for (int i = 0; i < this.indices.length; ++i) {
1419+ int tix = this.indices[i];
1420+ int rix = rhs.indices[i];
1421+ if ((tix == 0) != (rix == 0)) {
1422+ return false;
1423+ }
1424+ if (tix != 0) {
1425+ tix *= 6;
1426+ rix *= 6;
1427+ for (int j = 6; j > 0; --j) {
1428+ if (this.indices[--tix] != rhs.indices[--rix]) {
1429+ return false;
1430+ }
1431+ }
1432+ }
1433+ }
1434+ return true;
1435+ }
1436+
1437+ // implements sgv.setGlyphTransform
1438+ void setGlyphTransform(int glyphIndex, AffineTransform newTX) {
1439+
1440+ // we store all the glyph transforms as a double array, and for each glyph there
1441+ // is an entry in the txIndices array indicating which transform to use. 0 means
1442+ // there's no transform, 1 means use the first transform (the 6 doubles at offset
1443+ // 0), 2 means use the second transform (the 6 doubles at offset 6), etc.
1444+ //
1445+ // Since this can be called multiple times, and since the number of transforms
1446+ // affects the time it takes to construct the glyphs, we try to keep the arrays as
1447+ // compact as possible, by removing transforms that are no longer used, and reusing
1448+ // transforms where we already have them.
1449+
1450+ double[] temp = new double[6];
1451+ boolean isIdentity = true;
1452+ if (newTX == null || newTX.isIdentity()) {
1453+ // Fill in temp
1454+ temp[0] = temp[3] = 1.0;
1455+ }
1456+ else {
1457+ isIdentity = false;
1458+ newTX.getMatrix(temp);
1459+ }
1460+
1461+ if (indices == null) {
1462+ if (isIdentity) { // no change
1463+ return;
1464+ }
1465+
1466+ indices = new int[sgv.glyphs.length];
1467+ indices[glyphIndex] = 1;
1468+ transforms = temp;
1469+ } else {
1470+ boolean addSlot = false; // assume we're not growing
1471+ int newIndex = -1;
1472+ if (isIdentity) {
1473+ newIndex = 0; // might shrink
1474+ } else {
1475+ addSlot = true; // assume no match
1476+ int i;
1477+ loop:
1478+ for (i = 0; i < transforms.length; i += 6) {
1479+ for (int j = 0; j < 6; ++j) {
1480+ if (transforms[i + j] != temp[j]) {
1481+ continue loop;
1482+ }
1483+ }
1484+ addSlot = false;
1485+ break;
1486+ }
1487+ newIndex = i / 6 + 1; // if no match, end of list
1488+ }
1489+
1490+ // if we're using the same transform, nothing to do
1491+ int oldIndex = indices[glyphIndex];
1492+ if (newIndex != oldIndex) {
1493+ // see if we are removing last use of the old slot
1494+ boolean removeSlot = false;
1495+ if (oldIndex != 0) {
1496+ removeSlot = true;
1497+ for (int i = 0; i < indices.length; ++i) {
1498+ if (indices[i] == oldIndex && i != glyphIndex) {
1499+ removeSlot = false;
1500+ break;
1501+ }
1502+ }
1503+ }
1504+
1505+ if (removeSlot && addSlot) { // reuse old slot with new transform
1506+ newIndex = oldIndex;
1507+ System.arraycopy(temp, 0, transforms, (newIndex - 1) * 6, 6);
1508+ } else if (removeSlot) {
1509+ if (transforms.length == 6) { // removing last one, so clear arrays
1510+ indices = null;
1511+ transforms = null;
1512+
1513+ sgv.clearCaches(glyphIndex);
1514+ sgv.clearFlags(FLAG_HAS_TRANSFORMS);
1515+ strikesRef = null;
1516+
1517+ return;
1518+ }
1519+
1520+ double[] ttemp = new double[transforms.length - 6];
1521+ System.arraycopy(transforms, 0, ttemp, 0, (oldIndex - 1) * 6);
1522+ System.arraycopy(transforms, oldIndex * 6, ttemp, (oldIndex - 1) * 6,
1523+ transforms.length - oldIndex * 6);
1524+ transforms = ttemp;
1525+
1526+ // clean up indices
1527+ for (int i = 0; i < indices.length; ++i) {
1528+ if (indices[i] > oldIndex) { // ignore == oldIndex, it's going away
1529+ indices[i] -= 1;
1530+ }
1531+ }
1532+ if (newIndex > oldIndex) { // don't forget to decrement this too if we need to
1533+ --newIndex;
1534+ }
1535+ } else if (addSlot) {
1536+ double[] ttemp = new double[transforms.length + 6];
1537+ System.arraycopy(transforms, 0, ttemp, 0, transforms.length);
1538+ System.arraycopy(temp, 0, ttemp, transforms.length, 6);
1539+ transforms = ttemp;
1540+ }
1541+
1542+ indices[glyphIndex] = newIndex;
1543+ }
1544+ }
1545+
1546+ sgv.clearCaches(glyphIndex);
1547+ sgv.addFlags(FLAG_HAS_TRANSFORMS);
1548+ strikesRef = null;
1549+ }
1550+
1551+ // implements sgv.getGlyphTransform
1552+ AffineTransform getGlyphTransform(int ix) {
1553+ int index = indices[ix];
1554+ if (index == 0) {
1555+ return null;
1556+ }
1557+
1558+ int x = (index - 1) * 6;
1559+ return new AffineTransform(transforms[x + 0],
1560+ transforms[x + 1],
1561+ transforms[x + 2],
1562+ transforms[x + 3],
1563+ transforms[x + 4],
1564+ transforms[x + 5]);
1565+ }
1566+
1567+ int transformCount() {
1568+ if (transforms == null) {
1569+ return 0;
1570+ }
1571+ return transforms.length / 6;
1572+ }
1573+
1574+ /**
1575+ * The strike cache works like this.
1576+ *
1577+ * -Each glyph is thought of as having a transform, usually identity.
1578+ * -Each request for a strike is based on a device transform, either the
1579+ * one in the frc or the rendering transform.
1580+ * -For general info, strikes are held with soft references.
1581+ * -When rendering, strikes must be held with hard references for the
1582+ * duration of the rendering call. GlyphList will have to hold this
1583+ * info along with the image and position info, but toss the strike info
1584+ * when done.
1585+ * -Build the strike cache as needed. If the dev transform we want to use
1586+ * has changed from the last time it is built, the cache is flushed by
1587+ * the caller before these methods are called.
1588+ *
1589+ * Use a tx that doesn't include translation components of dst tx.
1590+ */
1591+ Object setupGlyphImages(long[] images, float[] positions, AffineTransform tx) {
1592+ int len = sgv.glyphs.length;
1593+
1594+ GlyphStrike[] sl = getAllStrikes();
1595+ for (int i = 0; i < len; ++i) {
1596+ GlyphStrike gs = sl[indices[i]];
1597+ int glyphID = sgv.glyphs[i];
1598+ images[i] = gs.strike.getGlyphImagePtr(glyphID);
1599+
1600+ gs.getGlyphPosition(glyphID, i*2, sgv.positions, positions);
1601+ }
1602+ tx.transform(positions, 0, positions, 0, len);
1603+
1604+ return sl;
1605+ }
1606+
1607+ Rectangle getGlyphsPixelBounds(AffineTransform tx, float x, float y, int start, int count) {
1608+ Rectangle result = null;
1609+ Rectangle r = new Rectangle();
1610+ Point2D.Float pt = new Point.Float();
1611+ int n = start * 2;
1612+ while (--count >= 0) {
1613+ GlyphStrike gs = getStrike(start);
1614+ pt.x = x + sgv.positions[n++] + gs.dx;
1615+ pt.y = y + sgv.positions[n++] + gs.dy;
1616+ tx.transform(pt, pt);
1617+ gs.strike.getGlyphImageBounds(sgv.glyphs[start++], pt, r);
1618+ if (!r.isEmpty()) {
1619+ if (result == null) {
1620+ result = new Rectangle(r);
1621+ } else {
1622+ result.add(r);
1623+ }
1624+ }
1625+ }
1626+ return result != null ? result : r;
1627+ }
1628+
1629+ GlyphStrike getStrike(int glyphIndex) {
1630+ if (indices != null) {
1631+ GlyphStrike[] strikes = getStrikeArray();
1632+ return getStrikeAtIndex(strikes, indices[glyphIndex]);
1633+ }
1634+ return sgv.getDefaultStrike();
1635+ }
1636+
1637+ private GlyphStrike[] getAllStrikes() {
1638+ if (indices == null) {
1639+ return null;
1640+ }
1641+
1642+ GlyphStrike[] strikes = getStrikeArray();
1643+ if (!haveAllStrikes) {
1644+ for (int i = 0; i < strikes.length; ++i) {
1645+ getStrikeAtIndex(strikes, i);
1646+ }
1647+ haveAllStrikes = true;
1648+ }
1649+
1650+ return strikes;
1651+ }
1652+
1653+ private GlyphStrike[] getStrikeArray() {
1654+ GlyphStrike[] strikes = null;
1655+ if (strikesRef != null) {
1656+ strikes = (GlyphStrike[])strikesRef.get();
1657+ }
1658+ if (strikes == null) {
1659+ haveAllStrikes = false;
1660+ strikes = new GlyphStrike[transformCount() + 1];
1661+ strikesRef = new SoftReference(strikes);
1662+ }
1663+
1664+ return strikes;
1665+ }
1666+
1667+ private GlyphStrike getStrikeAtIndex(GlyphStrike[] strikes, int strikeIndex) {
1668+ GlyphStrike strike = strikes[strikeIndex];
1669+ if (strike == null) {
1670+ if (strikeIndex == 0) {
1671+ strike = sgv.getDefaultStrike();
1672+ } else {
1673+ int ix = (strikeIndex - 1) * 6;
1674+ AffineTransform gtx = new AffineTransform(transforms[ix],
1675+ transforms[ix+1],
1676+ transforms[ix+2],
1677+ transforms[ix+3],
1678+ transforms[ix+4],
1679+ transforms[ix+5]);
1680+
1681+ strike = GlyphStrike.create(sgv, sgv.dtx, gtx);
1682+ }
1683+ strikes[strikeIndex] = strike;
1684+ }
1685+ return strike;
1686+ }
1687+ }
1688+
1689+ // This adjusts the metrics by the translation components of the glyph
1690+ // transform. It is done here since the translation is not known by the
1691+ // strike.
1692+ // It adjusts the position of the image and the advance.
1693+
1694+ public static final class GlyphStrike {
1695+ StandardGlyphVector sgv;
1696+ FontStrike strike; // hard reference
1697+ float dx;
1698+ float dy;
1699+
1700+ static GlyphStrike create(StandardGlyphVector sgv, AffineTransform dtx, AffineTransform gtx) {
1701+ float dx = 0;
1702+ float dy = 0;
1703+
1704+ AffineTransform tx = sgv.ftx;
1705+ if (!dtx.isIdentity() || gtx != null) {
1706+ tx = new AffineTransform(sgv.ftx);
1707+ if (gtx != null) {
1708+ tx.preConcatenate(gtx);
1709+ dx = (float)tx.getTranslateX(); // uses ftx then gtx to get translation
1710+ dy = (float)tx.getTranslateY();
1711+ }
1712+ if (!dtx.isIdentity()) {
1713+ tx.preConcatenate(dtx);
1714+ }
1715+ }
1716+
1717+ int ptSize = 1; // only matters for 'gasp' case.
1718+ Object aaHint = sgv.frc.getAntiAliasingHint();
1719+ if (aaHint == VALUE_TEXT_ANTIALIAS_GASP) {
1720+ /* Must pass in the calculated point size for rendering.
1721+ * If the glyph tx is anything other than identity or a
1722+ * simple translate, calculate the transformed point size.
1723+ */
1724+ if (!tx.isIdentity() &&
1725+ (tx.getType() & ~AffineTransform.TYPE_TRANSLATION) != 0) {
1726+ double shearx = tx.getShearX();
1727+ if (shearx != 0) {
1728+ double scaley = tx.getScaleY();
1729+ ptSize =
1730+ (int)Math.sqrt(shearx * shearx + scaley * scaley);
1731+ } else {
1732+ ptSize = (int)(Math.abs(tx.getScaleY()));
1733+ }
1734+ }
1735+ }
1736+ int aa = FontStrikeDesc.getAAHintIntVal(aaHint,sgv.font2D, ptSize);
1737+ int fm = FontStrikeDesc.getFMHintIntVal
1738+ (sgv.frc.getFractionalMetricsHint());
1739+ FontStrikeDesc desc = new FontStrikeDesc(dtx,
1740+ tx,
1741+ sgv.font.getStyle(),
1742+ aa, fm);
1743+
1744+ FontStrike strike = sgv.font2D.getStrike(desc); // !!! getStrike(desc, false)
1745+
1746+ return new GlyphStrike(sgv, strike, dx, dy);
1747+ }
1748+
1749+ private GlyphStrike(StandardGlyphVector sgv, FontStrike strike, float dx, float dy) {
1750+ this.sgv = sgv;
1751+ this.strike = strike;
1752+ this.dx = dx;
1753+ this.dy = dy;
1754+ }
1755+
1756+ void getADL(ADL result) {
1757+ StrikeMetrics sm = strike.getFontMetrics();
1758+ Point2D.Float delta = null;
1759+ if (sgv.font.isTransformed()) {
1760+ delta = new Point2D.Float();
1761+ delta.x = (float)sgv.font.getTransform().getTranslateX();
1762+ delta.y = (float)sgv.font.getTransform().getTranslateY();
1763+ }
1764+
1765+ result.ascentX = -sm.ascentX;
1766+ result.ascentY = -sm.ascentY;
1767+ result.descentX = sm.descentX;
1768+ result.descentY = sm.descentY;
1769+ result.leadingX = sm.leadingX;
1770+ result.leadingY = sm.leadingY;
1771+ }
1772+
1773+ void getGlyphPosition(int glyphID, int ix, float[] positions, float[] result) {
1774+ result[ix] = positions[ix] + dx;
1775+ ++ix;
1776+ result[ix] = positions[ix] + dy;
1777+ }
1778+
1779+ void addDefaultGlyphAdvance(int glyphID, Point2D.Float result) {
1780+ // !!! change this API? Creates unnecessary garbage. Also the name doesn't quite fit.
1781+ // strike.addGlyphAdvance(Point2D.Float adv); // hey, whaddya know, matches my api :-)
1782+ Point2D.Float adv = strike.getGlyphMetrics(glyphID);
1783+ result.x += adv.x + dx;
1784+ result.y += adv.y + dy;
1785+ }
1786+
1787+ Rectangle2D getGlyphOutlineBounds(int glyphID, float x, float y) {
1788+ Rectangle2D result = null;
1789+ if (sgv.invdtx == null) {
1790+ result = new Rectangle2D.Float();
1791+ result.setRect(strike.getGlyphOutlineBounds(glyphID)); // don't mutate cached rect
1792+ } else {
1793+ GeneralPath gp = strike.getGlyphOutline(glyphID, 0, 0);
1794+ gp.transform(sgv.invdtx);
1795+ result = gp.getBounds2D();
1796+ }
1797+ /* Since x is the logical advance of the glyph to this point.
1798+ * Because of the way that Rectangle.union is specified, this
1799+ * means that subsequent unioning of a rect including that
1800+ * will be affected, even if the glyph is empty. So skip such
1801+ * cases. This alone isn't a complete solution since x==0
1802+ * may also not be what is wanted. The code that does the
1803+ * unioning also needs to be aware to ignore empty glyphs.
1804+ */
1805+ if (!result.isEmpty()) {
1806+ result.setRect(result.getMinX() + x + dx,
1807+ result.getMinY() + y + dy,
1808+ result.getWidth(), result.getHeight());
1809+ }
1810+ return result;
1811+ }
1812+
1813+ void appendGlyphOutline(int glyphID, GeneralPath result, float x, float y) {
1814+ // !!! fontStrike needs a method for this. For that matter, GeneralPath does.
1815+ GeneralPath gp = null;
1816+ if (sgv.invdtx == null) {
1817+ gp = strike.getGlyphOutline(glyphID, x + dx, y + dy);
1818+ } else {
1819+ gp = strike.getGlyphOutline(glyphID, 0, 0);
1820+ gp.transform(sgv.invdtx);
1821+ gp.transform(AffineTransform.getTranslateInstance(x + dx, y + dy));
1822+ }
1823+ PathIterator iterator = gp.getPathIterator(null);
1824+ result.append(iterator, false);
1825+ }
1826+ }
1827+
1828+ public String toString() {
1829+ return appendString(null).toString();
1830+ }
1831+
1832+ StringBuffer appendString(StringBuffer buf) {
1833+ if (buf == null) {
1834+ buf = new StringBuffer();
1835+ }
1836+ try {
1837+ buf.append("SGV{font: ");
1838+ buf.append(font.toString());
1839+ buf.append(", frc: ");
1840+ buf.append(frc.toString());
1841+ buf.append(", glyphs: (");
1842+ buf.append(glyphs.length);
1843+ buf.append(")[");
1844+ for (int i = 0; i < glyphs.length; ++i) {
1845+ if (i > 0) {
1846+ buf.append(", ");
1847+ }
1848+ buf.append(Integer.toHexString(glyphs[i]));
1849+ }
1850+ buf.append("]");
1851+ if (positions != null) {
1852+ buf.append(", positions: (");
1853+ buf.append(positions.length);
1854+ buf.append(")[");
1855+ for (int i = 0; i < positions.length; i += 2) {
1856+ if (i > 0) {
1857+ buf.append(", ");
1858+ }
1859+ buf.append(positions[i]);
1860+ buf.append("@");
1861+ buf.append(positions[i+1]);
1862+ }
1863+ buf.append("]");
1864+ }
1865+ if (charIndices != null) {
1866+ buf.append(", indices: (");
1867+ buf.append(charIndices.length);
1868+ buf.append(")[");
1869+ for (int i = 0; i < charIndices.length; ++i) {
1870+ if (i > 0) {
1871+ buf.append(", ");
1872+ }
1873+ buf.append(charIndices[i]);
1874+ }
1875+ buf.append("]");
1876+ }
1877+ buf.append(", flags:");
1878+ if (getLayoutFlags() == 0) {
1879+ buf.append(" default");
1880+ } else {
1881+ if ((flags & FLAG_HAS_TRANSFORMS) != 0) {
1882+ buf.append(" tx");
1883+ }
1884+ if ((flags & FLAG_HAS_POSITION_ADJUSTMENTS) != 0) {
1885+ buf.append(" pos");
1886+ }
1887+ if ((flags & FLAG_RUN_RTL) != 0) {
1888+ buf.append(" rtl");
1889+ }
1890+ if ((flags & FLAG_COMPLEX_GLYPHS) != 0) {
1891+ buf.append(" complex");
1892+ }
1893+ }
1894+ }
1895+ catch(Exception e) {
1896+ buf.append(" " + e.getMessage());
1897+ }
1898+ buf.append("}");
1899+
1900+ return buf;
1901+ }
1902+
1903+ static class ADL {
1904+ public float ascentX;
1905+ public float ascentY;
1906+ public float descentX;
1907+ public float descentY;
1908+ public float leadingX;
1909+ public float leadingY;
1910+
1911+ public String toString() {
1912+ return toStringBuffer(null).toString();
1913+ }
1914+
1915+ protected StringBuffer toStringBuffer(StringBuffer result) {
1916+ if (result == null) {
1917+ result = new StringBuffer();
1918+ }
1919+ result.append("ax: ");
1920+ result.append(ascentX);
1921+ result.append(" ay: ");
1922+ result.append(ascentY);
1923+ result.append(" dx: ");
1924+ result.append(descentX);
1925+ result.append(" dy: ");
1926+ result.append(descentY);
1927+ result.append(" lx: ");
1928+ result.append(leadingX);
1929+ result.append(" ly: ");
1930+ result.append(leadingY);
1931+
1932+ return result;
1933+ }
1934+ }
1935+}