segIntGlyphImages[segIndex][subIndex] = (int)glyphPtr; return glyphPtr; } else { StrikeCache.freeIntPointer((int)glyphPtr); return segIntGlyphImages[segIndex][subIndex] & INTMASK; } case LONGARRAY: if (longGlyphImages[glyphCode] == 0L) { longGlyphImages[glyphCode] = glyphPtr; return glyphPtr; } else { StrikeCache.freeLongPointer(glyphPtr); return longGlyphImages[glyphCode]; } case SEGLONGARRAY: segIndex = glyphCode >> SEGSHIFT; subIndex = glyphCode % SEGSIZE; if (segLongGlyphImages[segIndex] == null) { segLongGlyphImages[segIndex] = new long[SEGSIZE]; } if (segLongGlyphImages[segIndex][subIndex] == 0L) { segLongGlyphImages[segIndex][subIndex] = glyphPtr; return glyphPtr; } else { StrikeCache.freeLongPointer(glyphPtr); return segLongGlyphImages[segIndex][subIndex]; } } /* Reach here only when the cache is not initialised which is only * for the first glyph to be initialised in the strike. * Initialise it and recurse. Note that we are already synchronized. */ initGlyphCache(); return setCachedGlyphPtr(glyphCode, glyphPtr); } /* Called only from synchronized code or constructor */ private synchronized void initGlyphCache() { int numGlyphs = mapper.getNumGlyphs(); int tmpFormat = UNINITIALISED; if (segmentedCache) { int numSegments = (numGlyphs + SEGSIZE-1)/SEGSIZE; if (longAddresses) { tmpFormat = SEGLONGARRAY; segLongGlyphImages = new long[numSegments][]; this.disposer.segLongGlyphImages = segLongGlyphImages; } else { tmpFormat = SEGINTARRAY; segIntGlyphImages = new int[numSegments][]; this.disposer.segIntGlyphImages = segIntGlyphImages; } } else { if (longAddresses) { tmpFormat = LONGARRAY; longGlyphImages = new long[numGlyphs]; this.disposer.longGlyphImages = longGlyphImages; } else { tmpFormat = INTARRAY; intGlyphImages = new int[numGlyphs]; this.disposer.intGlyphImages = intGlyphImages; } } glyphCacheFormat = tmpFormat; } float getGlyphAdvance(int glyphCode) { return getGlyphAdvance(glyphCode, true); } /* Metrics info is always retrieved. If the GlyphInfo address is non-zero * then metrics info there is valid and can just be copied. * This is in user space coordinates unless getUserAdv == false. * Device space advance should not be propagated out of this class. */ private float getGlyphAdvance(int glyphCode, boolean getUserAdv) { float advance; if (glyphCode >= INVISIBLE_GLYPHS) { return 0f; } /* Notes on the (getUserAdv == false) case. * * Setting getUserAdv == false is internal to this class. * If there's no graphics transform we can let * getGlyphAdvance take its course, and potentially caching in * advances arrays, except for signalling that * getUserAdv == false means there is no need to create an image. * It is possible that code already calculated the user advance, * and it is desirable to take advantage of that work. * But, if there's a transform and we want device advance, we * can't use any values cached in the advances arrays - unless * first re-transform them into device space using 'desc.devTx'. * invertDevTx is null if the graphics transform is identity, * a translate, or non-invertible. The latter case should * not ever occur in the getUserAdv == false path. * In other words its either null, or the inversion of a * simple uniform scale. If its null, we can populate and * use the advance caches as normal. * * If we don't find a cached value, obtain the device advance and * return it. This will get stashed on the image by the caller and any * subsequent metrics calls will be able to use it as is the case * whenever an image is what is initially requested. * * Don't query if there's a value cached on the image, since this * getUserAdv==false code path is entered solely when none exists. */ if (horizontalAdvances != null) { advance = horizontalAdvances[glyphCode]; if (advance != Float.MAX_VALUE) { if (!getUserAdv && invertDevTx != null) { Point2D.Float metrics = new Point2D.Float(advance, 0f); desc.devTx.deltaTransform(metrics, metrics); return metrics.x; } else { return advance; } } } else if (segmentedCache && segHorizontalAdvances != null) { int segIndex = glyphCode >> SEGSHIFT; float[] subArray = segHorizontalAdvances[segIndex]; if (subArray != null) { advance = subArray[glyphCode % SEGSIZE]; if (advance != Float.MAX_VALUE) { if (!getUserAdv && invertDevTx != null) { Point2D.Float metrics = new Point2D.Float(advance, 0f); desc.devTx.deltaTransform(metrics, metrics); return metrics.x; } else { return advance; } } } } if (!getUserAdv && invertDevTx != null) { Point2D.Float metrics = new Point2D.Float(); fileFont.getGlyphMetrics(pScalerContext, glyphCode, metrics); return metrics.x; } if (invertDevTx != null || !getUserAdv) { /* If there is a device transform need x & y advance to * transform back into user space. */ advance = getGlyphMetrics(glyphCode, getUserAdv).x; } else { long glyphPtr; if (getImageWithAdvance) { /* A heuristic optimisation says that for most cases its * worthwhile retrieving the image at the same time as the * advance. So here we get the image data even if its not * already cached. */ glyphPtr = getGlyphImagePtr(glyphCode); } else { glyphPtr = getCachedGlyphPtr(glyphCode); } if (glyphPtr != 0L) { advance = StrikeCache.unsafe.getFloat (glyphPtr + StrikeCache.xAdvanceOffset); } else { advance = fileFont.getGlyphAdvance(pScalerContext, glyphCode); } } if (horizontalAdvances != null) { horizontalAdvances[glyphCode] = advance; } else if (segmentedCache && segHorizontalAdvances != null) { int segIndex = glyphCode >> SEGSHIFT; int subIndex = glyphCode % SEGSIZE; if (segHorizontalAdvances[segIndex] == null) { segHorizontalAdvances[segIndex] = new float[SEGSIZE]; for (int i=0; i<SEGSIZE; i++) { segHorizontalAdvances[segIndex][i] = Float.MAX_VALUE; } } segHorizontalAdvances[segIndex][subIndex] = advance; } return advance; } float getCodePointAdvance(int cp) { return getGlyphAdvance(mapper.charToGlyph(cp)); } /** * Result and pt are both in device space. */ void getGlyphImageBounds(int glyphCode, Point2D.Float pt, Rectangle result) { long ptr = getGlyphImagePtr(glyphCode); float topLeftX, topLeftY; /* With our current design NULL ptr is not possible but if we eventually allow scalers to return NULL pointers this check might be actually useful. */ if (ptr == 0L) { result.x = (int) Math.floor(pt.x); result.y = (int) Math.floor(pt.y); result.width = result.height = 0; return; } topLeftX = StrikeCache.unsafe.getFloat(ptr+StrikeCache.topLeftXOffset); topLeftY = StrikeCache.unsafe.getFloat(ptr+StrikeCache.topLeftYOffset); result.x = (int)Math.floor(pt.x + topLeftX); result.y = (int)Math.floor(pt.y + topLeftY); result.width = StrikeCache.unsafe.getShort(ptr+StrikeCache.widthOffset) &0x0ffff; result.height = StrikeCache.unsafe.getShort(ptr+StrikeCache.heightOffset) &0x0ffff; /* HRGB LCD text may have padding that is empty. This is almost always * going to be when topLeftX is -2 or less. * Try to return a tighter bounding box in that case. * If the first three bytes of every row are all zero, then * add 1 to "x" and reduce "width" by 1. */ if ((desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HRGB || desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HBGR) && topLeftX <= -2.0f) { int minx = getGlyphImageMinX(ptr, (int)result.x); if (minx > result.x) { result.x += 1; result.width -=1; } } } private int getGlyphImageMinX(long ptr, int origMinX) { int width = StrikeCache.unsafe.getChar(ptr+StrikeCache.widthOffset); int height = StrikeCache.unsafe.getChar(ptr+StrikeCache.heightOffset); int rowBytes = StrikeCache.unsafe.getChar(ptr+StrikeCache.rowBytesOffset); if (rowBytes == width) { return origMinX; } long pixelData; if (StrikeCache.nativeAddressSize == 4) { pixelData = 0xffffffff & StrikeCache.unsafe.getInt(ptr + StrikeCache.pixelDataOffset); } else { pixelData = StrikeCache.unsafe.getLong(ptr + StrikeCache.pixelDataOffset); } if (pixelData == 0L) { return origMinX; } for (int y=0;y<height;y++) { for (int x=0;x<3;x++) { if (StrikeCache.unsafe.getByte(pixelData+y*rowBytes+x) != 0) { return origMinX; } } } return origMinX+1; } /* These 3 metrics methods below should be implemented to return * values in user space. */ StrikeMetrics getFontMetrics() { if (strikeMetrics == null) { strikeMetrics = fileFont.getFontMetrics(pScalerContext); if (invertDevTx != null) { strikeMetrics.convertToUserSpace(invertDevTx); } } return strikeMetrics; } Point2D.Float getGlyphMetrics(int glyphCode) { return getGlyphMetrics(glyphCode, true); } private Point2D.Float getGlyphMetrics(int glyphCode, boolean getImage) { Point2D.Float metrics = new Point2D.Float(); // !!! or do we force sgv user glyphs? if (glyphCode >= INVISIBLE_GLYPHS) { return metrics; } long glyphPtr; if (getImageWithAdvance && getImage) { /* A heuristic optimisation says that for most cases its * worthwhile retrieving the image at the same time as the * metrics. So here we get the image data even if its not * already cached. */ glyphPtr = getGlyphImagePtr(glyphCode); } else { glyphPtr = getCachedGlyphPtr(glyphCode); } if (glyphPtr != 0L) { metrics = new Point2D.Float(); metrics.x = StrikeCache.unsafe.getFloat (glyphPtr + StrikeCache.xAdvanceOffset); metrics.y = StrikeCache.unsafe.getFloat (glyphPtr + StrikeCache.yAdvanceOffset); /* advance is currently in device space, need to convert back * into user space. * This must not include the translation component. */ if (invertDevTx != null) { invertDevTx.deltaTransform(metrics, metrics); } } else { /* We sometimes cache these metrics as they are expensive to * generate for large glyphs. * We never reach this path if we obtain images with advances. * But if we do not obtain images with advances its possible that * we first obtain this information, then the image, and never * will access this value again. */ Integer key = Integer.valueOf(glyphCode); Point2D.Float value = null; ConcurrentHashMap<Integer, Point2D.Float> glyphMetricsMap = null; if (glyphMetricsMapRef != null) { glyphMetricsMap = glyphMetricsMapRef.get(); } if (glyphMetricsMap != null) { value = glyphMetricsMap.get(key); if (value != null) { metrics.x = value.x; metrics.y = value.y; /* already in user space */ return metrics; } } if (value == null) { fileFont.getGlyphMetrics(pScalerContext, glyphCode, metrics); /* advance is currently in device space, need to convert back * into user space. */ if (invertDevTx != null) { invertDevTx.deltaTransform(metrics, metrics); } value = new Point2D.Float(metrics.x, metrics.y); /* We aren't synchronizing here so it is possible to * overwrite the map with another one but this is harmless. */ if (glyphMetricsMap == null) { glyphMetricsMap = new ConcurrentHashMap<Integer, Point2D.Float>(); glyphMetricsMapRef = new SoftReference<ConcurrentHashMap<Integer, Point2D.Float>>(glyphMetricsMap); } glyphMetricsMap.put(key, value); } } return metrics; } Point2D.Float getCharMetrics(char ch) { return getGlyphMetrics(mapper.charToGlyph(ch)); } /* The caller of this can be trusted to return a copy of this * return value rectangle to public API. In fact frequently it * can't use use this return value directly anyway. * This returns bounds in device space. Currently the only * caller is SGV and it converts back to user space. * We could change things so that this code does the conversion so * that all coords coming out of the font system are converted back * into user space even if they were measured in device space. * The same applies to the other methods that return outlines (below) * But it may make particular sense for this method that caches its * results. * There'd be plenty of exceptions, to this too, eg getGlyphPoint needs * device coords as its called from native layout and getGlyphImageBounds * is used by GlyphVector.getGlyphPixelBounds which is specified to * return device coordinates, the image pointers aren't really used * up in Java code either. */ Rectangle2D.Float getGlyphOutlineBounds(int glyphCode) { if (boundsMap == null) { boundsMap = new ConcurrentHashMap<Integer, Rectangle2D.Float>(); } Integer key = Integer.valueOf(glyphCode); Rectangle2D.Float bounds = boundsMap.get(key); if (bounds == null) { bounds = fileFont.getGlyphOutlineBounds(pScalerContext, glyphCode); boundsMap.put(key, bounds); } return bounds; } public Rectangle2D getOutlineBounds(int glyphCode) { return fileFont.getGlyphOutlineBounds(pScalerContext, glyphCode); } private WeakReference<ConcurrentHashMap<Integer,GeneralPath>> outlineMapRef; GeneralPath getGlyphOutline(int glyphCode, float x, float y) { GeneralPath gp = null; ConcurrentHashMap<Integer, GeneralPath> outlineMap = null; if (outlineMapRef != null) { outlineMap = outlineMapRef.get(); if (outlineMap != null) { gp = (GeneralPath)outlineMap.get(glyphCode); } } if (gp == null) { gp = fileFont.getGlyphOutline(pScalerContext, glyphCode, 0, 0); if (outlineMap == null) { outlineMap = new ConcurrentHashMap<Integer, GeneralPath>(); outlineMapRef = new WeakReference <ConcurrentHashMap<Integer,GeneralPath>>(outlineMap); } outlineMap.put(glyphCode, gp); } gp = (GeneralPath)gp.clone(); // mutable! if (x != 0f || y != 0f) { gp.transform(AffineTransform.getTranslateInstance(x, y)); } return gp; } GeneralPath getGlyphVectorOutline(int[] glyphs, float x, float y) { return fileFont.getGlyphVectorOutline(pScalerContext, glyphs, glyphs.length, x, y); } protected void adjustPoint(Point2D.Float pt) { if (invertDevTx != null) { invertDevTx.deltaTransform(pt, pt); } }}Lines 501–951 of 951. Previous lines
segIntGlyphImages[segIndex][subIndex] = (int)glyphPtr; return glyphPtr; } else { StrikeCache.freeIntPointer((int)glyphPtr); return segIntGlyphImages[segIndex][subIndex] & INTMASK; } case LONGARRAY: if (longGlyphImages[glyphCode] == 0L) { longGlyphImages[glyphCode] = glyphPtr; return glyphPtr; } else { StrikeCache.freeLongPointer(glyphPtr); return longGlyphImages[glyphCode]; } case SEGLONGARRAY: segIndex = glyphCode >> SEGSHIFT; subIndex = glyphCode % SEGSIZE; if (segLongGlyphImages[segIndex] == null) { segLongGlyphImages[segIndex] = new long[SEGSIZE]; } if (segLongGlyphImages[segIndex][subIndex] == 0L) { segLongGlyphImages[segIndex][subIndex] = glyphPtr; return glyphPtr; } else { StrikeCache.freeLongPointer(glyphPtr); return segLongGlyphImages[segIndex][subIndex]; } } /* Reach here only when the cache is not initialised which is only * for the first glyph to be initialised in the strike. * Initialise it and recurse. Note that we are already synchronized. */ initGlyphCache(); return setCachedGlyphPtr(glyphCode, glyphPtr); } /* Called only from synchronized code or constructor */ private synchronized void initGlyphCache() { int numGlyphs = mapper.getNumGlyphs(); int tmpFormat = UNINITIALISED; if (segmentedCache) { int numSegments = (numGlyphs + SEGSIZE-1)/SEGSIZE; if (longAddresses) { tmpFormat = SEGLONGARRAY; segLongGlyphImages = new long[numSegments][]; this.disposer.segLongGlyphImages = segLongGlyphImages; } else { tmpFormat = SEGINTARRAY; segIntGlyphImages = new int[numSegments][]; this.disposer.segIntGlyphImages = segIntGlyphImages; } } else { if (longAddresses) { tmpFormat = LONGARRAY; longGlyphImages = new long[numGlyphs]; this.disposer.longGlyphImages = longGlyphImages; } else { tmpFormat = INTARRAY; intGlyphImages = new int[numGlyphs]; this.disposer.intGlyphImages = intGlyphImages; } } glyphCacheFormat = tmpFormat; } float getGlyphAdvance(int glyphCode) { return getGlyphAdvance(glyphCode, true); } /* Metrics info is always retrieved. If the GlyphInfo address is non-zero * then metrics info there is valid and can just be copied. * This is in user space coordinates unless getUserAdv == false. * Device space advance should not be propagated out of this class. */ private float getGlyphAdvance(int glyphCode, boolean getUserAdv) { float advance; if (glyphCode >= INVISIBLE_GLYPHS) { return 0f; } /* Notes on the (getUserAdv == false) case. * * Setting getUserAdv == false is internal to this class. * If there's no graphics transform we can let * getGlyphAdvance take its course, and potentially caching in * advances arrays, except for signalling that * getUserAdv == false means there is no need to create an image. * It is possible that code already calculated the user advance, * and it is desirable to take advantage of that work. * But, if there's a transform and we want device advance, we * can't use any values cached in the advances arrays - unless * first re-transform them into device space using 'desc.devTx'. * invertDevTx is null if the graphics transform is identity, * a translate, or non-invertible. The latter case should * not ever occur in the getUserAdv == false path. * In other words its either null, or the inversion of a * simple uniform scale. If its null, we can populate and * use the advance caches as normal. * * If we don't find a cached value, obtain the device advance and * return it. This will get stashed on the image by the caller and any * subsequent metrics calls will be able to use it as is the case * whenever an image is what is initially requested. * * Don't query if there's a value cached on the image, since this * getUserAdv==false code path is entered solely when none exists. */ if (horizontalAdvances != null) { advance = horizontalAdvances[glyphCode]; if (advance != Float.MAX_VALUE) { if (!getUserAdv && invertDevTx != null) { Point2D.Float metrics = new Point2D.Float(advance, 0f); desc.devTx.deltaTransform(metrics, metrics); return metrics.x; } else { return advance; } } } else if (segmentedCache && segHorizontalAdvances != null) { int segIndex = glyphCode >> SEGSHIFT; float[] subArray = segHorizontalAdvances[segIndex]; if (subArray != null) { advance = subArray[glyphCode % SEGSIZE]; if (advance != Float.MAX_VALUE) { if (!getUserAdv && invertDevTx != null) { Point2D.Float metrics = new Point2D.Float(advance, 0f); desc.devTx.deltaTransform(metrics, metrics); return metrics.x; } else { return advance; } } } } if (!getUserAdv && invertDevTx != null) { Point2D.Float metrics = new Point2D.Float(); fileFont.getGlyphMetrics(pScalerContext, glyphCode, metrics); return metrics.x; } if (invertDevTx != null || !getUserAdv) { /* If there is a device transform need x & y advance to * transform back into user space. */ advance = getGlyphMetrics(glyphCode, getUserAdv).x; } else { long glyphPtr; if (getImageWithAdvance) { /* A heuristic optimisation says that for most cases its * worthwhile retrieving the image at the same time as the * advance. So here we get the image data even if its not * already cached. */ glyphPtr = getGlyphImagePtr(glyphCode); } else { glyphPtr = getCachedGlyphPtr(glyphCode); } if (glyphPtr != 0L) { advance = StrikeCache.unsafe.getFloat (glyphPtr + StrikeCache.xAdvanceOffset); } else { advance = fileFont.getGlyphAdvance(pScalerContext, glyphCode); } } if (horizontalAdvances != null) { horizontalAdvances[glyphCode] = advance; } else if (segmentedCache && segHorizontalAdvances != null) { int segIndex = glyphCode >> SEGSHIFT; int subIndex = glyphCode % SEGSIZE; if (segHorizontalAdvances[segIndex] == null) { segHorizontalAdvances[segIndex] = new float[SEGSIZE]; for (int i=0; i<SEGSIZE; i++) { segHorizontalAdvances[segIndex][i] = Float.MAX_VALUE; } } segHorizontalAdvances[segIndex][subIndex] = advance; } return advance; } float getCodePointAdvance(int cp) { return getGlyphAdvance(mapper.charToGlyph(cp)); } /** * Result and pt are both in device space. */ void getGlyphImageBounds(int glyphCode, Point2D.Float pt, Rectangle result) { long ptr = getGlyphImagePtr(glyphCode); float topLeftX, topLeftY; /* With our current design NULL ptr is not possible but if we eventually allow scalers to return NULL pointers this check might be actually useful. */ if (ptr == 0L) { result.x = (int) Math.floor(pt.x); result.y = (int) Math.floor(pt.y); result.width = result.height = 0; return; } topLeftX = StrikeCache.unsafe.getFloat(ptr+StrikeCache.topLeftXOffset); topLeftY = StrikeCache.unsafe.getFloat(ptr+StrikeCache.topLeftYOffset); result.x = (int)Math.floor(pt.x + topLeftX); result.y = (int)Math.floor(pt.y + topLeftY); result.width = StrikeCache.unsafe.getShort(ptr+StrikeCache.widthOffset) &0x0ffff; result.height = StrikeCache.unsafe.getShort(ptr+StrikeCache.heightOffset) &0x0ffff; /* HRGB LCD text may have padding that is empty. This is almost always * going to be when topLeftX is -2 or less. * Try to return a tighter bounding box in that case. * If the first three bytes of every row are all zero, then * add 1 to "x" and reduce "width" by 1. */ if ((desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HRGB || desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HBGR) && topLeftX <= -2.0f) { int minx = getGlyphImageMinX(ptr, (int)result.x); if (minx > result.x) { result.x += 1; result.width -=1; } } } private int getGlyphImageMinX(long ptr, int origMinX) { int width = StrikeCache.unsafe.getChar(ptr+StrikeCache.widthOffset); int height = StrikeCache.unsafe.getChar(ptr+StrikeCache.heightOffset); int rowBytes = StrikeCache.unsafe.getChar(ptr+StrikeCache.rowBytesOffset); if (rowBytes == width) { return origMinX; } long pixelData; if (StrikeCache.nativeAddressSize == 4) { pixelData = 0xffffffff & StrikeCache.unsafe.getInt(ptr + StrikeCache.pixelDataOffset); } else { pixelData = StrikeCache.unsafe.getLong(ptr + StrikeCache.pixelDataOffset); } if (pixelData == 0L) { return origMinX; } for (int y=0;y<height;y++) { for (int x=0;x<3;x++) { if (StrikeCache.unsafe.getByte(pixelData+y*rowBytes+x) != 0) { return origMinX; } } } return origMinX+1; } /* These 3 metrics methods below should be implemented to return * values in user space. */ StrikeMetrics getFontMetrics() { if (strikeMetrics == null) { strikeMetrics = fileFont.getFontMetrics(pScalerContext); if (invertDevTx != null) { strikeMetrics.convertToUserSpace(invertDevTx); } } return strikeMetrics; } Point2D.Float getGlyphMetrics(int glyphCode) { return getGlyphMetrics(glyphCode, true); } private Point2D.Float getGlyphMetrics(int glyphCode, boolean getImage) { Point2D.Float metrics = new Point2D.Float(); // !!! or do we force sgv user glyphs? if (glyphCode >= INVISIBLE_GLYPHS) { return metrics; } long glyphPtr; if (getImageWithAdvance && getImage) { /* A heuristic optimisation says that for most cases its * worthwhile retrieving the image at the same time as the * metrics. So here we get the image data even if its not * already cached. */ glyphPtr = getGlyphImagePtr(glyphCode); } else { glyphPtr = getCachedGlyphPtr(glyphCode); } if (glyphPtr != 0L) { metrics = new Point2D.Float(); metrics.x = StrikeCache.unsafe.getFloat (glyphPtr + StrikeCache.xAdvanceOffset); metrics.y = StrikeCache.unsafe.getFloat (glyphPtr + StrikeCache.yAdvanceOffset); /* advance is currently in device space, need to convert back * into user space. * This must not include the translation component. */ if (invertDevTx != null) { invertDevTx.deltaTransform(metrics, metrics); } } else { /* We sometimes cache these metrics as they are expensive to * generate for large glyphs. * We never reach this path if we obtain images with advances. * But if we do not obtain images with advances its possible that * we first obtain this information, then the image, and never * will access this value again. */ Integer key = Integer.valueOf(glyphCode); Point2D.Float value = null; ConcurrentHashMap<Integer, Point2D.Float> glyphMetricsMap = null; if (glyphMetricsMapRef != null) { glyphMetricsMap = glyphMetricsMapRef.get(); } if (glyphMetricsMap != null) { value = glyphMetricsMap.get(key); if (value != null) { metrics.x = value.x; metrics.y = value.y; /* already in user space */ return metrics; } } if (value == null) { fileFont.getGlyphMetrics(pScalerContext, glyphCode, metrics); /* advance is currently in device space, need to convert back * into user space. */ if (invertDevTx != null) { invertDevTx.deltaTransform(metrics, metrics); } value = new Point2D.Float(metrics.x, metrics.y); /* We aren't synchronizing here so it is possible to * overwrite the map with another one but this is harmless. */ if (glyphMetricsMap == null) { glyphMetricsMap = new ConcurrentHashMap<Integer, Point2D.Float>(); glyphMetricsMapRef = new SoftReference<ConcurrentHashMap<Integer, Point2D.Float>>(glyphMetricsMap); } glyphMetricsMap.put(key, value); } } return metrics; } Point2D.Float getCharMetrics(char ch) { return getGlyphMetrics(mapper.charToGlyph(ch)); } /* The caller of this can be trusted to return a copy of this * return value rectangle to public API. In fact frequently it * can't use use this return value directly anyway. * This returns bounds in device space. Currently the only * caller is SGV and it converts back to user space. * We could change things so that this code does the conversion so * that all coords coming out of the font system are converted back * into user space even if they were measured in device space. * The same applies to the other methods that return outlines (below) * But it may make particular sense for this method that caches its * results. * There'd be plenty of exceptions, to this too, eg getGlyphPoint needs * device coords as its called from native layout and getGlyphImageBounds * is used by GlyphVector.getGlyphPixelBounds which is specified to * return device coordinates, the image pointers aren't really used * up in Java code either. */ Rectangle2D.Float getGlyphOutlineBounds(int glyphCode) { if (boundsMap == null) { boundsMap = new ConcurrentHashMap<Integer, Rectangle2D.Float>(); } Integer key = Integer.valueOf(glyphCode); Rectangle2D.Float bounds = boundsMap.get(key); if (bounds == null) { bounds = fileFont.getGlyphOutlineBounds(pScalerContext, glyphCode); boundsMap.put(key, bounds); } return bounds; } public Rectangle2D getOutlineBounds(int glyphCode) { return fileFont.getGlyphOutlineBounds(pScalerContext, glyphCode); } private WeakReference<ConcurrentHashMap<Integer,GeneralPath>> outlineMapRef; GeneralPath getGlyphOutline(int glyphCode, float x, float y) { GeneralPath gp = null; ConcurrentHashMap<Integer, GeneralPath> outlineMap = null; if (outlineMapRef != null) { outlineMap = outlineMapRef.get(); if (outlineMap != null) { gp = (GeneralPath)outlineMap.get(glyphCode); } } if (gp == null) { gp = fileFont.getGlyphOutline(pScalerContext, glyphCode, 0, 0); if (outlineMap == null) { outlineMap = new ConcurrentHashMap<Integer, GeneralPath>(); outlineMapRef = new WeakReference <ConcurrentHashMap<Integer,GeneralPath>>(outlineMap); } outlineMap.put(glyphCode, gp); } gp = (GeneralPath)gp.clone(); // mutable! if (x != 0f || y != 0f) { gp.transform(AffineTransform.getTranslateInstance(x, y)); } return gp; } GeneralPath getGlyphVectorOutline(int[] glyphs, float x, float y) { return fileFont.getGlyphVectorOutline(pScalerContext, glyphs, glyphs.length, x, y); } protected void adjustPoint(Point2D.Float pt) { if (invertDevTx != null) { invertDevTx.deltaTransform(pt, pt); } } }