/* * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.font; import java.awt.Font; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import static java.awt.RenderingHints.*; import java.awt.Shape; import java.awt.font.FontRenderContext; import java.awt.font.GlyphMetrics; import java.awt.font.GlyphJustificationInfo; import java.awt.font.GlyphVector; import java.awt.font.LineMetrics; import java.awt.font.TextAttribute; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.lang.ref.SoftReference; import java.text.CharacterIterator; import sun.awt.SunHints; import sun.java2d.loops.FontInfo; /** * Standard implementation of GlyphVector used by Font, GlyphList, and * SunGraphics2D. * * The main issues involve the semantics of the various transforms * (font, glyph, device) and their effect on rendering and metrics. * * Very, very unfortunately, the translation component of the font * transform affects where the text gets rendered. It offsets the * rendering origin. None of the other metrics of the glyphvector * are affected, making them inconsistent with the rendering behavior. * I think the translation component of the font would be better * interpreted as the translation component of a per-glyph transform, * but I don't know if this is possible to change. * * After the font transform is applied, the glyph transform is * applied. This makes glyph transforms relative to font transforms, * if the font transform changes, the glyph transform will have the * same (relative) effect on the outline of the glyph. The outline * and logical bounds are passed through the glyph transform before * being returned. The glyph metrics ignore the glyph transform, but * provide the outline bounds and the advance vector of the glyph (the * latter will be rotated if the font is rotated). The default layout * places each glyph at the end of the advance vector of the previous * glyph, and since the glyph transform translates the advance vector, * this means a glyph transform affects the positions of all * subsequent glyphs if defaultLayout is called after setting a glyph * transform. In the glyph info array, the bounds are the outline * bounds including the glyph transform, and the positions are as * computed, and the advances are the deltas between the positions. * * (There's a bug in the logical bounds of a rotated glyph for * composite fonts, it's not to spec (in 1.4.0, 1.4.1, 1.4.2). The * problem is that the rotated composite doesn't handle the multiple * ascents and descents properly in both x and y. You end up with * a rotated advance vector but an unrotated ascent and descent.) * * Finally, the whole thing is transformed by the device transform to * position it on the page. * * Another bug: The glyph outline seems to ignore fractional point * size information, but the images (and advances) don't ignore it. * * Small fonts drawn at large magnification have odd advances when * fractional metrics is off-- that's because the advances depend on * the frc. When the frc is scaled appropriately, the advances are * fine. FM or a large frc (high numbers) make the advances right. * * The buffer aa flag doesn't affect rendering, the glyph vector * renders as AA if aa is set in its frc, and as non-aa if aa is not * set in its frc. * * font rotation, baseline, vertical etc. * * Font rotation and baseline Line metrics should be measured along a * unit vector pi/4 cc from the baseline vector. For 'horizontal' * fonts the baseline vector is the x vector passed through the font * transform (ignoring translation), for 'vertical' it is the y * vector. This definition makes ascent, descent, etc independent of * shear, so shearing can be used to simulate italic. This means no * fonts have 'negative ascents' or 'zero ascents' etc. * * Having a coordinate system with orthogonal axes where one is * parallel to the baseline means we could use rectangles and interpret * them in terms of this coordinate system. Unfortunately there * is support for rotated fonts in the jdk already so maintaining * the semantics of existing code (getlogical bounds, etc) might * be difficult. * * A font transform transforms both the baseline and all the glyphs * in the font, so it does not rotate the glyph w.r.t the baseline. * If you do want to rotate individual glyphs, you need to apply a * glyph transform. If performDefaultLayout is called after this, * the transformed glyph advances will affect the glyph positions. * * useful additions * - select vertical metrics - glyphs are rotated pi/4 cc and vertical * metrics are used to align them to the baseline. * - define baseline for font (glyph rotation not linked to baseline) * - define extra space (delta between each glyph along baseline) * - define offset (delta from 'true' baseline, impacts ascent and * descent as these are still computed from true basline and pinned * to zero, used in superscript). */ public class StandardGlyphVector extends GlyphVector { private Font font; private FontRenderContext frc; private int[] glyphs; // always private int[] userGlyphs; // used to return glyphs to the client. private float[] positions; // only if not default advances private int[] charIndices; // only if interesting private int flags; // indicates whether positions, charIndices is interesting private static final int UNINITIALIZED_FLAGS = -1; // transforms information private GlyphTransformInfo gti; // information about per-glyph transforms // !!! can we get rid of any of this extra stuff? private AffineTransform ftx; // font transform without translation private AffineTransform dtx; // device transform used for strike calculations, no translation private AffineTransform invdtx; // inverse of dtx or null if dtx is identity private AffineTransform frctx; // font render context transform, wish we could just share it private Font2D font2D; // basic strike-independent stuff private SoftReference fsref; // font strike reference for glyphs with no per-glyph transform ///////////////////////////// // Constructors and Factory methods ///////////////////////////// public StandardGlyphVector(Font font, String str, FontRenderContext frc) { init(font, str.toCharArray(), 0, str.length(), frc, UNINITIALIZED_FLAGS); } public StandardGlyphVector(Font font, char[] text, FontRenderContext frc) { init(font, text, 0, text.length, frc, UNINITIALIZED_FLAGS); } public StandardGlyphVector(Font font, char[] text, int start, int count, FontRenderContext frc) { init(font, text, start, count, frc, UNINITIALIZED_FLAGS); } private float getTracking(Font font) { if (font.hasLayoutAttributes()) { AttributeValues values = ((AttributeMap)font.getAttributes()).getValues(); return values.getTracking(); } return 0; } // used by GlyphLayout to construct a glyphvector public StandardGlyphVector(Font font, FontRenderContext frc, int[] glyphs, float[] positions, int[] indices, int flags) { initGlyphVector(font, frc, glyphs, positions, indices, flags); // this code should go into layout float track = getTracking(font); if (track != 0) { track *= font.getSize2D(); Point2D.Float trackPt = new Point2D.Float(track, 0); // advance delta if (font.isTransformed()) { AffineTransform at = font.getTransform(); at.deltaTransform(trackPt, trackPt); } // how do we know its a base glyph // for now, it is if the natural advance of the glyph is non-zero Font2D f2d = FontUtilities.getFont2D(font); FontStrike strike = f2d.getStrike(font, frc); float[] deltas = { trackPt.x, trackPt.y }; for (int j = 0; j < deltas.length; ++j) { float inc = deltas[j]; if (inc != 0) { float delta = 0; for (int i = j, n = 0; n < glyphs.length; i += 2) { if (strike.getGlyphAdvance(glyphs[n++]) != 0) { // might be an inadequate test positions[i] += delta; delta += inc; } } positions[positions.length-2+j] += delta; } } } } public void initGlyphVector(Font font, FontRenderContext frc, int[] glyphs, float[] positions, int[] indices, int flags) { this.font = font; this.frc = frc; this.glyphs = glyphs; this.userGlyphs = glyphs; // no need to check this.positions = positions; this.charIndices = indices; this.flags = flags; initFontData(); } public StandardGlyphVector(Font font, CharacterIterator iter, FontRenderContext frc) { int offset = iter.getBeginIndex(); char[] text = new char [iter.getEndIndex() - offset]; for(char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) { text[iter.getIndex() - offset] = c; } init(font, text, 0, text.length, frc, UNINITIALIZED_FLAGS); } public StandardGlyphVector(Font font, int[] glyphs, FontRenderContext frc) { // !!! find callers of this // should be able to fully init from raw data, e.g. charmap, flags too. this.font = font; this.frc = frc; this.flags = UNINITIALIZED_FLAGS; initFontData(); this.userGlyphs = glyphs; this.glyphs = getValidatedGlyphs(this.userGlyphs); } /* This is called from the rendering loop. FontInfo is supplied * because a GV caches a strike and glyph images suitable for its FRC. * LCD text isn't currently supported on all surfaces, in which case * standard AA must be used. This is most likely to occur when LCD text * is requested and the surface is some non-standard type or hardward * surface for which there are no accelerated loops. * We can detect this as being AA=="ON" in the FontInfo and AA!="ON" * and AA!="GASP" in the FRC - since this only occurs for LCD text we don't * need to check any more precisely what value is in the FRC. */ public static StandardGlyphVector getStandardGV(GlyphVector gv, FontInfo info) { if (info.aaHint == SunHints.INTVAL_TEXT_ANTIALIAS_ON) { Object aaHint = gv.getFontRenderContext().getAntiAliasingHint(); if (aaHint != VALUE_TEXT_ANTIALIAS_ON && aaHint != VALUE_TEXT_ANTIALIAS_GASP) { /* We need to create a new GV with AA==ON for rendering */ FontRenderContext frc = gv.getFontRenderContext(); frc = new FontRenderContext(frc.getTransform(), VALUE_TEXT_ANTIALIAS_ON, frc.getFractionalMetricsHint()); return new StandardGlyphVector(gv, frc); } } if (gv instanceof StandardGlyphVector) { return (StandardGlyphVector)gv; } return new StandardGlyphVector(gv, gv.getFontRenderContext()); } ///////////////////////////// // GlyphVector API ///////////////////////////// public Font getFont() { return this.font; } public FontRenderContext getFontRenderContext() { return this.frc; } public void performDefaultLayout() { positions = null; if (getTracking(font) == 0) { clearFlags(FLAG_HAS_POSITION_ADJUSTMENTS); } } public int getNumGlyphs() { return glyphs.length; } public int getGlyphCode(int glyphIndex) { return userGlyphs[glyphIndex]; } public int[] getGlyphCodes(int start, int count, int[] result) { if (count < 0) { throw new IllegalArgumentException("count = " + count); } if (start < 0) { throw new IndexOutOfBoundsException("start = " + start); } if (start > glyphs.length - count) { // watch out for overflow if index + count overlarge throw new IndexOutOfBoundsException("start + count = " + (start + count)); } if (result == null) { result = new int[count]; } // if arraycopy were faster, we wouldn't code this for (int i = 0; i < count; ++i) { result[i] = userGlyphs[i + start]; } return result; } public int getGlyphCharIndex(int ix) { if (ix < 0 && ix >= glyphs.length) { throw new IndexOutOfBoundsException("" + ix); } if (charIndices == null) { if ((getLayoutFlags() & FLAG_RUN_RTL) != 0) { return glyphs.length - 1 - ix; } return ix; } return charIndices[ix]; } public int[] getGlyphCharIndices(int start, int count, int[] result) { if (start < 0 || count < 0 || (count > glyphs.length - start)) { throw new IndexOutOfBoundsException("" + start + ", " + count); } if (result == null) { result = new int[count]; } if (charIndices == null) { if ((getLayoutFlags() & FLAG_RUN_RTL) != 0) { for (int i = 0, n = glyphs.length - 1 - start; i < count; ++i, --n) { result[i] = n; } } else { for (int i = 0, n = start; i < count; ++i, ++n) { result[i] = n; } } } else { for (int i = 0; i < count; ++i) { result[i] = charIndices[i + start]; } } return result; } // !!! not cached, assume TextLayout will cache if necessary // !!! reexamine for per-glyph-transforms // !!! revisit for text-on-a-path, vertical public Rectangle2D getLogicalBounds() { setFRCTX(); initPositions(); LineMetrics lm = font.getLineMetrics("", frc); float minX, minY, maxX, maxY; // horiz only for now... minX = 0; minY = -lm.getAscent(); maxX = 0; maxY = lm.getDescent() + lm.getLeading(); if (glyphs.length > 0) { maxX = positions[positions.length - 2]; } return new Rectangle2D.Float(minX, minY, maxX - minX, maxY - minY); } // !!! not cached, assume TextLayout will cache if necessary public Rectangle2D getVisualBounds() { Rectangle2D result = null; for (int i = 0; i < glyphs.length; ++i) { Rectangle2D glyphVB = getGlyphVisualBounds(i).getBounds2D(); if (!glyphVB.isEmpty()) { if (result == null) { result = glyphVB; } else { Rectangle2D.union(result, glyphVB, result); } } } if (result == null) { result = new Rectangle2D.Float(0, 0, 0, 0); } return result; } // !!! not cached, assume TextLayout will cache if necessary // !!! fontStrike needs a method for this public Rectangle getPixelBounds(FontRenderContext renderFRC, float x, float y) { return getGlyphsPixelBounds(renderFRC, x, y, 0, glyphs.length); } public Shape getOutline() { return getGlyphsOutline(0, glyphs.length, 0, 0); } public Shape getOutline(float x, float y) { return getGlyphsOutline(0, glyphs.length, x, y); } // relative to gv origin public Shape getGlyphOutline(int ix) { return getGlyphsOutline(ix, 1, 0, 0); } // relative to gv origin offset by x, y public Shape getGlyphOutline(int ix, float x, float y) { return getGlyphsOutline(ix, 1, x, y); } public Point2D getGlyphPosition(int ix) { initPositions(); ix *= 2; return new Point2D.Float(positions[ix], positions[ix + 1]); } public void setGlyphPosition(int ix, Point2D pos) { initPositions(); int ix2 = ix << 1; positions[ix2] = (float)pos.getX(); positions[ix2 + 1] = (float)pos.getY(); clearCaches(ix); addFlags(FLAG_HAS_POSITION_ADJUSTMENTS); } public AffineTransform getGlyphTransform(int ix) { if (ix < 0 || ix >= glyphs.length) { throw new IndexOutOfBoundsException("ix = " + ix); } if (gti != null) { return gti.getGlyphTransform(ix); } return null; // spec'd as returning null } public void setGlyphTransform(int ix, AffineTransform newTX) { if (ix < 0 || ix >= glyphs.length) { throw new IndexOutOfBoundsException("ix = " + ix); } if (gti == null) { if (newTX == null || newTX.isIdentity()) { return; } gti = new GlyphTransformInfo(this); } gti.setGlyphTransform(ix, newTX); // sets flags if (gti.transformCount() == 0) { gti = null; } } public int getLayoutFlags() { if (flags == UNINITIALIZED_FLAGS) { flags = 0; if (charIndices != null && glyphs.length > 1) { boolean ltr = true; boolean rtl = true; int rtlix = charIndices.length; // rtl index for (int i = 0; i < charIndices.length && (ltr || rtl); ++i) { int cx = charIndices[i]; ltr = ltr && (cx == i); rtl = rtl && (cx == --rtlix); } if (rtl) flags |= FLAG_RUN_RTL; if (!rtl && !ltr) flags |= FLAG_COMPLEX_GLYPHS; } } return flags; } public float[] getGlyphPositions(int start, int count, float[] result) { if (count < 0) { throw new IllegalArgumentException("count = " + count); } if (start < 0) { throw new IndexOutOfBoundsException("start = " + start); } if (start > glyphs.length + 1 - count) { // watch for overflow throw new IndexOutOfBoundsException("start + count = " + (start + count)); } return internalGetGlyphPositions(start, count, 0, result); } public Shape getGlyphLogicalBounds(int ix) { if (ix < 0 || ix >= glyphs.length) { throw new IndexOutOfBoundsException("ix = " + ix); } Shape[] lbcache; if (lbcacheRef == null || (lbcache = lbcacheRef.get()) == null) { lbcache = new Shape[glyphs.length]; lbcacheRef = new SoftReference<>(lbcache); } Shape result = lbcache[ix]; if (result == null) { setFRCTX(); initPositions(); // !!! ought to return a rectangle2d for simple cases, though the following works for all // get the position, the tx offset, and the x,y advance and x,y adl. The // shape is the box formed by adv (width) and adl (height) offset by // the position plus the tx offset minus the ascent. ADL adl = new ADL(); GlyphStrike gs = getGlyphStrike(ix); gs.getADL(adl); Point2D.Float adv = gs.strike.getGlyphMetrics(glyphs[ix]); float wx = adv.x; float wy = adv.y; float hx = adl.descentX + adl.leadingX + adl.ascentX; float hy = adl.descentY + adl.leadingY + adl.ascentY; float x = positions[ix*2] + gs.dx - adl.ascentX; float y = positions[ix*2+1] + gs.dy - adl.ascentY; GeneralPath gp = new GeneralPath(); gp.moveTo(x, y); gp.lineTo(x + wx, y + wy); gp.lineTo(x + wx + hx, y + wy + hy); gp.lineTo(x + hx, y + hy); gp.closePath(); result = new DelegatingShape(gp); lbcache[ix] = result; } return result; } private SoftReference lbcacheRef; public Shape getGlyphVisualBounds(int ix) { if (ix < 0 || ix >= glyphs.length) { throw new IndexOutOfBoundsException("ix = " + ix); } Shape[] vbcache; if (vbcacheRef == null || (vbcache = vbcacheRef.get()) == null) { vbcache = new Shape[glyphs.length]; vbcacheRef = new SoftReference<>(vbcache); } Shape result = vbcache[ix]; if (result == null) { result = new DelegatingShape(getGlyphOutlineBounds(ix)); vbcache[ix] = result; } return result; } private SoftReference vbcacheRef; public Rectangle getGlyphPixelBounds(int index, FontRenderContext renderFRC, float x, float y) { return getGlyphsPixelBounds(renderFRC, x, y, index, 1); } public GlyphMetrics getGlyphMetrics(int ix) { if (ix < 0 || ix >= glyphs.length) { throw new IndexOutOfBoundsException("ix = " + ix); } Rectangle2D vb = getGlyphVisualBounds(ix).getBounds2D(); Point2D pt = getGlyphPosition(ix); vb.setRect(vb.getMinX() - pt.getX(), vb.getMinY() - pt.getY(), vb.getWidth(), vb.getHeight()); Point2D.Float adv = getGlyphStrike(ix).strike.getGlyphMetrics(glyphs[ix]); GlyphMetrics gm = new GlyphMetrics(true, adv.x, adv.y, vb, GlyphMetrics.STANDARD); return gm; } public GlyphJustificationInfo getGlyphJustificationInfo(int ix) { if (ix < 0 || ix >= glyphs.length) { throw new IndexOutOfBoundsException("ix = " + ix); } // currently we don't have enough information to do this right. should // get info from the font and use real OT/GX justification. Right now // sun/font/ExtendedTextSourceLabel assigns one of three infos // based on whether the char is kanji, space, or other. return null; } public boolean equals(GlyphVector rhs) { if (this == rhs) { return true; } if (rhs == null) { return false; } try { StandardGlyphVector other = (StandardGlyphVector)rhs; if (glyphs.length != other.glyphs.length) { return false; } for (int i = 0; i < glyphs.length; ++i) { if (glyphs[i] != other.glyphs[i]) { return false; } } if (!font.equals(other.font)) { return false; } if (!frc.equals(other.frc)) { return false; } if ((other.positions == null) != (positions == null)) { if (positions == null) { initPositions(); } else { other.initPositions(); } } if (positions != null) { for (int i = 0; i < positions.length; ++i) { if (positions[i] != other.positions[i]) { return false; } } } if (gti == null) { return other.gti == null; } else { return gti.equals(other.gti); } } catch (ClassCastException e) { // assume they are different simply by virtue of the class difference return false; } } /** * As a concrete subclass of Object that implements equality, this must * implement hashCode. */ public int hashCode() { return font.hashCode() ^ glyphs.length; } /** * Since we implement equality comparisons for GlyphVector, we implement * the inherited Object.equals(Object) as well. GlyphVector should do * this, and define two glyphvectors as not equal if the classes differ. */ public boolean equals(Object rhs) { try { return equals((GlyphVector)rhs); } catch (ClassCastException e) { return false; } } /** * Sometimes I wish java had covariant return types... */ public StandardGlyphVector copy() { return (StandardGlyphVector)clone(); } /** * As a concrete subclass of GlyphVector, this must implement clone. */ public Object clone() { // positions, gti are mutable so we have to clone them // font2d can be shared // fsref is a cache and can be shared try { StandardGlyphVector result = (StandardGlyphVector)super.clone(); result.clearCaches(); if (positions != null) { result.positions = positions.clone(); } if (gti != null) { result.gti = new GlyphTransformInfo(result, gti); } return result; } catch (CloneNotSupportedException e) { } return this; } ////////////////////// // StandardGlyphVector new public methods ///////////////////// /* * Set a multiple glyph positions at one time. GlyphVector only * provides API to set a single glyph at a time. */ public void setGlyphPositions(float[] srcPositions, int srcStart, int start, int count) { if (count < 0) { throw new IllegalArgumentException("count = " + count); } initPositions(); for (int i = start * 2, e = i + count * 2, p = srcStart; i < e; ++i, ++p) { positions[i] = srcPositions[p]; } clearCaches(); addFlags(FLAG_HAS_POSITION_ADJUSTMENTS); } /** * Set all the glyph positions, including the 'after last glyph' position. * The srcPositions array must be of length (numGlyphs + 1) * 2. */ public void setGlyphPositions(float[] srcPositions) { int requiredLength = glyphs.length * 2 + 2; if (srcPositions.length != requiredLength) { throw new IllegalArgumentException("srcPositions.length != " + requiredLength); } positions = srcPositions.clone(); clearCaches(); addFlags(FLAG_HAS_POSITION_ADJUSTMENTS); } /** * This is a convenience overload that gets all the glyph positions, which * is what you usually want to do if you're getting more than one. * !!! should I bother taking result parameter? */ public float[] getGlyphPositions(float[] result) { return internalGetGlyphPositions(0, glyphs.length + 1, 0, result); } /** * Get transform information for the requested range of glyphs. * If no glyphs have a transform, return null. * If a glyph has no transform (or is the identity transform) its entry in the result array will be null. * If the passed-in result is null an array will be allocated for the caller. * Each transform instance in the result array will unique, and independent of the GlyphVector's transform. */ public AffineTransform[] getGlyphTransforms(int start, int count, AffineTransform[] result) { if (start < 0 || count < 0 || start + count > glyphs.length) { throw new IllegalArgumentException("start: " + start + " count: " + count); } if (gti == null) { return null; } if (result == null) { result = new AffineTransform[count]; } for (int i = 0; i < count; ++i, ++start) { result[i] = gti.getGlyphTransform(start); } return result; } /** * Convenience overload for getGlyphTransforms(int, int, AffineTransform[], int); */ public AffineTransform[] getGlyphTransforms() { return getGlyphTransforms(0, glyphs.length, null); } /** * Set a number of glyph transforms. * Original transforms are unchanged. The array may contain nulls, and also may * contain multiple references to the same transform instance. */ public void setGlyphTransforms(AffineTransform[] srcTransforms, int srcStart, int start, int count) { for (int i = start, e = start + count; i < e; ++i) { setGlyphTransform(i, srcTransforms[srcStart + i]); } } /** * Convenience overload of setGlyphTransforms(AffineTransform[], int, int, int). */ public void setGlyphTransforms(AffineTransform[] srcTransforms) { setGlyphTransforms(srcTransforms, 0, 0, glyphs.length); } /** * For each glyph return posx, posy, advx, advy, visx, visy, visw, vish. */ public float[] getGlyphInfo() { setFRCTX(); initPositions(); float[] result = new float[glyphs.length * 8]; for (int i = 0, n = 0; i < glyphs.length; ++i, n += 8) { float x = positions[i*2]; float y = positions[i*2+1]; result[n] = x; result[n+1] = y; int glyphID = glyphs[i]; GlyphStrike s = getGlyphStrike(i); Point2D.Float adv = s.strike.getGlyphMetrics(glyphID); result[n+2] = adv.x; result[n+3] = adv.y; Rectangle2D vb = getGlyphVisualBounds(i).getBounds2D(); result[n+4] = (float)(vb.getMinX()); result[n+5] = (float)(vb.getMinY()); result[n+6] = (float)(vb.getWidth()); result[n+7] = (float)(vb.getHeight()); } return result; } /** * !!! not used currently, but might be by getPixelbounds? */ public void pixellate(FontRenderContext renderFRC, Point2D loc, Point pxResult) { if (renderFRC == null) { renderFRC = frc; } // it is a total pain that you have to copy the transform. AffineTransform at = renderFRC.getTransform(); at.transform(loc, loc); pxResult.x = (int)loc.getX(); // but must not behave oddly around zero pxResult.y = (int)loc.getY(); loc.setLocation(pxResult.x, pxResult.y); try { at.inverseTransform(loc, loc); } catch (NoninvertibleTransformException e) { throw new IllegalArgumentException("must be able to invert frc transform"); } } ////////////////////// // StandardGlyphVector package private methods ///////////////////// // used by glyphlist to determine if it needs to allocate/size positions array // gti always uses positions because the gtx might have translation. We also // need positions if the rendering dtx is different from the frctx. boolean needsPositions(double[] devTX) { return gti != null || (getLayoutFlags() & FLAG_HAS_POSITION_ADJUSTMENTS) != 0 || !matchTX(devTX, frctx); } // used by glyphList to get strong refs to font strikes for duration of rendering call // if devTX matches current devTX, we're ready to go // if we don't have multiple transforms, we're already ok // !!! I'm not sure fontInfo works so well for glyphvector, since we have to be able to handle // the multiple-strikes case /* * GlyphList calls this to set up its images data. First it calls needsPositions, * passing the devTX, to see if it should provide us a positions array to fill. * It only doesn't need them if we're a simple glyph vector whose frctx matches the * devtx. * Then it calls setupGlyphImages. If we need positions, we make sure we have our * default positions based on the frctx first. Then we set the devTX, and use * strikes based on it to generate the images. Finally, we fill in the positions * array. * If we have transforms, we delegate to gti. It depends on our having first * initialized the positions and devTX. */ Object setupGlyphImages(long[] images, float[] positions, double[] devTX) { initPositions(); // FIRST ensure we have positions based on our frctx setRenderTransform(devTX); // THEN make sure we are using the desired devTX if (gti != null) { return gti.setupGlyphImages(images, positions, dtx); } GlyphStrike gs = getDefaultStrike(); gs.strike.getGlyphImagePtrs(glyphs, images, glyphs.length); if (positions != null) { if (dtx.isIdentity()) { System.arraycopy(this.positions, 0, positions, 0, glyphs.length * 2); } else { dtx.transform(this.positions, 0, positions, 0, glyphs.length); } } return gs; } ////////////////////// // StandardGlyphVector private methods ///////////////////// // We keep translation in our frctx since getPixelBounds uses it. But // GlyphList pulls out the translation and applies it separately, so // we strip it out when we set the dtx. Basically nothing uses the // translation except getPixelBounds. // called by needsPositions, setRenderTransform private static boolean matchTX(double[] lhs, AffineTransform rhs) { return lhs[0] == rhs.getScaleX() && lhs[1] == rhs.getShearY() && lhs[2] == rhs.getShearX() && lhs[3] == rhs.getScaleY(); } // returns new tx if old one has translation, otherwise returns old one private static AffineTransform getNonTranslateTX(AffineTransform tx) { if (tx.getTranslateX() != 0 || tx.getTranslateY() != 0) { tx = new AffineTransform(tx.getScaleX(), tx.getShearY(), tx.getShearX(), tx.getScaleY(), 0, 0); } return tx; } private static boolean equalNonTranslateTX(AffineTransform lhs, AffineTransform rhs) { return lhs.getScaleX() == rhs.getScaleX() && lhs.getShearY() == rhs.getShearY() && lhs.getShearX() == rhs.getShearX() && lhs.getScaleY() == rhs.getScaleY(); } // called by setupGlyphImages (after needsPositions, so redundant match check?) private void setRenderTransform(double[] devTX) { assert(devTX.length == 4); if (!matchTX(devTX, dtx)) { resetDTX(new AffineTransform(devTX)); // no translation since devTX len == 4. } } // called by getGlyphsPixelBounds private final void setDTX(AffineTransform tx) { if (!equalNonTranslateTX(dtx, tx)) { resetDTX(getNonTranslateTX(tx)); } } // called by most functions private final void setFRCTX() { if (!equalNonTranslateTX(frctx, dtx)) { resetDTX(getNonTranslateTX(frctx)); } } /** * Change the dtx for the strike refs we use. Keeps a reference to the at. At * must not contain translation. * Called by setRenderTransform, setDTX, initFontData. */ private final void resetDTX(AffineTransform at) { fsref = null; dtx = at; invdtx = null; if (!dtx.isIdentity()) { try { invdtx = dtx.createInverse(); } catch (NoninvertibleTransformException e) { // we needn't care for rendering } } if (gti != null) { gti.strikesRef = null; } } /** * Utility used by getStandardGV. * Constructs a StandardGlyphVector from a generic glyph vector. * Do not call this from new contexts without considering the comment * about "userGlyphs". */ private StandardGlyphVector(GlyphVector gv, FontRenderContext frc) { this.font = gv.getFont(); this.frc = frc; initFontData(); int nGlyphs = gv.getNumGlyphs(); this.userGlyphs = gv.getGlyphCodes(0, nGlyphs, null); if (gv instanceof StandardGlyphVector) { /* userGlyphs will be OK because this is a private constructor * and the returned instance is used only for rendering. * It's not constructable by user code, nor returned to the * application. So we know "userGlyphs" are valid as having * been either already validated or are the result of layout. */ this.glyphs = userGlyphs; } else { this.glyphs = getValidatedGlyphs(this.userGlyphs); } this.flags = gv.getLayoutFlags() & FLAG_MASK; if ((flags & FLAG_HAS_POSITION_ADJUSTMENTS) != 0) { this.positions = gv.getGlyphPositions(0, nGlyphs + 1, null); } if ((flags & FLAG_COMPLEX_GLYPHS) != 0) { this.charIndices = gv.getGlyphCharIndices(0, nGlyphs, null); } if ((flags & FLAG_HAS_TRANSFORMS) != 0) { AffineTransform[] txs = new AffineTransform[nGlyphs]; // worst case for (int i = 0; i < nGlyphs; ++i) { txs[i] = gv.getGlyphTransform(i); // gv doesn't have getGlyphsTransforms } setGlyphTransforms(txs); } } /* Before asking the Font we see if the glyph code is * FFFE or FFFF which are special values that we should be internally * ready to handle as meaning invisible glyphs. The Font would report * those as the missing glyph. */ int[] getValidatedGlyphs(int[] oglyphs) { int len = oglyphs.length; int[] vglyphs = new int[len]; for (int i=0; i text.length) { throw new ArrayIndexOutOfBoundsException("start or count out of bounds"); } this.font = font; this.frc = frc; this.flags = flags; if (getTracking(font) != 0) { addFlags(FLAG_HAS_POSITION_ADJUSTMENTS); } // !!! change mapper interface? if (start != 0) { char[] temp = new char[count]; System.arraycopy(text, start, temp, 0, count); text = temp; } initFontData(); // sets up font2D // !!! no layout for now, should add checks // !!! need to support creating a StandardGlyphVector from a TextMeasurer's info... glyphs = new int[count]; // hmmm /* Glyphs obtained here are already validated by the font */ userGlyphs = glyphs; font2D.getMapper().charsToGlyphs(count, text, glyphs); } private void initFontData() { font2D = FontUtilities.getFont2D(font); float s = font.getSize2D(); if (font.isTransformed()) { ftx = font.getTransform(); if (ftx.getTranslateX() != 0 || ftx.getTranslateY() != 0) { addFlags(FLAG_HAS_POSITION_ADJUSTMENTS); } ftx.setTransform(ftx.getScaleX(), ftx.getShearY(), ftx.getShearX(), ftx.getScaleY(), 0, 0); ftx.scale(s, s); } else { ftx = AffineTransform.getScaleInstance(s, s); } frctx = frc.getTransform(); resetDTX(getNonTranslateTX(frctx)); } /** * Copy glyph position data into a result array starting at the indicated * offset in the array. If the passed-in result array is null, a new * array will be allocated and returned. * * This is an internal method and does no extra argument checking. * * @param start the index of the first glyph to get * @param count the number of glyphs to get * @param offset the offset into result at which to put the data * @param result an array to hold the x,y positions * @return the modified position array */ private float[] internalGetGlyphPositions(int start, int count, int offset, float[] result) { if (result == null) { result = new float[offset + count * 2]; } initPositions(); // System.arraycopy is slow for stuff like this for (int i = offset, e = offset + count * 2, p = start * 2; i < e; ++i, ++p) { result[i] = positions[p]; } return result; } private Rectangle2D getGlyphOutlineBounds(int ix) { setFRCTX(); initPositions(); return getGlyphStrike(ix).getGlyphOutlineBounds(glyphs[ix], positions[ix*2], positions[ix*2+1]); } /** * Used by getOutline, getGlyphsOutline */ private Shape getGlyphsOutline(int start, int count, float x, float y) { setFRCTX(); initPositions(); GeneralPath result = new GeneralPath(GeneralPath.WIND_NON_ZERO); for (int i = start, e = start + count, n = start * 2; i < e; ++i, n += 2) { float px = x + positions[n]; float py = y + positions[n+1]; getGlyphStrike(i).appendGlyphOutline(glyphs[i], result, px, py); } return result; } private Rectangle getGlyphsPixelBounds(FontRenderContext frc, float x, float y, int start, int count) { initPositions(); // FIRST ensure we have positions based on our frctx AffineTransform tx = null; if (frc == null || frc.equals(this.frc)) { tx = frctx; } else { tx = frc.getTransform(); } setDTX(tx); // need to get the right strikes, but we use tx itself to translate the points if (gti != null) { return gti.getGlyphsPixelBounds(tx, x, y, start, count); } FontStrike fs = getDefaultStrike().strike; Rectangle result = null; Rectangle r = new Rectangle(); Point2D.Float pt = new Point.Float(); int n = start * 2; while (--count >= 0) { pt.x = x + positions[n++]; pt.y = y + positions[n++]; tx.transform(pt, pt); fs.getGlyphImageBounds(glyphs[start++], pt, r); if (!r.isEmpty()) { if (result == null) { result = new Rectangle(r); } else { result.add(r); } } } return result != null ? result : r; } private void clearCaches(int ix) { if (lbcacheRef != null) { Shape[] lbcache = lbcacheRef.get(); if (lbcache != null) { lbcache[ix] = null; } } if (vbcacheRef != null) { Shape[] vbcache = vbcacheRef.get(); if (vbcache != null) { vbcache[ix] = null; } } } private void clearCaches() { lbcacheRef = null; vbcacheRef = null; } // internal use only for possible future extension /** * A flag used with getLayoutFlags that indicates whether this GlyphVector uses * a vertical baseline. */ public static final int FLAG_USES_VERTICAL_BASELINE = 128; /** * A flag used with getLayoutFlags that indicates whether this GlyphVector uses * vertical glyph metrics. A GlyphVector can use vertical metrics on a * horizontal line, or vice versa. */ public static final int FLAG_USES_VERTICAL_METRICS = 256; /** * A flag used with getLayoutFlags that indicates whether this GlyphVector uses * the 'alternate orientation.' Glyphs have a default orientation given a * particular baseline and metrics orientation, this is the orientation appropriate * for left-to-right text. For example, the letter 'A' can have four orientations, * with the point at 12, 3, 6, or 9 'o clock. The following table shows where the * point displays for different values of vertical baseline (vb), vertical * metrics (vm) and alternate orientation (fo):
*
* vb vm ao * -- -- -- -- * f f f 12 ^ horizontal metrics on horizontal lines * f f t 6 v * f t f 9 < vertical metrics on horizontal lines * f t t 3 > * t f f 3 > horizontal metrics on vertical lines * t f t 9 < * t t f 12 ^ vertical metrics on vertical lines * t t t 6 v *
*/ public static final int FLAG_USES_ALTERNATE_ORIENTATION = 512; /** * Ensure that the positions array exists and holds position data. * If the array is null, this allocates it and sets default positions. */ private void initPositions() { if (positions == null) { setFRCTX(); positions = new float[glyphs.length * 2 + 2]; Point2D.Float trackPt = null; float track = getTracking(font); if (track != 0) { track *= font.getSize2D(); trackPt = new Point2D.Float(track, 0); // advance delta } Point2D.Float pt = new Point2D.Float(0, 0); if (font.isTransformed()) { AffineTransform at = font.getTransform(); at.transform(pt, pt); positions[0] = pt.x; positions[1] = pt.y; if (trackPt != null) { at.deltaTransform(trackPt, trackPt); } } for (int i = 0, n = 2; i < glyphs.length; ++i, n += 2) { getGlyphStrike(i).addDefaultGlyphAdvance(glyphs[i], pt); if (trackPt != null) { pt.x += trackPt.x; pt.y += trackPt.y; } positions[n] = pt.x; positions[n+1] = pt.y; } } } /** * OR newFlags with existing flags. First computes existing flags if needed. */ private void addFlags(int newflags) { flags = getLayoutFlags() | newflags; } /** * AND the complement of clearedFlags with existing flags. First computes existing flags if needed. */ private void clearFlags(int clearedFlags) { flags = getLayoutFlags() & ~clearedFlags; } // general utility methods // encapsulate the test to check whether we have per-glyph transforms private GlyphStrike getGlyphStrike(int ix) { if (gti == null) { return getDefaultStrike(); } else { return gti.getStrike(ix); } } // encapsulate access to cached default glyph strike private GlyphStrike getDefaultStrike() { GlyphStrike gs = null; if (fsref != null) { gs = fsref.get(); } if (gs == null) { gs = GlyphStrike.create(this, dtx, null); fsref = new SoftReference<>(gs); } return gs; } ///////////////////// // Internal utility classes ///////////////////// // !!! I have this as a separate class instead of just inside SGV, // but I previously didn't bother. Now I'm trying this again. // Probably still not worth it, but I'd like to keep sgv's small in the common case. static final class GlyphTransformInfo { StandardGlyphVector sgv; // reference back to glyph vector - yuck int[] indices; // index into unique strikes double[] transforms; // six doubles per unique transform, because AT is a pain to manipulate SoftReference strikesRef; // ref to unique strikes, one per transform boolean haveAllStrikes; // true if the strike array has been filled by getStrikes(). // used when first setting a transform GlyphTransformInfo(StandardGlyphVector sgv) { this.sgv = sgv; } // used when cloning a glyph vector, need to set back link GlyphTransformInfo(StandardGlyphVector sgv, GlyphTransformInfo rhs) { this.sgv = sgv; this.indices = rhs.indices == null ? null : rhs.indices.clone(); this.transforms = rhs.transforms == null ? null : rhs.transforms.clone(); this.strikesRef = null; // can't share cache, so rather than clone, we just null out } // used in sgv equality public boolean equals(GlyphTransformInfo rhs) { if (rhs == null) { return false; } if (rhs == this) { return true; } if (this.indices.length != rhs.indices.length) { return false; } if (this.transforms.length != rhs.transforms.length) { return false; } // slow since we end up processing the same transforms multiple // times, but since transforms can be in any order, we either do // this or create a mapping. Equality tests aren't common so // leave it like this. for (int i = 0; i < this.indices.length; ++i) { int tix = this.indices[i]; int rix = rhs.indices[i]; if ((tix == 0) != (rix == 0)) { return false; } if (tix != 0) { tix *= 6; rix *= 6; for (int j = 6; j > 0; --j) { if (this.indices[--tix] != rhs.indices[--rix]) { return false; } } } } return true; } // implements sgv.setGlyphTransform void setGlyphTransform(int glyphIndex, AffineTransform newTX) { // we store all the glyph transforms as a double array, and for each glyph there // is an entry in the txIndices array indicating which transform to use. 0 means // there's no transform, 1 means use the first transform (the 6 doubles at offset // 0), 2 means use the second transform (the 6 doubles at offset 6), etc. // // Since this can be called multiple times, and since the number of transforms // affects the time it takes to construct the glyphs, we try to keep the arrays as // compact as possible, by removing transforms that are no longer used, and reusing // transforms where we already have them. double[] temp = new double[6]; boolean isIdentity = true; if (newTX == null || newTX.isIdentity()) { // Fill in temp temp[0] = temp[3] = 1.0; } else { isIdentity = false; newTX.getMatrix(temp); } if (indices == null) { if (isIdentity) { // no change return; } indices = new int[sgv.glyphs.length]; indices[glyphIndex] = 1; transforms = temp; } else { boolean addSlot = false; // assume we're not growing int newIndex = -1; if (isIdentity) { newIndex = 0; // might shrink } else { addSlot = true; // assume no match int i; loop: for (i = 0; i < transforms.length; i += 6) { for (int j = 0; j < 6; ++j) { if (transforms[i + j] != temp[j]) { continue loop; } } addSlot = false; break; } newIndex = i / 6 + 1; // if no match, end of list } // if we're using the same transform, nothing to do int oldIndex = indices[glyphIndex]; if (newIndex != oldIndex) { // see if we are removing last use of the old slot boolean removeSlot = false; if (oldIndex != 0) { removeSlot = true; for (int i = 0; i < indices.length; ++i) { if (indices[i] == oldIndex && i != glyphIndex) { removeSlot = false; break; } } } if (removeSlot && addSlot) { // reuse old slot with new transform newIndex = oldIndex; System.arraycopy(temp, 0, transforms, (newIndex - 1) * 6, 6); } else if (removeSlot) { if (transforms.length == 6) { // removing last one, so clear arrays indices = null; transforms = null; sgv.clearCaches(glyphIndex); sgv.clearFlags(FLAG_HAS_TRANSFORMS); strikesRef = null; return; } double[] ttemp = new double[transforms.length - 6]; System.arraycopy(transforms, 0, ttemp, 0, (oldIndex - 1) * 6); System.arraycopy(transforms, oldIndex * 6, ttemp, (oldIndex - 1) * 6, transforms.length - oldIndex * 6); transforms = ttemp; // clean up indices for (int i = 0; i < indices.length; ++i) { if (indices[i] > oldIndex) { // ignore == oldIndex, it's going away indices[i] -= 1; } } if (newIndex > oldIndex) { // don't forget to decrement this too if we need to --newIndex; } } else if (addSlot) { double[] ttemp = new double[transforms.length + 6]; System.arraycopy(transforms, 0, ttemp, 0, transforms.length); System.arraycopy(temp, 0, ttemp, transforms.length, 6); transforms = ttemp; } indices[glyphIndex] = newIndex; } } sgv.clearCaches(glyphIndex); sgv.addFlags(FLAG_HAS_TRANSFORMS); strikesRef = null; } // implements sgv.getGlyphTransform AffineTransform getGlyphTransform(int ix) { int index = indices[ix]; if (index == 0) { return null; } int x = (index - 1) * 6; return new AffineTransform(transforms[x + 0], transforms[x + 1], transforms[x + 2], transforms[x + 3], transforms[x + 4], transforms[x + 5]); } int transformCount() { if (transforms == null) { return 0; } return transforms.length / 6; } /** * The strike cache works like this. * * -Each glyph is thought of as having a transform, usually identity. * -Each request for a strike is based on a device transform, either the * one in the frc or the rendering transform. * -For general info, strikes are held with soft references. * -When rendering, strikes must be held with hard references for the * duration of the rendering call. GlyphList will have to hold this * info along with the image and position info, but toss the strike info * when done. * -Build the strike cache as needed. If the dev transform we want to use * has changed from the last time it is built, the cache is flushed by * the caller before these methods are called. * * Use a tx that doesn't include translation components of dst tx. */ Object setupGlyphImages(long[] images, float[] positions, AffineTransform tx) { int len = sgv.glyphs.length; GlyphStrike[] sl = getAllStrikes(); for (int i = 0; i < len; ++i) { GlyphStrike gs = sl[indices[i]]; int glyphID = sgv.glyphs[i]; images[i] = gs.strike.getGlyphImagePtr(glyphID); gs.getGlyphPosition(glyphID, i*2, sgv.positions, positions); } tx.transform(positions, 0, positions, 0, len); return sl; } Rectangle getGlyphsPixelBounds(AffineTransform tx, float x, float y, int start, int count) { Rectangle result = null; Rectangle r = new Rectangle(); Point2D.Float pt = new Point.Float(); int n = start * 2; while (--count >= 0) { GlyphStrike gs = getStrike(start); pt.x = x + sgv.positions[n++] + gs.dx; pt.y = y + sgv.positions[n++] + gs.dy; tx.transform(pt, pt); gs.strike.getGlyphImageBounds(sgv.glyphs[start++], pt, r); if (!r.isEmpty()) { if (result == null) { result = new Rectangle(r); } else { result.add(r); } } } return result != null ? result : r; } GlyphStrike getStrike(int glyphIndex) { if (indices != null) { GlyphStrike[] strikes = getStrikeArray(); return getStrikeAtIndex(strikes, indices[glyphIndex]); } return sgv.getDefaultStrike(); } private GlyphStrike[] getAllStrikes() { if (indices == null) { return null; } GlyphStrike[] strikes = getStrikeArray(); if (!haveAllStrikes) { for (int i = 0; i < strikes.length; ++i) { getStrikeAtIndex(strikes, i); } haveAllStrikes = true; } return strikes; } private GlyphStrike[] getStrikeArray() { GlyphStrike[] strikes = null; if (strikesRef != null) { strikes = strikesRef.get(); } if (strikes == null) { haveAllStrikes = false; strikes = new GlyphStrike[transformCount() + 1]; strikesRef = new SoftReference<>(strikes); } return strikes; } private GlyphStrike getStrikeAtIndex(GlyphStrike[] strikes, int strikeIndex) { GlyphStrike strike = strikes[strikeIndex]; if (strike == null) { if (strikeIndex == 0) { strike = sgv.getDefaultStrike(); } else { int ix = (strikeIndex - 1) * 6; AffineTransform gtx = new AffineTransform(transforms[ix], transforms[ix+1], transforms[ix+2], transforms[ix+3], transforms[ix+4], transforms[ix+5]); strike = GlyphStrike.create(sgv, sgv.dtx, gtx); } strikes[strikeIndex] = strike; } return strike; } } // This adjusts the metrics by the translation components of the glyph // transform. It is done here since the translation is not known by the // strike. // It adjusts the position of the image and the advance. public static final class GlyphStrike { StandardGlyphVector sgv; FontStrike strike; // hard reference float dx; float dy; static GlyphStrike create(StandardGlyphVector sgv, AffineTransform dtx, AffineTransform gtx) { float dx = 0; float dy = 0; AffineTransform tx = sgv.ftx; if (!dtx.isIdentity() || gtx != null) { tx = new AffineTransform(sgv.ftx); if (gtx != null) { tx.preConcatenate(gtx); dx = (float)tx.getTranslateX(); // uses ftx then gtx to get translation dy = (float)tx.getTranslateY(); } if (!dtx.isIdentity()) { tx.preConcatenate(dtx); } } int ptSize = 1; // only matters for 'gasp' case. Object aaHint = sgv.frc.getAntiAliasingHint(); if (aaHint == VALUE_TEXT_ANTIALIAS_GASP) { /* Must pass in the calculated point size for rendering. * If the glyph tx is anything other than identity or a * simple translate, calculate the transformed point size. */ if (!tx.isIdentity() && (tx.getType() & ~AffineTransform.TYPE_TRANSLATION) != 0) { double shearx = tx.getShearX(); if (shearx != 0) { double scaley = tx.getScaleY(); ptSize = (int)Math.sqrt(shearx * shearx + scaley * scaley); } else { ptSize = (int)(Math.abs(tx.getScaleY())); } } } int aa = FontStrikeDesc.getAAHintIntVal(aaHint,sgv.font2D, ptSize); int fm = FontStrikeDesc.getFMHintIntVal (sgv.frc.getFractionalMetricsHint()); FontStrikeDesc desc = new FontStrikeDesc(dtx, tx, sgv.font.getStyle(), aa, fm); // Get the strike via the handle. Shouldn't matter // if we've invalidated the font but its an extra precaution. FontStrike strike = sgv.font2D.handle.font2D.getStrike(desc); // !!! getStrike(desc, false) return new GlyphStrike(sgv, strike, dx, dy); } private GlyphStrike(StandardGlyphVector sgv, FontStrike strike, float dx, float dy) { this.sgv = sgv; this.strike = strike; this.dx = dx; this.dy = dy; } void getADL(ADL result) { StrikeMetrics sm = strike.getFontMetrics(); Point2D.Float delta = null; if (sgv.font.isTransformed()) { delta = new Point2D.Float(); delta.x = (float)sgv.font.getTransform().getTranslateX(); delta.y = (float)sgv.font.getTransform().getTranslateY(); } result.ascentX = -sm.ascentX; result.ascentY = -sm.ascentY; result.descentX = sm.descentX; result.descentY = sm.descentY; result.leadingX = sm.leadingX; result.leadingY = sm.leadingY; } void getGlyphPosition(int glyphID, int ix, float[] positions, float[] result) { result[ix] = positions[ix] + dx; ++ix; result[ix] = positions[ix] + dy; } void addDefaultGlyphAdvance(int glyphID, Point2D.Float result) { // !!! change this API? Creates unnecessary garbage. Also the name doesn't quite fit. // strike.addGlyphAdvance(Point2D.Float adv); // hey, whaddya know, matches my api :-) Point2D.Float adv = strike.getGlyphMetrics(glyphID); result.x += adv.x + dx; result.y += adv.y + dy; } Rectangle2D getGlyphOutlineBounds(int glyphID, float x, float y) { Rectangle2D result = null; if (sgv.invdtx == null) { result = new Rectangle2D.Float(); result.setRect(strike.getGlyphOutlineBounds(glyphID)); // don't mutate cached rect } else { GeneralPath gp = strike.getGlyphOutline(glyphID, 0, 0); gp.transform(sgv.invdtx); result = gp.getBounds2D(); } /* Since x is the logical advance of the glyph to this point. * Because of the way that Rectangle.union is specified, this * means that subsequent unioning of a rect including that * will be affected, even if the glyph is empty. So skip such * cases. This alone isn't a complete solution since x==0 * may also not be what is wanted. The code that does the * unioning also needs to be aware to ignore empty glyphs. */ if (!result.isEmpty()) { result.setRect(result.getMinX() + x + dx, result.getMinY() + y + dy, result.getWidth(), result.getHeight()); } return result; } void appendGlyphOutline(int glyphID, GeneralPath result, float x, float y) { // !!! fontStrike needs a method for this. For that matter, GeneralPath does. GeneralPath gp = null; if (sgv.invdtx == null) { gp = strike.getGlyphOutline(glyphID, x + dx, y + dy); } else { gp = strike.getGlyphOutline(glyphID, 0, 0); gp.transform(sgv.invdtx); gp.transform(AffineTransform.getTranslateInstance(x + dx, y + dy)); } PathIterator iterator = gp.getPathIterator(null); result.append(iterator, false); } } public String toString() { return appendString(null).toString(); } StringBuffer appendString(StringBuffer buf) { if (buf == null) { buf = new StringBuffer(); } try { buf.append("SGV{font: "); buf.append(font.toString()); buf.append(", frc: "); buf.append(frc.toString()); buf.append(", glyphs: ("); buf.append(glyphs.length); buf.append(")["); for (int i = 0; i < glyphs.length; ++i) { if (i > 0) { buf.append(", "); } buf.append(Integer.toHexString(glyphs[i])); } buf.append("]"); if (positions != null) { buf.append(", positions: ("); buf.append(positions.length); buf.append(")["); for (int i = 0; i < positions.length; i += 2) { if (i > 0) { buf.append(", "); } buf.append(positions[i]); buf.append("@"); buf.append(positions[i+1]); } buf.append("]"); } if (charIndices != null) { buf.append(", indices: ("); buf.append(charIndices.length); buf.append(")["); for (int i = 0; i < charIndices.length; ++i) { if (i > 0) { buf.append(", "); } buf.append(charIndices[i]); } buf.append("]"); } buf.append(", flags:"); if (getLayoutFlags() == 0) { buf.append(" default"); } else { if ((flags & FLAG_HAS_TRANSFORMS) != 0) { buf.append(" tx"); } if ((flags & FLAG_HAS_POSITION_ADJUSTMENTS) != 0) { buf.append(" pos"); } if ((flags & FLAG_RUN_RTL) != 0) { buf.append(" rtl"); } if ((flags & FLAG_COMPLEX_GLYPHS) != 0) { buf.append(" complex"); } } } catch(Exception e) { buf.append(' ').append(e.getMessage()); } buf.append('}'); return buf; } static class ADL { public float ascentX; public float ascentY; public float descentX; public float descentY; public float leadingX; public float leadingY; public String toString() { return toStringBuffer(null).toString(); } protected StringBuffer toStringBuffer(StringBuffer result) { if (result == null) { result = new StringBuffer(); } result.append("ax: "); result.append(ascentX); result.append(" ay: "); result.append(ascentY); result.append(" dx: "); result.append(descentX); result.append(" dy: "); result.append(descentY); result.append(" lx: "); result.append(leadingX); result.append(" ly: "); result.append(leadingY); return result; } } }