Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/font/StandardGlyphVector.java
38829 views
/*1* Copyright (c) 1998, 2012, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.font;2627import java.awt.Font;28import java.awt.Graphics2D;29import java.awt.Point;30import java.awt.Rectangle;31import static java.awt.RenderingHints.*;32import java.awt.Shape;33import java.awt.font.FontRenderContext;34import java.awt.font.GlyphMetrics;35import java.awt.font.GlyphJustificationInfo;36import java.awt.font.GlyphVector;37import java.awt.font.LineMetrics;38import java.awt.font.TextAttribute;39import java.awt.geom.AffineTransform;40import java.awt.geom.GeneralPath;41import java.awt.geom.NoninvertibleTransformException;42import java.awt.geom.PathIterator;43import java.awt.geom.Point2D;44import java.awt.geom.Rectangle2D;45import java.lang.ref.SoftReference;46import java.text.CharacterIterator;4748import sun.awt.SunHints;49import sun.java2d.loops.FontInfo;5051/**52* Standard implementation of GlyphVector used by Font, GlyphList, and53* SunGraphics2D.54*55* The main issues involve the semantics of the various transforms56* (font, glyph, device) and their effect on rendering and metrics.57*58* Very, very unfortunately, the translation component of the font59* transform affects where the text gets rendered. It offsets the60* rendering origin. None of the other metrics of the glyphvector61* are affected, making them inconsistent with the rendering behavior.62* I think the translation component of the font would be better63* interpreted as the translation component of a per-glyph transform,64* but I don't know if this is possible to change.65*66* After the font transform is applied, the glyph transform is67* applied. This makes glyph transforms relative to font transforms,68* if the font transform changes, the glyph transform will have the69* same (relative) effect on the outline of the glyph. The outline70* and logical bounds are passed through the glyph transform before71* being returned. The glyph metrics ignore the glyph transform, but72* provide the outline bounds and the advance vector of the glyph (the73* latter will be rotated if the font is rotated). The default layout74* places each glyph at the end of the advance vector of the previous75* glyph, and since the glyph transform translates the advance vector,76* this means a glyph transform affects the positions of all77* subsequent glyphs if defaultLayout is called after setting a glyph78* transform. In the glyph info array, the bounds are the outline79* bounds including the glyph transform, and the positions are as80* computed, and the advances are the deltas between the positions.81*82* (There's a bug in the logical bounds of a rotated glyph for83* composite fonts, it's not to spec (in 1.4.0, 1.4.1, 1.4.2). The84* problem is that the rotated composite doesn't handle the multiple85* ascents and descents properly in both x and y. You end up with86* a rotated advance vector but an unrotated ascent and descent.)87*88* Finally, the whole thing is transformed by the device transform to89* position it on the page.90*91* Another bug: The glyph outline seems to ignore fractional point92* size information, but the images (and advances) don't ignore it.93*94* Small fonts drawn at large magnification have odd advances when95* fractional metrics is off-- that's because the advances depend on96* the frc. When the frc is scaled appropriately, the advances are97* fine. FM or a large frc (high numbers) make the advances right.98*99* The buffer aa flag doesn't affect rendering, the glyph vector100* renders as AA if aa is set in its frc, and as non-aa if aa is not101* set in its frc.102*103* font rotation, baseline, vertical etc.104*105* Font rotation and baseline Line metrics should be measured along a106* unit vector pi/4 cc from the baseline vector. For 'horizontal'107* fonts the baseline vector is the x vector passed through the font108* transform (ignoring translation), for 'vertical' it is the y109* vector. This definition makes ascent, descent, etc independent of110* shear, so shearing can be used to simulate italic. This means no111* fonts have 'negative ascents' or 'zero ascents' etc.112*113* Having a coordinate system with orthogonal axes where one is114* parallel to the baseline means we could use rectangles and interpret115* them in terms of this coordinate system. Unfortunately there116* is support for rotated fonts in the jdk already so maintaining117* the semantics of existing code (getlogical bounds, etc) might118* be difficult.119*120* A font transform transforms both the baseline and all the glyphs121* in the font, so it does not rotate the glyph w.r.t the baseline.122* If you do want to rotate individual glyphs, you need to apply a123* glyph transform. If performDefaultLayout is called after this,124* the transformed glyph advances will affect the glyph positions.125*126* useful additions127* - select vertical metrics - glyphs are rotated pi/4 cc and vertical128* metrics are used to align them to the baseline.129* - define baseline for font (glyph rotation not linked to baseline)130* - define extra space (delta between each glyph along baseline)131* - define offset (delta from 'true' baseline, impacts ascent and132* descent as these are still computed from true basline and pinned133* to zero, used in superscript).134*/135public class StandardGlyphVector extends GlyphVector {136private Font font;137private FontRenderContext frc;138private int[] glyphs; // always139private int[] userGlyphs; // used to return glyphs to the client.140private float[] positions; // only if not default advances141private int[] charIndices; // only if interesting142private int flags; // indicates whether positions, charIndices is interesting143144private static final int UNINITIALIZED_FLAGS = -1;145146// transforms information147private GlyphTransformInfo gti; // information about per-glyph transforms148149// !!! can we get rid of any of this extra stuff?150private AffineTransform ftx; // font transform without translation151private AffineTransform dtx; // device transform used for strike calculations, no translation152private AffineTransform invdtx; // inverse of dtx or null if dtx is identity153private AffineTransform frctx; // font render context transform, wish we could just share it154private Font2D font2D; // basic strike-independent stuff155private SoftReference fsref; // font strike reference for glyphs with no per-glyph transform156157/////////////////////////////158// Constructors and Factory methods159/////////////////////////////160161public StandardGlyphVector(Font font, String str, FontRenderContext frc) {162init(font, str.toCharArray(), 0, str.length(), frc, UNINITIALIZED_FLAGS);163}164165public StandardGlyphVector(Font font, char[] text, FontRenderContext frc) {166init(font, text, 0, text.length, frc, UNINITIALIZED_FLAGS);167}168169public StandardGlyphVector(Font font, char[] text, int start, int count,170FontRenderContext frc) {171init(font, text, start, count, frc, UNINITIALIZED_FLAGS);172}173174private float getTracking(Font font) {175if (font.hasLayoutAttributes()) {176AttributeValues values = ((AttributeMap)font.getAttributes()).getValues();177return values.getTracking();178}179return 0;180}181182// used by GlyphLayout to construct a glyphvector183public StandardGlyphVector(Font font, FontRenderContext frc, int[] glyphs, float[] positions,184int[] indices, int flags) {185initGlyphVector(font, frc, glyphs, positions, indices, flags);186187// this code should go into layout188float track = getTracking(font);189if (track != 0) {190track *= font.getSize2D();191Point2D.Float trackPt = new Point2D.Float(track, 0); // advance delta192if (font.isTransformed()) {193AffineTransform at = font.getTransform();194at.deltaTransform(trackPt, trackPt);195}196197// how do we know its a base glyph198// for now, it is if the natural advance of the glyph is non-zero199Font2D f2d = FontUtilities.getFont2D(font);200FontStrike strike = f2d.getStrike(font, frc);201202float[] deltas = { trackPt.x, trackPt.y };203for (int j = 0; j < deltas.length; ++j) {204float inc = deltas[j];205if (inc != 0) {206float delta = 0;207for (int i = j, n = 0; n < glyphs.length; i += 2) {208if (strike.getGlyphAdvance(glyphs[n++]) != 0) { // might be an inadequate test209positions[i] += delta;210delta += inc;211}212}213positions[positions.length-2+j] += delta;214}215}216}217}218219public void initGlyphVector(Font font, FontRenderContext frc, int[] glyphs, float[] positions,220int[] indices, int flags) {221this.font = font;222this.frc = frc;223this.glyphs = glyphs;224this.userGlyphs = glyphs; // no need to check225this.positions = positions;226this.charIndices = indices;227this.flags = flags;228229initFontData();230}231232public StandardGlyphVector(Font font, CharacterIterator iter, FontRenderContext frc) {233int offset = iter.getBeginIndex();234char[] text = new char [iter.getEndIndex() - offset];235for(char c = iter.first();236c != CharacterIterator.DONE;237c = iter.next()) {238text[iter.getIndex() - offset] = c;239}240init(font, text, 0, text.length, frc, UNINITIALIZED_FLAGS);241}242243public StandardGlyphVector(Font font, int[] glyphs, FontRenderContext frc) {244// !!! find callers of this245// should be able to fully init from raw data, e.g. charmap, flags too.246this.font = font;247this.frc = frc;248this.flags = UNINITIALIZED_FLAGS;249250initFontData();251this.userGlyphs = glyphs;252this.glyphs = getValidatedGlyphs(this.userGlyphs);253}254255/* This is called from the rendering loop. FontInfo is supplied256* because a GV caches a strike and glyph images suitable for its FRC.257* LCD text isn't currently supported on all surfaces, in which case258* standard AA must be used. This is most likely to occur when LCD text259* is requested and the surface is some non-standard type or hardward260* surface for which there are no accelerated loops.261* We can detect this as being AA=="ON" in the FontInfo and AA!="ON"262* and AA!="GASP" in the FRC - since this only occurs for LCD text we don't263* need to check any more precisely what value is in the FRC.264*/265public static StandardGlyphVector getStandardGV(GlyphVector gv,266FontInfo info) {267if (info.aaHint == SunHints.INTVAL_TEXT_ANTIALIAS_ON) {268Object aaHint = gv.getFontRenderContext().getAntiAliasingHint();269if (aaHint != VALUE_TEXT_ANTIALIAS_ON &&270aaHint != VALUE_TEXT_ANTIALIAS_GASP) {271/* We need to create a new GV with AA==ON for rendering */272FontRenderContext frc = gv.getFontRenderContext();273frc = new FontRenderContext(frc.getTransform(),274VALUE_TEXT_ANTIALIAS_ON,275frc.getFractionalMetricsHint());276return new StandardGlyphVector(gv, frc);277}278}279if (gv instanceof StandardGlyphVector) {280return (StandardGlyphVector)gv;281}282return new StandardGlyphVector(gv, gv.getFontRenderContext());283}284285/////////////////////////////286// GlyphVector API287/////////////////////////////288289public Font getFont() {290return this.font;291}292293public FontRenderContext getFontRenderContext() {294return this.frc;295}296297public void performDefaultLayout() {298positions = null;299if (getTracking(font) == 0) {300clearFlags(FLAG_HAS_POSITION_ADJUSTMENTS);301}302}303304public int getNumGlyphs() {305return glyphs.length;306}307308public int getGlyphCode(int glyphIndex) {309return userGlyphs[glyphIndex];310}311312public int[] getGlyphCodes(int start, int count, int[] result) {313if (count < 0) {314throw new IllegalArgumentException("count = " + count);315}316if (start < 0) {317throw new IndexOutOfBoundsException("start = " + start);318}319if (start > glyphs.length - count) { // watch out for overflow if index + count overlarge320throw new IndexOutOfBoundsException("start + count = " + (start + count));321}322323if (result == null) {324result = new int[count];325}326327// if arraycopy were faster, we wouldn't code this328for (int i = 0; i < count; ++i) {329result[i] = userGlyphs[i + start];330}331332return result;333}334335public int getGlyphCharIndex(int ix) {336if (ix < 0 && ix >= glyphs.length) {337throw new IndexOutOfBoundsException("" + ix);338}339if (charIndices == null) {340if ((getLayoutFlags() & FLAG_RUN_RTL) != 0) {341return glyphs.length - 1 - ix;342}343return ix;344}345return charIndices[ix];346}347348public int[] getGlyphCharIndices(int start, int count, int[] result) {349if (start < 0 || count < 0 || (count > glyphs.length - start)) {350throw new IndexOutOfBoundsException("" + start + ", " + count);351}352if (result == null) {353result = new int[count];354}355if (charIndices == null) {356if ((getLayoutFlags() & FLAG_RUN_RTL) != 0) {357for (int i = 0, n = glyphs.length - 1 - start;358i < count; ++i, --n) {359result[i] = n;360}361} else {362for (int i = 0, n = start; i < count; ++i, ++n) {363result[i] = n;364}365}366} else {367for (int i = 0; i < count; ++i) {368result[i] = charIndices[i + start];369}370}371return result;372}373374// !!! not cached, assume TextLayout will cache if necessary375// !!! reexamine for per-glyph-transforms376// !!! revisit for text-on-a-path, vertical377public Rectangle2D getLogicalBounds() {378setFRCTX();379initPositions();380381LineMetrics lm = font.getLineMetrics("", frc);382383float minX, minY, maxX, maxY;384// horiz only for now...385minX = 0;386minY = -lm.getAscent();387maxX = 0;388maxY = lm.getDescent() + lm.getLeading();389if (glyphs.length > 0) {390maxX = positions[positions.length - 2];391}392393return new Rectangle2D.Float(minX, minY, maxX - minX, maxY - minY);394}395396// !!! not cached, assume TextLayout will cache if necessary397public Rectangle2D getVisualBounds() {398Rectangle2D result = null;399for (int i = 0; i < glyphs.length; ++i) {400Rectangle2D glyphVB = getGlyphVisualBounds(i).getBounds2D();401if (!glyphVB.isEmpty()) {402if (result == null) {403result = glyphVB;404} else {405Rectangle2D.union(result, glyphVB, result);406}407}408}409if (result == null) {410result = new Rectangle2D.Float(0, 0, 0, 0);411}412return result;413}414415// !!! not cached, assume TextLayout will cache if necessary416// !!! fontStrike needs a method for this417public Rectangle getPixelBounds(FontRenderContext renderFRC, float x, float y) {418return getGlyphsPixelBounds(renderFRC, x, y, 0, glyphs.length);419}420421public Shape getOutline() {422return getGlyphsOutline(0, glyphs.length, 0, 0);423}424425public Shape getOutline(float x, float y) {426return getGlyphsOutline(0, glyphs.length, x, y);427}428429// relative to gv origin430public Shape getGlyphOutline(int ix) {431return getGlyphsOutline(ix, 1, 0, 0);432}433434// relative to gv origin offset by x, y435public Shape getGlyphOutline(int ix, float x, float y) {436return getGlyphsOutline(ix, 1, x, y);437}438439public Point2D getGlyphPosition(int ix) {440initPositions();441442ix *= 2;443return new Point2D.Float(positions[ix], positions[ix + 1]);444}445446public void setGlyphPosition(int ix, Point2D pos) {447initPositions();448449int ix2 = ix << 1;450positions[ix2] = (float)pos.getX();451positions[ix2 + 1] = (float)pos.getY();452453clearCaches(ix);454addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);455}456457public AffineTransform getGlyphTransform(int ix) {458if (ix < 0 || ix >= glyphs.length) {459throw new IndexOutOfBoundsException("ix = " + ix);460}461if (gti != null) {462return gti.getGlyphTransform(ix);463}464return null; // spec'd as returning null465}466467public void setGlyphTransform(int ix, AffineTransform newTX) {468if (ix < 0 || ix >= glyphs.length) {469throw new IndexOutOfBoundsException("ix = " + ix);470}471472if (gti == null) {473if (newTX == null || newTX.isIdentity()) {474return;475}476gti = new GlyphTransformInfo(this);477}478gti.setGlyphTransform(ix, newTX); // sets flags479if (gti.transformCount() == 0) {480gti = null;481}482}483484public int getLayoutFlags() {485if (flags == UNINITIALIZED_FLAGS) {486flags = 0;487488if (charIndices != null && glyphs.length > 1) {489boolean ltr = true;490boolean rtl = true;491492int rtlix = charIndices.length; // rtl index493for (int i = 0; i < charIndices.length && (ltr || rtl); ++i) {494int cx = charIndices[i];495496ltr = ltr && (cx == i);497rtl = rtl && (cx == --rtlix);498}499500if (rtl) flags |= FLAG_RUN_RTL;501if (!rtl && !ltr) flags |= FLAG_COMPLEX_GLYPHS;502}503}504505return flags;506}507508public float[] getGlyphPositions(int start, int count, float[] result) {509if (count < 0) {510throw new IllegalArgumentException("count = " + count);511}512if (start < 0) {513throw new IndexOutOfBoundsException("start = " + start);514}515if (start > glyphs.length + 1 - count) { // watch for overflow516throw new IndexOutOfBoundsException("start + count = " + (start + count));517}518519return internalGetGlyphPositions(start, count, 0, result);520}521522public Shape getGlyphLogicalBounds(int ix) {523if (ix < 0 || ix >= glyphs.length) {524throw new IndexOutOfBoundsException("ix = " + ix);525}526527Shape[] lbcache;528if (lbcacheRef == null || (lbcache = (Shape[])lbcacheRef.get()) == null) {529lbcache = new Shape[glyphs.length];530lbcacheRef = new SoftReference(lbcache);531}532533Shape result = lbcache[ix];534if (result == null) {535setFRCTX();536initPositions();537538// !!! ought to return a rectangle2d for simple cases, though the following works for all539540// get the position, the tx offset, and the x,y advance and x,y adl. The541// shape is the box formed by adv (width) and adl (height) offset by542// the position plus the tx offset minus the ascent.543544ADL adl = new ADL();545GlyphStrike gs = getGlyphStrike(ix);546gs.getADL(adl);547548Point2D.Float adv = gs.strike.getGlyphMetrics(glyphs[ix]);549550float wx = adv.x;551float wy = adv.y;552float hx = adl.descentX + adl.leadingX + adl.ascentX;553float hy = adl.descentY + adl.leadingY + adl.ascentY;554float x = positions[ix*2] + gs.dx - adl.ascentX;555float y = positions[ix*2+1] + gs.dy - adl.ascentY;556557GeneralPath gp = new GeneralPath();558gp.moveTo(x, y);559gp.lineTo(x + wx, y + wy);560gp.lineTo(x + wx + hx, y + wy + hy);561gp.lineTo(x + hx, y + hy);562gp.closePath();563564result = new DelegatingShape(gp);565lbcache[ix] = result;566}567568return result;569}570private SoftReference lbcacheRef;571572public Shape getGlyphVisualBounds(int ix) {573if (ix < 0 || ix >= glyphs.length) {574throw new IndexOutOfBoundsException("ix = " + ix);575}576577Shape[] vbcache;578if (vbcacheRef == null || (vbcache = (Shape[])vbcacheRef.get()) == null) {579vbcache = new Shape[glyphs.length];580vbcacheRef = new SoftReference(vbcache);581}582583Shape result = vbcache[ix];584if (result == null) {585result = new DelegatingShape(getGlyphOutlineBounds(ix));586vbcache[ix] = result;587}588589return result;590}591private SoftReference vbcacheRef;592593public Rectangle getGlyphPixelBounds(int index, FontRenderContext renderFRC, float x, float y) {594return getGlyphsPixelBounds(renderFRC, x, y, index, 1);595}596597public GlyphMetrics getGlyphMetrics(int ix) {598if (ix < 0 || ix >= glyphs.length) {599throw new IndexOutOfBoundsException("ix = " + ix);600}601602Rectangle2D vb = getGlyphVisualBounds(ix).getBounds2D();603Point2D pt = getGlyphPosition(ix);604vb.setRect(vb.getMinX() - pt.getX(),605vb.getMinY() - pt.getY(),606vb.getWidth(),607vb.getHeight());608Point2D.Float adv =609getGlyphStrike(ix).strike.getGlyphMetrics(glyphs[ix]);610GlyphMetrics gm = new GlyphMetrics(true, adv.x, adv.y,611vb,612GlyphMetrics.STANDARD);613return gm;614}615616public GlyphJustificationInfo getGlyphJustificationInfo(int ix) {617if (ix < 0 || ix >= glyphs.length) {618throw new IndexOutOfBoundsException("ix = " + ix);619}620621// currently we don't have enough information to do this right. should622// get info from the font and use real OT/GX justification. Right now623// sun/font/ExtendedTextSourceLabel assigns one of three infos624// based on whether the char is kanji, space, or other.625626return null;627}628629public boolean equals(GlyphVector rhs) {630if (this == rhs) {631return true;632}633if (rhs == null) {634return false;635}636637try {638StandardGlyphVector other = (StandardGlyphVector)rhs;639640if (glyphs.length != other.glyphs.length) {641return false;642}643644for (int i = 0; i < glyphs.length; ++i) {645if (glyphs[i] != other.glyphs[i]) {646return false;647}648}649650if (!font.equals(other.font)) {651return false;652}653654if (!frc.equals(other.frc)) {655return false;656}657658if ((other.positions == null) != (positions == null)) {659if (positions == null) {660initPositions();661} else {662other.initPositions();663}664}665666if (positions != null) {667for (int i = 0; i < positions.length; ++i) {668if (positions[i] != other.positions[i]) {669return false;670}671}672}673674if (gti == null) {675return other.gti == null;676} else {677return gti.equals(other.gti);678}679}680catch (ClassCastException e) {681// assume they are different simply by virtue of the class difference682683return false;684}685}686687/**688* As a concrete subclass of Object that implements equality, this must689* implement hashCode.690*/691public int hashCode() {692return font.hashCode() ^ glyphs.length;693}694695/**696* Since we implement equality comparisons for GlyphVector, we implement697* the inherited Object.equals(Object) as well. GlyphVector should do698* this, and define two glyphvectors as not equal if the classes differ.699*/700public boolean equals(Object rhs) {701try {702return equals((GlyphVector)rhs);703}704catch (ClassCastException e) {705return false;706}707}708709/**710* Sometimes I wish java had covariant return types...711*/712public StandardGlyphVector copy() {713return (StandardGlyphVector)clone();714}715716/**717* As a concrete subclass of GlyphVector, this must implement clone.718*/719public Object clone() {720// positions, gti are mutable so we have to clone them721// font2d can be shared722// fsref is a cache and can be shared723try {724StandardGlyphVector result = (StandardGlyphVector)super.clone();725726result.clearCaches();727728if (positions != null) {729result.positions = (float[])positions.clone();730}731732if (gti != null) {733result.gti = new GlyphTransformInfo(result, gti);734}735736return result;737}738catch (CloneNotSupportedException e) {739}740741return this;742}743744//////////////////////745// StandardGlyphVector new public methods746/////////////////////747748/*749* Set a multiple glyph positions at one time. GlyphVector only750* provides API to set a single glyph at a time.751*/752public void setGlyphPositions(float[] srcPositions, int srcStart,753int start, int count) {754if (count < 0) {755throw new IllegalArgumentException("count = " + count);756}757758initPositions();759for (int i = start * 2, e = i + count * 2, p = srcStart; i < e; ++i, ++p) {760positions[i] = srcPositions[p];761}762763clearCaches();764addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);765}766767/**768* Set all the glyph positions, including the 'after last glyph' position.769* The srcPositions array must be of length (numGlyphs + 1) * 2.770*/771public void setGlyphPositions(float[] srcPositions) {772int requiredLength = glyphs.length * 2 + 2;773if (srcPositions.length != requiredLength) {774throw new IllegalArgumentException("srcPositions.length != " + requiredLength);775}776777positions = (float[])srcPositions.clone();778779clearCaches();780addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);781}782783/**784* This is a convenience overload that gets all the glyph positions, which785* is what you usually want to do if you're getting more than one.786* !!! should I bother taking result parameter?787*/788public float[] getGlyphPositions(float[] result) {789return internalGetGlyphPositions(0, glyphs.length + 1, 0, result);790}791792/**793* Get transform information for the requested range of glyphs.794* If no glyphs have a transform, return null.795* If a glyph has no transform (or is the identity transform) its entry in the result array will be null.796* If the passed-in result is null an array will be allocated for the caller.797* Each transform instance in the result array will unique, and independent of the GlyphVector's transform.798*/799public AffineTransform[] getGlyphTransforms(int start, int count, AffineTransform[] result) {800if (start < 0 || count < 0 || start + count > glyphs.length) {801throw new IllegalArgumentException("start: " + start + " count: " + count);802}803804if (gti == null) {805return null;806}807808if (result == null) {809result = new AffineTransform[count];810}811812for (int i = 0; i < count; ++i, ++start) {813result[i] = gti.getGlyphTransform(start);814}815816return result;817}818819/**820* Convenience overload for getGlyphTransforms(int, int, AffineTransform[], int);821*/822public AffineTransform[] getGlyphTransforms() {823return getGlyphTransforms(0, glyphs.length, null);824}825826/**827* Set a number of glyph transforms.828* Original transforms are unchanged. The array may contain nulls, and also may829* contain multiple references to the same transform instance.830*/831public void setGlyphTransforms(AffineTransform[] srcTransforms, int srcStart, int start, int count) {832for (int i = start, e = start + count; i < e; ++i) {833setGlyphTransform(i, srcTransforms[srcStart + i]);834}835}836837/**838* Convenience overload of setGlyphTransforms(AffineTransform[], int, int, int).839*/840public void setGlyphTransforms(AffineTransform[] srcTransforms) {841setGlyphTransforms(srcTransforms, 0, 0, glyphs.length);842}843844/**845* For each glyph return posx, posy, advx, advy, visx, visy, visw, vish.846*/847public float[] getGlyphInfo() {848setFRCTX();849initPositions();850float[] result = new float[glyphs.length * 8];851for (int i = 0, n = 0; i < glyphs.length; ++i, n += 8) {852float x = positions[i*2];853float y = positions[i*2+1];854result[n] = x;855result[n+1] = y;856857int glyphID = glyphs[i];858GlyphStrike s = getGlyphStrike(i);859Point2D.Float adv = s.strike.getGlyphMetrics(glyphID);860result[n+2] = adv.x;861result[n+3] = adv.y;862863Rectangle2D vb = getGlyphVisualBounds(i).getBounds2D();864result[n+4] = (float)(vb.getMinX());865result[n+5] = (float)(vb.getMinY());866result[n+6] = (float)(vb.getWidth());867result[n+7] = (float)(vb.getHeight());868}869return result;870}871872/**873* !!! not used currently, but might be by getPixelbounds?874*/875public void pixellate(FontRenderContext renderFRC, Point2D loc, Point pxResult) {876if (renderFRC == null) {877renderFRC = frc;878}879880// it is a total pain that you have to copy the transform.881882AffineTransform at = renderFRC.getTransform();883at.transform(loc, loc);884pxResult.x = (int)loc.getX(); // but must not behave oddly around zero885pxResult.y = (int)loc.getY();886loc.setLocation(pxResult.x, pxResult.y);887try {888at.inverseTransform(loc, loc);889}890catch (NoninvertibleTransformException e) {891throw new IllegalArgumentException("must be able to invert frc transform");892}893}894895//////////////////////896// StandardGlyphVector package private methods897/////////////////////898899// used by glyphlist to determine if it needs to allocate/size positions array900// gti always uses positions because the gtx might have translation. We also901// need positions if the rendering dtx is different from the frctx.902903boolean needsPositions(double[] devTX) {904return gti != null ||905(getLayoutFlags() & FLAG_HAS_POSITION_ADJUSTMENTS) != 0 ||906!matchTX(devTX, frctx);907}908909// used by glyphList to get strong refs to font strikes for duration of rendering call910// if devTX matches current devTX, we're ready to go911// if we don't have multiple transforms, we're already ok912913// !!! I'm not sure fontInfo works so well for glyphvector, since we have to be able to handle914// the multiple-strikes case915916/*917* GlyphList calls this to set up its images data. First it calls needsPositions,918* passing the devTX, to see if it should provide us a positions array to fill.919* It only doesn't need them if we're a simple glyph vector whose frctx matches the920* devtx.921* Then it calls setupGlyphImages. If we need positions, we make sure we have our922* default positions based on the frctx first. Then we set the devTX, and use923* strikes based on it to generate the images. Finally, we fill in the positions924* array.925* If we have transforms, we delegate to gti. It depends on our having first926* initialized the positions and devTX.927*/928Object setupGlyphImages(long[] images, float[] positions, double[] devTX) {929initPositions(); // FIRST ensure we have positions based on our frctx930setRenderTransform(devTX); // THEN make sure we are using the desired devTX931932if (gti != null) {933return gti.setupGlyphImages(images, positions, dtx);934}935936GlyphStrike gs = getDefaultStrike();937gs.strike.getGlyphImagePtrs(glyphs, images, glyphs.length);938939if (positions != null) {940if (dtx.isIdentity()) {941System.arraycopy(this.positions, 0, positions, 0, glyphs.length * 2);942} else {943dtx.transform(this.positions, 0, positions, 0, glyphs.length);944}945}946947return gs;948}949950//////////////////////951// StandardGlyphVector private methods952/////////////////////953954// We keep translation in our frctx since getPixelBounds uses it. But955// GlyphList pulls out the translation and applies it separately, so956// we strip it out when we set the dtx. Basically nothing uses the957// translation except getPixelBounds.958959// called by needsPositions, setRenderTransform960private static boolean matchTX(double[] lhs, AffineTransform rhs) {961return962lhs[0] == rhs.getScaleX() &&963lhs[1] == rhs.getShearY() &&964lhs[2] == rhs.getShearX() &&965lhs[3] == rhs.getScaleY();966}967968// returns new tx if old one has translation, otherwise returns old one969private static AffineTransform getNonTranslateTX(AffineTransform tx) {970if (tx.getTranslateX() != 0 || tx.getTranslateY() != 0) {971tx = new AffineTransform(tx.getScaleX(), tx.getShearY(),972tx.getShearX(), tx.getScaleY(),9730, 0);974}975return tx;976}977978private static boolean equalNonTranslateTX(AffineTransform lhs, AffineTransform rhs) {979return lhs.getScaleX() == rhs.getScaleX() &&980lhs.getShearY() == rhs.getShearY() &&981lhs.getShearX() == rhs.getShearX() &&982lhs.getScaleY() == rhs.getScaleY();983}984985// called by setupGlyphImages (after needsPositions, so redundant match check?)986private void setRenderTransform(double[] devTX) {987assert(devTX.length == 4);988if (!matchTX(devTX, dtx)) {989resetDTX(new AffineTransform(devTX)); // no translation since devTX len == 4.990}991}992993// called by getGlyphsPixelBounds994private final void setDTX(AffineTransform tx) {995if (!equalNonTranslateTX(dtx, tx)) {996resetDTX(getNonTranslateTX(tx));997}998}9991000// called by most functions1001private final void setFRCTX() {1002if (!equalNonTranslateTX(frctx, dtx)) {1003resetDTX(getNonTranslateTX(frctx));1004}1005}10061007/**1008* Change the dtx for the strike refs we use. Keeps a reference to the at. At1009* must not contain translation.1010* Called by setRenderTransform, setDTX, initFontData.1011*/1012private final void resetDTX(AffineTransform at) {1013fsref = null;1014dtx = at;1015invdtx = null;1016if (!dtx.isIdentity()) {1017try {1018invdtx = dtx.createInverse();1019}1020catch (NoninvertibleTransformException e) {1021// we needn't care for rendering1022}1023}1024if (gti != null) {1025gti.strikesRef = null;1026}1027}10281029/**1030* Utility used by getStandardGV.1031* Constructs a StandardGlyphVector from a generic glyph vector.1032* Do not call this from new contexts without considering the comment1033* about "userGlyphs".1034*/1035private StandardGlyphVector(GlyphVector gv, FontRenderContext frc) {1036this.font = gv.getFont();1037this.frc = frc;1038initFontData();10391040int nGlyphs = gv.getNumGlyphs();1041this.userGlyphs = gv.getGlyphCodes(0, nGlyphs, null);1042if (gv instanceof StandardGlyphVector) {1043/* userGlyphs will be OK because this is a private constructor1044* and the returned instance is used only for rendering.1045* It's not constructable by user code, nor returned to the1046* application. So we know "userGlyphs" are valid as having1047* been either already validated or are the result of layout.1048*/1049this.glyphs = userGlyphs;1050} else {1051this.glyphs = getValidatedGlyphs(this.userGlyphs);1052}1053this.flags = gv.getLayoutFlags() & FLAG_MASK;10541055if ((flags & FLAG_HAS_POSITION_ADJUSTMENTS) != 0) {1056this.positions = gv.getGlyphPositions(0, nGlyphs + 1, null);1057}10581059if ((flags & FLAG_COMPLEX_GLYPHS) != 0) {1060this.charIndices = gv.getGlyphCharIndices(0, nGlyphs, null);1061}10621063if ((flags & FLAG_HAS_TRANSFORMS) != 0) {1064AffineTransform[] txs = new AffineTransform[nGlyphs]; // worst case1065for (int i = 0; i < nGlyphs; ++i) {1066txs[i] = gv.getGlyphTransform(i); // gv doesn't have getGlyphsTransforms1067}10681069setGlyphTransforms(txs);1070}1071}10721073/* Before asking the Font we see if the glyph code is1074* FFFE or FFFF which are special values that we should be internally1075* ready to handle as meaning invisible glyphs. The Font would report1076* those as the missing glyph.1077*/1078int[] getValidatedGlyphs(int[] oglyphs) {1079int len = oglyphs.length;1080int[] vglyphs = new int[len];1081for (int i=0; i<len; i++) {1082if (oglyphs[i] == 0xFFFE || oglyphs[i] == 0xFFFF) {1083vglyphs[i] = oglyphs[i];1084} else {1085vglyphs[i] = font2D.getValidatedGlyphCode(oglyphs[i]);1086}1087}1088return vglyphs;1089}10901091// utility used by constructors1092private void init(Font font, char[] text, int start, int count,1093FontRenderContext frc, int flags) {10941095if (start < 0 || count < 0 || start + count > text.length) {1096throw new ArrayIndexOutOfBoundsException("start or count out of bounds");1097}10981099this.font = font;1100this.frc = frc;1101this.flags = flags;11021103if (getTracking(font) != 0) {1104addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);1105}11061107// !!! change mapper interface?1108if (start != 0) {1109char[] temp = new char[count];1110System.arraycopy(text, start, temp, 0, count);1111text = temp;1112}11131114initFontData(); // sets up font2D11151116// !!! no layout for now, should add checks1117// !!! need to support creating a StandardGlyphVector from a TextMeasurer's info...1118glyphs = new int[count]; // hmmm1119/* Glyphs obtained here are already validated by the font */1120userGlyphs = glyphs;1121font2D.getMapper().charsToGlyphs(count, text, glyphs);1122}11231124private void initFontData() {1125font2D = FontUtilities.getFont2D(font);1126if (font2D instanceof FontSubstitution) {1127font2D = ((FontSubstitution)font2D).getCompositeFont2D();1128}1129float s = font.getSize2D();1130if (font.isTransformed()) {1131ftx = font.getTransform();1132if (ftx.getTranslateX() != 0 || ftx.getTranslateY() != 0) {1133addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);1134}1135ftx.setTransform(ftx.getScaleX(), ftx.getShearY(), ftx.getShearX(), ftx.getScaleY(), 0, 0);1136ftx.scale(s, s);1137} else {1138ftx = AffineTransform.getScaleInstance(s, s);1139}11401141frctx = frc.getTransform();1142resetDTX(getNonTranslateTX(frctx));1143}11441145/**1146* Copy glyph position data into a result array starting at the indicated1147* offset in the array. If the passed-in result array is null, a new1148* array will be allocated and returned.1149*1150* This is an internal method and does no extra argument checking.1151*1152* @param start the index of the first glyph to get1153* @param count the number of glyphs to get1154* @param offset the offset into result at which to put the data1155* @param result an array to hold the x,y positions1156* @return the modified position array1157*/1158private float[] internalGetGlyphPositions(int start, int count, int offset, float[] result) {1159if (result == null) {1160result = new float[offset + count * 2];1161}11621163initPositions();11641165// System.arraycopy is slow for stuff like this1166for (int i = offset, e = offset + count * 2, p = start * 2; i < e; ++i, ++p) {1167result[i] = positions[p];1168}11691170return result;1171}11721173private Rectangle2D getGlyphOutlineBounds(int ix) {1174setFRCTX();1175initPositions();1176return getGlyphStrike(ix).getGlyphOutlineBounds(glyphs[ix], positions[ix*2], positions[ix*2+1]);1177}11781179/**1180* Used by getOutline, getGlyphsOutline1181*/1182private Shape getGlyphsOutline(int start, int count, float x, float y) {1183setFRCTX();1184initPositions();11851186GeneralPath result = new GeneralPath(GeneralPath.WIND_NON_ZERO);1187for (int i = start, e = start + count, n = start * 2; i < e; ++i, n += 2) {1188float px = x + positions[n];1189float py = y + positions[n+1];11901191getGlyphStrike(i).appendGlyphOutline(glyphs[i], result, px, py);1192}11931194return result;1195}11961197private Rectangle getGlyphsPixelBounds(FontRenderContext frc, float x, float y, int start, int count) {1198initPositions(); // FIRST ensure we have positions based on our frctx11991200AffineTransform tx = null;1201if (frc == null || frc.equals(this.frc)) {1202tx = frctx;1203} else {1204tx = frc.getTransform();1205}1206setDTX(tx); // need to get the right strikes, but we use tx itself to translate the points12071208if (gti != null) {1209return gti.getGlyphsPixelBounds(tx, x, y, start, count);1210}12111212FontStrike fs = getDefaultStrike().strike;1213Rectangle result = null;1214Rectangle r = new Rectangle();1215Point2D.Float pt = new Point.Float();1216int n = start * 2;1217while (--count >= 0) {1218pt.x = x + positions[n++];1219pt.y = y + positions[n++];1220tx.transform(pt, pt);1221fs.getGlyphImageBounds(glyphs[start++], pt, r);1222if (!r.isEmpty()) {1223if (result == null) {1224result = new Rectangle(r);1225} else {1226result.add(r);1227}1228}1229}1230return result != null ? result : r;1231}12321233private void clearCaches(int ix) {1234if (lbcacheRef != null) {1235Shape[] lbcache = (Shape[])lbcacheRef.get();1236if (lbcache != null) {1237lbcache[ix] = null;1238}1239}12401241if (vbcacheRef != null) {1242Shape[] vbcache = (Shape[])vbcacheRef.get();1243if (vbcache != null) {1244vbcache[ix] = null;1245}1246}1247}12481249private void clearCaches() {1250lbcacheRef = null;1251vbcacheRef = null;1252}12531254// internal use only for possible future extension12551256/**1257* A flag used with getLayoutFlags that indicates whether this <code>GlyphVector</code> uses1258* a vertical baseline.1259*/1260public static final int FLAG_USES_VERTICAL_BASELINE = 128;12611262/**1263* A flag used with getLayoutFlags that indicates whether this <code>GlyphVector</code> uses1264* vertical glyph metrics. A <code>GlyphVector</code> can use vertical metrics on a1265* horizontal line, or vice versa.1266*/1267public static final int FLAG_USES_VERTICAL_METRICS = 256;12681269/**1270* A flag used with getLayoutFlags that indicates whether this <code>GlyphVector</code> uses1271* the 'alternate orientation.' Glyphs have a default orientation given a1272* particular baseline and metrics orientation, this is the orientation appropriate1273* for left-to-right text. For example, the letter 'A' can have four orientations,1274* with the point at 12, 3, 6, or 9 'o clock. The following table shows where the1275* point displays for different values of vertical baseline (vb), vertical1276* metrics (vm) and alternate orientation (fo):<br>1277* <blockquote>1278* vb vm ao1279* -- -- -- --1280* f f f 12 ^ horizontal metrics on horizontal lines1281* f f t 6 v1282* f t f 9 < vertical metrics on horizontal lines1283* f t t 3 >1284* t f f 3 > horizontal metrics on vertical lines1285* t f t 9 <1286* t t f 12 ^ vertical metrics on vertical lines1287* t t t 6 v1288* </blockquote>1289*/1290public static final int FLAG_USES_ALTERNATE_ORIENTATION = 512;129112921293/**1294* Ensure that the positions array exists and holds position data.1295* If the array is null, this allocates it and sets default positions.1296*/1297private void initPositions() {1298if (positions == null) {1299setFRCTX();13001301positions = new float[glyphs.length * 2 + 2];13021303Point2D.Float trackPt = null;1304float track = getTracking(font);1305if (track != 0) {1306track *= font.getSize2D();1307trackPt = new Point2D.Float(track, 0); // advance delta1308}13091310Point2D.Float pt = new Point2D.Float(0, 0);1311if (font.isTransformed()) {1312AffineTransform at = font.getTransform();1313at.transform(pt, pt);1314positions[0] = pt.x;1315positions[1] = pt.y;13161317if (trackPt != null) {1318at.deltaTransform(trackPt, trackPt);1319}1320}1321for (int i = 0, n = 2; i < glyphs.length; ++i, n += 2) {1322getGlyphStrike(i).addDefaultGlyphAdvance(glyphs[i], pt);1323if (trackPt != null) {1324pt.x += trackPt.x;1325pt.y += trackPt.y;1326}1327positions[n] = pt.x;1328positions[n+1] = pt.y;1329}1330}1331}13321333/**1334* OR newFlags with existing flags. First computes existing flags if needed.1335*/1336private void addFlags(int newflags) {1337flags = getLayoutFlags() | newflags;1338}13391340/**1341* AND the complement of clearedFlags with existing flags. First computes existing flags if needed.1342*/1343private void clearFlags(int clearedFlags) {1344flags = getLayoutFlags() & ~clearedFlags;1345}13461347// general utility methods13481349// encapsulate the test to check whether we have per-glyph transforms1350private GlyphStrike getGlyphStrike(int ix) {1351if (gti == null) {1352return getDefaultStrike();1353} else {1354return gti.getStrike(ix);1355}1356}13571358// encapsulate access to cached default glyph strike1359private GlyphStrike getDefaultStrike() {1360GlyphStrike gs = null;1361if (fsref != null) {1362gs = (GlyphStrike)fsref.get();1363}1364if (gs == null) {1365gs = GlyphStrike.create(this, dtx, null);1366fsref = new SoftReference(gs);1367}1368return gs;1369}137013711372/////////////////////1373// Internal utility classes1374/////////////////////13751376// !!! I have this as a separate class instead of just inside SGV,1377// but I previously didn't bother. Now I'm trying this again.1378// Probably still not worth it, but I'd like to keep sgv's small in the common case.13791380static final class GlyphTransformInfo {1381StandardGlyphVector sgv; // reference back to glyph vector - yuck1382int[] indices; // index into unique strikes1383double[] transforms; // six doubles per unique transform, because AT is a pain to manipulate1384SoftReference strikesRef; // ref to unique strikes, one per transform1385boolean haveAllStrikes; // true if the strike array has been filled by getStrikes().13861387// used when first setting a transform1388GlyphTransformInfo(StandardGlyphVector sgv) {1389this.sgv = sgv;1390}13911392// used when cloning a glyph vector, need to set back link1393GlyphTransformInfo(StandardGlyphVector sgv, GlyphTransformInfo rhs) {1394this.sgv = sgv;13951396this.indices = rhs.indices == null ? null : (int[])rhs.indices.clone();1397this.transforms = rhs.transforms == null ? null : (double[])rhs.transforms.clone();1398this.strikesRef = null; // can't share cache, so rather than clone, we just null out1399}14001401// used in sgv equality1402public boolean equals(GlyphTransformInfo rhs) {1403if (rhs == null) {1404return false;1405}1406if (rhs == this) {1407return true;1408}1409if (this.indices.length != rhs.indices.length) {1410return false;1411}1412if (this.transforms.length != rhs.transforms.length) {1413return false;1414}14151416// slow since we end up processing the same transforms multiple1417// times, but since transforms can be in any order, we either do1418// this or create a mapping. Equality tests aren't common so1419// leave it like this.1420for (int i = 0; i < this.indices.length; ++i) {1421int tix = this.indices[i];1422int rix = rhs.indices[i];1423if ((tix == 0) != (rix == 0)) {1424return false;1425}1426if (tix != 0) {1427tix *= 6;1428rix *= 6;1429for (int j = 6; j > 0; --j) {1430if (this.indices[--tix] != rhs.indices[--rix]) {1431return false;1432}1433}1434}1435}1436return true;1437}14381439// implements sgv.setGlyphTransform1440void setGlyphTransform(int glyphIndex, AffineTransform newTX) {14411442// we store all the glyph transforms as a double array, and for each glyph there1443// is an entry in the txIndices array indicating which transform to use. 0 means1444// there's no transform, 1 means use the first transform (the 6 doubles at offset1445// 0), 2 means use the second transform (the 6 doubles at offset 6), etc.1446//1447// Since this can be called multiple times, and since the number of transforms1448// affects the time it takes to construct the glyphs, we try to keep the arrays as1449// compact as possible, by removing transforms that are no longer used, and reusing1450// transforms where we already have them.14511452double[] temp = new double[6];1453boolean isIdentity = true;1454if (newTX == null || newTX.isIdentity()) {1455// Fill in temp1456temp[0] = temp[3] = 1.0;1457}1458else {1459isIdentity = false;1460newTX.getMatrix(temp);1461}14621463if (indices == null) {1464if (isIdentity) { // no change1465return;1466}14671468indices = new int[sgv.glyphs.length];1469indices[glyphIndex] = 1;1470transforms = temp;1471} else {1472boolean addSlot = false; // assume we're not growing1473int newIndex = -1;1474if (isIdentity) {1475newIndex = 0; // might shrink1476} else {1477addSlot = true; // assume no match1478int i;1479loop:1480for (i = 0; i < transforms.length; i += 6) {1481for (int j = 0; j < 6; ++j) {1482if (transforms[i + j] != temp[j]) {1483continue loop;1484}1485}1486addSlot = false;1487break;1488}1489newIndex = i / 6 + 1; // if no match, end of list1490}14911492// if we're using the same transform, nothing to do1493int oldIndex = indices[glyphIndex];1494if (newIndex != oldIndex) {1495// see if we are removing last use of the old slot1496boolean removeSlot = false;1497if (oldIndex != 0) {1498removeSlot = true;1499for (int i = 0; i < indices.length; ++i) {1500if (indices[i] == oldIndex && i != glyphIndex) {1501removeSlot = false;1502break;1503}1504}1505}15061507if (removeSlot && addSlot) { // reuse old slot with new transform1508newIndex = oldIndex;1509System.arraycopy(temp, 0, transforms, (newIndex - 1) * 6, 6);1510} else if (removeSlot) {1511if (transforms.length == 6) { // removing last one, so clear arrays1512indices = null;1513transforms = null;15141515sgv.clearCaches(glyphIndex);1516sgv.clearFlags(FLAG_HAS_TRANSFORMS);1517strikesRef = null;15181519return;1520}15211522double[] ttemp = new double[transforms.length - 6];1523System.arraycopy(transforms, 0, ttemp, 0, (oldIndex - 1) * 6);1524System.arraycopy(transforms, oldIndex * 6, ttemp, (oldIndex - 1) * 6,1525transforms.length - oldIndex * 6);1526transforms = ttemp;15271528// clean up indices1529for (int i = 0; i < indices.length; ++i) {1530if (indices[i] > oldIndex) { // ignore == oldIndex, it's going away1531indices[i] -= 1;1532}1533}1534if (newIndex > oldIndex) { // don't forget to decrement this too if we need to1535--newIndex;1536}1537} else if (addSlot) {1538double[] ttemp = new double[transforms.length + 6];1539System.arraycopy(transforms, 0, ttemp, 0, transforms.length);1540System.arraycopy(temp, 0, ttemp, transforms.length, 6);1541transforms = ttemp;1542}15431544indices[glyphIndex] = newIndex;1545}1546}15471548sgv.clearCaches(glyphIndex);1549sgv.addFlags(FLAG_HAS_TRANSFORMS);1550strikesRef = null;1551}15521553// implements sgv.getGlyphTransform1554AffineTransform getGlyphTransform(int ix) {1555int index = indices[ix];1556if (index == 0) {1557return null;1558}15591560int x = (index - 1) * 6;1561return new AffineTransform(transforms[x + 0],1562transforms[x + 1],1563transforms[x + 2],1564transforms[x + 3],1565transforms[x + 4],1566transforms[x + 5]);1567}15681569int transformCount() {1570if (transforms == null) {1571return 0;1572}1573return transforms.length / 6;1574}15751576/**1577* The strike cache works like this.1578*1579* -Each glyph is thought of as having a transform, usually identity.1580* -Each request for a strike is based on a device transform, either the1581* one in the frc or the rendering transform.1582* -For general info, strikes are held with soft references.1583* -When rendering, strikes must be held with hard references for the1584* duration of the rendering call. GlyphList will have to hold this1585* info along with the image and position info, but toss the strike info1586* when done.1587* -Build the strike cache as needed. If the dev transform we want to use1588* has changed from the last time it is built, the cache is flushed by1589* the caller before these methods are called.1590*1591* Use a tx that doesn't include translation components of dst tx.1592*/1593Object setupGlyphImages(long[] images, float[] positions, AffineTransform tx) {1594int len = sgv.glyphs.length;15951596GlyphStrike[] sl = getAllStrikes();1597for (int i = 0; i < len; ++i) {1598GlyphStrike gs = sl[indices[i]];1599int glyphID = sgv.glyphs[i];1600images[i] = gs.strike.getGlyphImagePtr(glyphID);16011602gs.getGlyphPosition(glyphID, i*2, sgv.positions, positions);1603}1604tx.transform(positions, 0, positions, 0, len);16051606return sl;1607}16081609Rectangle getGlyphsPixelBounds(AffineTransform tx, float x, float y, int start, int count) {1610Rectangle result = null;1611Rectangle r = new Rectangle();1612Point2D.Float pt = new Point.Float();1613int n = start * 2;1614while (--count >= 0) {1615GlyphStrike gs = getStrike(start);1616pt.x = x + sgv.positions[n++] + gs.dx;1617pt.y = y + sgv.positions[n++] + gs.dy;1618tx.transform(pt, pt);1619gs.strike.getGlyphImageBounds(sgv.glyphs[start++], pt, r);1620if (!r.isEmpty()) {1621if (result == null) {1622result = new Rectangle(r);1623} else {1624result.add(r);1625}1626}1627}1628return result != null ? result : r;1629}16301631GlyphStrike getStrike(int glyphIndex) {1632if (indices != null) {1633GlyphStrike[] strikes = getStrikeArray();1634return getStrikeAtIndex(strikes, indices[glyphIndex]);1635}1636return sgv.getDefaultStrike();1637}16381639private GlyphStrike[] getAllStrikes() {1640if (indices == null) {1641return null;1642}16431644GlyphStrike[] strikes = getStrikeArray();1645if (!haveAllStrikes) {1646for (int i = 0; i < strikes.length; ++i) {1647getStrikeAtIndex(strikes, i);1648}1649haveAllStrikes = true;1650}16511652return strikes;1653}16541655private GlyphStrike[] getStrikeArray() {1656GlyphStrike[] strikes = null;1657if (strikesRef != null) {1658strikes = (GlyphStrike[])strikesRef.get();1659}1660if (strikes == null) {1661haveAllStrikes = false;1662strikes = new GlyphStrike[transformCount() + 1];1663strikesRef = new SoftReference(strikes);1664}16651666return strikes;1667}16681669private GlyphStrike getStrikeAtIndex(GlyphStrike[] strikes, int strikeIndex) {1670GlyphStrike strike = strikes[strikeIndex];1671if (strike == null) {1672if (strikeIndex == 0) {1673strike = sgv.getDefaultStrike();1674} else {1675int ix = (strikeIndex - 1) * 6;1676AffineTransform gtx = new AffineTransform(transforms[ix],1677transforms[ix+1],1678transforms[ix+2],1679transforms[ix+3],1680transforms[ix+4],1681transforms[ix+5]);16821683strike = GlyphStrike.create(sgv, sgv.dtx, gtx);1684}1685strikes[strikeIndex] = strike;1686}1687return strike;1688}1689}16901691// This adjusts the metrics by the translation components of the glyph1692// transform. It is done here since the translation is not known by the1693// strike.1694// It adjusts the position of the image and the advance.16951696public static final class GlyphStrike {1697StandardGlyphVector sgv;1698FontStrike strike; // hard reference1699float dx;1700float dy;17011702static GlyphStrike create(StandardGlyphVector sgv, AffineTransform dtx, AffineTransform gtx) {1703float dx = 0;1704float dy = 0;17051706AffineTransform tx = sgv.ftx;1707if (!dtx.isIdentity() || gtx != null) {1708tx = new AffineTransform(sgv.ftx);1709if (gtx != null) {1710tx.preConcatenate(gtx);1711dx = (float)tx.getTranslateX(); // uses ftx then gtx to get translation1712dy = (float)tx.getTranslateY();1713}1714if (!dtx.isIdentity()) {1715tx.preConcatenate(dtx);1716}1717}17181719int ptSize = 1; // only matters for 'gasp' case.1720Object aaHint = sgv.frc.getAntiAliasingHint();1721if (aaHint == VALUE_TEXT_ANTIALIAS_GASP) {1722/* Must pass in the calculated point size for rendering.1723* If the glyph tx is anything other than identity or a1724* simple translate, calculate the transformed point size.1725*/1726if (!tx.isIdentity() &&1727(tx.getType() & ~AffineTransform.TYPE_TRANSLATION) != 0) {1728double shearx = tx.getShearX();1729if (shearx != 0) {1730double scaley = tx.getScaleY();1731ptSize =1732(int)Math.sqrt(shearx * shearx + scaley * scaley);1733} else {1734ptSize = (int)(Math.abs(tx.getScaleY()));1735}1736}1737}1738int aa = FontStrikeDesc.getAAHintIntVal(aaHint,sgv.font2D, ptSize);1739int fm = FontStrikeDesc.getFMHintIntVal1740(sgv.frc.getFractionalMetricsHint());1741FontStrikeDesc desc = new FontStrikeDesc(dtx,1742tx,1743sgv.font.getStyle(),1744aa, fm);1745// Get the strike via the handle. Shouldn't matter1746// if we've invalidated the font but its an extra precaution.1747// do we want the CompFont from CFont here ?1748Font2D f2d = sgv.font2D;1749if (f2d instanceof FontSubstitution) {1750f2d = ((FontSubstitution)f2d).getCompositeFont2D();1751}1752FontStrike strike = f2d.handle.font2D.getStrike(desc); // !!! getStrike(desc, false)17531754return new GlyphStrike(sgv, strike, dx, dy);1755}17561757private GlyphStrike(StandardGlyphVector sgv, FontStrike strike, float dx, float dy) {1758this.sgv = sgv;1759this.strike = strike;1760this.dx = dx;1761this.dy = dy;1762}17631764void getADL(ADL result) {1765StrikeMetrics sm = strike.getFontMetrics();1766Point2D.Float delta = null;1767if (sgv.font.isTransformed()) {1768delta = new Point2D.Float();1769delta.x = (float)sgv.font.getTransform().getTranslateX();1770delta.y = (float)sgv.font.getTransform().getTranslateY();1771}17721773result.ascentX = -sm.ascentX;1774result.ascentY = -sm.ascentY;1775result.descentX = sm.descentX;1776result.descentY = sm.descentY;1777result.leadingX = sm.leadingX;1778result.leadingY = sm.leadingY;1779}17801781void getGlyphPosition(int glyphID, int ix, float[] positions, float[] result) {1782result[ix] = positions[ix] + dx;1783++ix;1784result[ix] = positions[ix] + dy;1785}17861787void addDefaultGlyphAdvance(int glyphID, Point2D.Float result) {1788// !!! change this API? Creates unnecessary garbage. Also the name doesn't quite fit.1789// strike.addGlyphAdvance(Point2D.Float adv); // hey, whaddya know, matches my api :-)1790Point2D.Float adv = strike.getGlyphMetrics(glyphID);1791result.x += adv.x + dx;1792result.y += adv.y + dy;1793}17941795Rectangle2D getGlyphOutlineBounds(int glyphID, float x, float y) {1796Rectangle2D result = null;1797if (sgv.invdtx == null) {1798result = new Rectangle2D.Float();1799result.setRect(strike.getGlyphOutlineBounds(glyphID)); // don't mutate cached rect1800} else {1801GeneralPath gp = strike.getGlyphOutline(glyphID, 0, 0);1802gp.transform(sgv.invdtx);1803result = gp.getBounds2D();1804}1805/* Since x is the logical advance of the glyph to this point.1806* Because of the way that Rectangle.union is specified, this1807* means that subsequent unioning of a rect including that1808* will be affected, even if the glyph is empty. So skip such1809* cases. This alone isn't a complete solution since x==01810* may also not be what is wanted. The code that does the1811* unioning also needs to be aware to ignore empty glyphs.1812*/1813if (!result.isEmpty()) {1814result.setRect(result.getMinX() + x + dx,1815result.getMinY() + y + dy,1816result.getWidth(), result.getHeight());1817}1818return result;1819}18201821void appendGlyphOutline(int glyphID, GeneralPath result, float x, float y) {1822// !!! fontStrike needs a method for this. For that matter, GeneralPath does.1823GeneralPath gp = null;1824if (sgv.invdtx == null) {1825gp = strike.getGlyphOutline(glyphID, x + dx, y + dy);1826} else {1827gp = strike.getGlyphOutline(glyphID, 0, 0);1828gp.transform(sgv.invdtx);1829gp.transform(AffineTransform.getTranslateInstance(x + dx, y + dy));1830}1831PathIterator iterator = gp.getPathIterator(null);1832result.append(iterator, false);1833}1834}18351836public String toString() {1837return appendString(null).toString();1838}18391840StringBuffer appendString(StringBuffer buf) {1841if (buf == null) {1842buf = new StringBuffer();1843}1844try {1845buf.append("SGV{font: ");1846buf.append(font.toString());1847buf.append(", frc: ");1848buf.append(frc.toString());1849buf.append(", glyphs: (");1850buf.append(glyphs.length);1851buf.append(")[");1852for (int i = 0; i < glyphs.length; ++i) {1853if (i > 0) {1854buf.append(", ");1855}1856buf.append(Integer.toHexString(glyphs[i]));1857}1858buf.append("]");1859if (positions != null) {1860buf.append(", positions: (");1861buf.append(positions.length);1862buf.append(")[");1863for (int i = 0; i < positions.length; i += 2) {1864if (i > 0) {1865buf.append(", ");1866}1867buf.append(positions[i]);1868buf.append("@");1869buf.append(positions[i+1]);1870}1871buf.append("]");1872}1873if (charIndices != null) {1874buf.append(", indices: (");1875buf.append(charIndices.length);1876buf.append(")[");1877for (int i = 0; i < charIndices.length; ++i) {1878if (i > 0) {1879buf.append(", ");1880}1881buf.append(charIndices[i]);1882}1883buf.append("]");1884}1885buf.append(", flags:");1886if (getLayoutFlags() == 0) {1887buf.append(" default");1888} else {1889if ((flags & FLAG_HAS_TRANSFORMS) != 0) {1890buf.append(" tx");1891}1892if ((flags & FLAG_HAS_POSITION_ADJUSTMENTS) != 0) {1893buf.append(" pos");1894}1895if ((flags & FLAG_RUN_RTL) != 0) {1896buf.append(" rtl");1897}1898if ((flags & FLAG_COMPLEX_GLYPHS) != 0) {1899buf.append(" complex");1900}1901}1902}1903catch(Exception e) {1904buf.append(" " + e.getMessage());1905}1906buf.append("}");19071908return buf;1909}19101911static class ADL {1912public float ascentX;1913public float ascentY;1914public float descentX;1915public float descentY;1916public float leadingX;1917public float leadingY;19181919public String toString() {1920return toStringBuffer(null).toString();1921}19221923protected StringBuffer toStringBuffer(StringBuffer result) {1924if (result == null) {1925result = new StringBuffer();1926}1927result.append("ax: ");1928result.append(ascentX);1929result.append(" ay: ");1930result.append(ascentY);1931result.append(" dx: ");1932result.append(descentX);1933result.append(" dy: ");1934result.append(descentY);1935result.append(" lx: ");1936result.append(leadingX);1937result.append(" ly: ");1938result.append(leadingY);19391940return result;1941}1942}1943}194419451946