| /* | |
| * Copyright (c) 1998, 2017, 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<GlyphStrike> 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 | |
| float[] deltas = { trackPt.x, trackPt.y }; | |
| for (int j = 0; j < deltas.length; ++j) { | |
| float inc = deltas[j]; | |
| float prevPos = 0; | |
| if (inc != 0) { | |
| float delta = 0; | |
| for (int i = j; i < positions.length; i += 2) { | |
| if (i == j || prevPos != positions[i]) { | |
| prevPos = positions[i]; | |
| positions[i] += delta; | |
| delta += inc; | |
| } else if (prevPos == positions[i]) { | |
| positions[i] = positions[i - 2]; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| 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 hardware | |
| * 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) { | |
| if (ix < 0 || ix > glyphs.length) { | |
| throw new IndexOutOfBoundsException("ix = " + ix); | |
| } | |
| initPositions(); | |
| int ix2 = ix << 1; | |
| positions[ix2] = (float)pos.getX(); | |
| positions[ix2 + 1] = (float)pos.getY(); | |
| if (ix < glyphs.length) { | |
| 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<Shape[]> 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<Shape[]> 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; | |
| } | |
| ////////////////////// | |
| // 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 void setDTX(AffineTransform tx) { | |
| if (!equalNonTranslateTX(dtx, tx)) { | |
| resetDTX(getNonTranslateTX(tx)); | |
| } | |
| } | |
| // called by most functions | |
| private 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 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<len; i++) { | |
| if (oglyphs[i] == 0xFFFE || oglyphs[i] == 0xFFFF) { | |
| vglyphs[i] = oglyphs[i]; | |
| } else { | |
| vglyphs[i] = font2D.getValidatedGlyphCode(oglyphs[i]); | |
| } | |
| } | |
| return vglyphs; | |
| } | |
| // utility used by constructors | |
| private void init(Font font, char[] text, int start, int count, | |
| FontRenderContext frc, int flags) { | |
| if (start < 0 || count < 0 || start + count > 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); | |
| if (font2D instanceof FontSubstitution) { | |
| font2D = ((FontSubstitution)font2D).getCompositeFont2D(); | |
| } | |
| 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 {@code GlyphVector} uses | |
| * a vertical baseline. | |
| */ | |
| public static final int FLAG_USES_VERTICAL_BASELINE = 128; | |
| /** | |
| * A flag used with getLayoutFlags that indicates whether this {@code GlyphVector} uses | |
| * vertical glyph metrics. A {@code 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 {@code 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):<br> | |
| * <blockquote> | |
| * 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 | |
| * </blockquote> | |
| */ | |
| 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<GlyphStrike[]> 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. | |
| // do we want the CompFont from CFont here ? | |
| Font2D f2d = sgv.font2D; | |
| if (f2d instanceof FontSubstitution) { | |
| f2d = ((FontSubstitution)f2d).getCompositeFont2D(); | |
| } | |
| FontStrike strike = f2d.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; | |
| } | |
| } | |
| } |