package com.sun.javafx.font.coretext;
import com.sun.javafx.font.FontResource;
import com.sun.javafx.font.Glyph;
import com.sun.javafx.geom.RectBounds;
import com.sun.javafx.geom.Shape;
class CTGlyph implements Glyph {
private CTFontStrike strike;
private int glyphCode;
private CGRect bounds;
private double xAdvance;
private double yAdvance;
private boolean drawShapes;
private static boolean LCD_CONTEXT = true;
private static boolean CACHE_CONTEXT = true;
private static long cachedContextRef;
private static final int BITMAP_WIDTH = 256;
private static final int BITMAP_HEIGHT = 256;
private static final int MAX_SIZE = 320;
private static final long GRAY_COLORSPACE = OS.CGColorSpaceCreateDeviceGray();
private static final long RGB_COLORSPACE = OS.CGColorSpaceCreateDeviceRGB();
CTGlyph(CTFontStrike strike, int glyphCode, boolean drawShapes) {
this.strike = strike;
this.glyphCode = glyphCode;
this.drawShapes = drawShapes;
}
@Override public int getGlyphCode() {
return glyphCode;
}
@Override public RectBounds getBBox() {
CGRect rect = strike.getBBox(glyphCode);
if (rect == null) return new RectBounds();
return new RectBounds((float)rect.origin.x,
(float)rect.origin.y,
(float)(rect.origin.x + rect.size.width),
(float)(rect.origin.y + rect.size.height));
}
private void checkBounds() {
if (bounds != null) return;
bounds = new CGRect();
if (strike.getSize() == 0) return;
long fontRef = strike.getFontRef();
if (fontRef == 0) return;
int orientation = OS.kCTFontOrientationDefault;
CGSize size = new CGSize();
OS.CTFontGetAdvancesForGlyphs(fontRef, orientation, (short)glyphCode, size);
xAdvance = size.width;
yAdvance = -size.height;
if (drawShapes) return;
CTFontFile fr = (CTFontFile)strike.getFontResource();
float[] bb = new float[4];
fr.getGlyphBoundingBox((short)glyphCode, strike.getSize(), bb);
bounds.origin.x = bb[0];
bounds.origin.y = bb[1];
bounds.size.width = (bb[2] - bb[0]);
bounds.size.height = (bb[3] - bb[1]);
if (strike.matrix != null) {
OS.CGRectApplyAffineTransform(bounds, strike.matrix);
}
if (bounds.size.width < 0 || bounds.size.height < 0 ||
bounds.size.width > MAX_SIZE || bounds.size.height > MAX_SIZE) {
bounds.origin.x = bounds.origin.y = bounds.size.width = bounds.size.height = 0;
} else {
bounds.origin.x = (int)Math.floor(bounds.origin.x) - 1;
bounds.origin.y = (int)Math.floor(bounds.origin.y) - 1;
bounds.size.width = (int)Math.ceil(bounds.size.width) + 1 + 1 + 1;
bounds.size.height = (int)Math.ceil(bounds.size.height) + 1 + 1 + 1;
}
}
@Override public Shape getShape() {
return strike.createGlyphOutline(glyphCode);
}
private long createContext(boolean lcd, int width, int height) {
long space;
int bpc = 8, bpr, flags;
if (lcd) {
space = RGB_COLORSPACE;
bpr = width * 4;
flags = OS.kCGBitmapByteOrder32Host | OS.kCGImageAlphaPremultipliedFirst;
} else {
space = GRAY_COLORSPACE;
bpr = width;
flags = OS.kCGImageAlphaNone;
}
long context = OS.CGBitmapContextCreate(0, width, height, bpc, bpr, space, flags);
boolean subPixel = strike.isSubPixelGlyph();
OS.CGContextSetAllowsFontSmoothing(context, lcd);
OS.CGContextSetAllowsAntialiasing(context, true);
OS.CGContextSetAllowsFontSubpixelPositioning(context, subPixel);
OS.CGContextSetAllowsFontSubpixelQuantization(context, subPixel);
return context;
}
private long getCachedContext(boolean lcd) {
if (cachedContextRef == 0) {
cachedContextRef = createContext(lcd, BITMAP_WIDTH, BITMAP_HEIGHT);
}
return cachedContextRef;
}
private synchronized byte[] getImage(double x, double y, int w, int h, int subPixel) {
if (w == 0 || h == 0) return new byte[0];
long fontRef = strike.getFontRef();
boolean lcd = isLCDGlyph();
boolean lcdContext = LCD_CONTEXT || lcd;
CGAffineTransform matrix = strike.matrix;
boolean cache = CACHE_CONTEXT & BITMAP_WIDTH >= w & BITMAP_HEIGHT >= h;
long context = cache ? getCachedContext(lcdContext) :
createContext(lcdContext, w, h);
if (context == 0) return new byte[0];
OS.CGContextSetRGBFillColor(context, 1, 1, 1, 1);
CGRect rect = new CGRect();
rect.size.width = w;
rect.size.height = h;
OS.CGContextFillRect(context, rect);
double drawX = 0, drawY = 0;
if (matrix != null) {
OS.CGContextTranslateCTM(context, -x, -y);
} else {
drawX = x - strike.getSubPixelPosition(subPixel);
drawY = y;
}
OS.CGContextSetRGBFillColor(context, 0, 0, 0, 1);
OS.CTFontDrawGlyphs(fontRef, (short)glyphCode, -drawX, -drawY, context);
if (matrix != null) {
OS.CGContextTranslateCTM(context, x, y);
}
byte[] imageData;
if (lcd) {
imageData = OS.CGBitmapContextGetData(context, w, h, 24);
} else {
imageData = OS.CGBitmapContextGetData(context, w, h, 8);
}
if (imageData == null) {
bounds = new CGRect();
imageData = new byte[0];
}
if (!cache) {
OS.CGContextRelease(context);
}
return imageData;
}
@Override public byte[] getPixelData() {
return getPixelData(0);
}
@Override public byte[] getPixelData(int subPixel) {
checkBounds();
return getImage(bounds.origin.x, bounds.origin.y,
(int)bounds.size.width, (int)bounds.size.height, subPixel);
}
@Override public float getAdvance() {
checkBounds();
return (float)xAdvance;
}
@Override public float getPixelXAdvance() {
checkBounds();
return (float)xAdvance;
}
@Override public float getPixelYAdvance() {
checkBounds();
return (float)yAdvance;
}
@Override public int getWidth() {
checkBounds();
int w = (int)bounds.size.width;
return isLCDGlyph() ? w * 3 : w;
}
@Override public int getHeight() {
checkBounds();
return (int)bounds.size.height;
}
@Override public int getOriginX() {
checkBounds();
return (int)bounds.origin.x;
}
@Override public int getOriginY() {
checkBounds();
int h = (int)bounds.size.height;
int y = (int)bounds.origin.y;
return -h - y;
}
@Override public boolean isLCDGlyph() {
return strike.getAAMode() == FontResource.AA_LCD;
}
}