} } } 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 = (GlyphStrike[])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); FontStrike strike = sgv.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(" " + 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; } }}Lines 1501–1935 of 1935. Previous lines
} } } 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 = (GlyphStrike[])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); FontStrike strike = sgv.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(" " + 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; } } }