Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,10 @@ public interface CompositeFontResource extends FontResource {
*/
public int getSlotForFont(String fontName);

default boolean isColorGlyph(int glyphCode) {
int slot = (glyphCode >>> 24);
int slotglyphCode = glyphCode & CompositeGlyphMapper.GLYPHMASK;
FontResource slotResource = getSlotResource(slot);
return slotResource.isColorGlyph(slotglyphCode);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public interface FontConstants {
public static final int nameTag = 0x6E616D65; // 'name'
public static final int os_2Tag = 0x4F532F32; // 'OS/2'
public static final int postTag = 0x706F7374; // 'post'
public static final int colrTag = 0x434F4C52; // 'COLR'
public static final int sbixTag = 0x73626978; // 'sbix'

/* sizes, in bytes, of TT/TTC header records */
public static final int TTCHEADERSIZE = 12;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,7 @@ public FontStrike getStrike(float size, BaseTransform transform,
public void setPeer(Object peer);

public boolean isEmbeddedFont();

public boolean isColorGlyph(int gc);

}
Original file line number Diff line number Diff line change
Expand Up @@ -1178,6 +1178,33 @@ public FontStrike getStrike(float size, BaseTransform transform) {
return getStrike(size, transform, getDefaultAAMode());
}

@Override
public float getAdvance(int glyphCode, float ptSize) {
if (glyphCode == CharToGlyphMapper.INVISIBLE_GLYPH_ID) {
return 0f;
}

/*
* Platform-specific but it needs to be explained why this is needed.
* The hmtx table in the Apple Color Emoji font can be woefully off
* compared to the size of emoji glyph CoreText generates and the advance
* CoreText supports. So for macOS at least, we need to get those advances
* another way. Note : I also see "small" discrepancies for ordinary
* glyphs in the mac system font between hmtx and CoreText.
* Limit use of this because we aren't caching the result.
*/
if (PrismFontFactory.isMacOSX && isColorGlyph(glyphCode)) {
return getAdvanceFromPlatform(glyphCode, ptSize);
} else {
return getAdvanceFromHMTX(glyphCode, ptSize);
}
}

/* REMIND: We can cache here if it is slow */
protected float getAdvanceFromPlatform(int glyphCode, float ptSize) {
return getAdvanceFromHMTX(glyphCode, ptSize);
}

char[] advanceWidths = null;
/*
* This is returning the unhinted advance, should be OK so
Expand Down Expand Up @@ -1208,10 +1235,7 @@ public FontStrike getStrike(float size, BaseTransform transform) {
* they do not provide hdmx entry for sizes below that where hinting is
* required, suggesting the htmx table is fine for such cases.
*/
@Override
public float getAdvance(int glyphCode, float ptSize) {
if (glyphCode == CharToGlyphMapper.INVISIBLE_GLYPH_ID)
return 0f;
private float getAdvanceFromHMTX(int glyphCode, float ptSize) {

// If we haven't initialised yet, do so now.
if (advanceWidths == null && numHMetrics > 0) {
Expand Down Expand Up @@ -1374,4 +1398,114 @@ public boolean equals(Object obj) {
public int hashCode() {
return filename.hashCode() + (71 * fullName.hashCode());
}


private boolean checkedColorTables;
private boolean hasColorTables;
private synchronized boolean fontSupportsColorGlyphs() {
if (checkedColorTables) {
return hasColorTables;
}
hasColorTables =
getDirectoryEntry(sbixTag) != null ||
getDirectoryEntry(colrTag) != null;
checkedColorTables = true;

return hasColorTables;
}

public boolean isColorGlyph(int glyphID) {
if (!fontSupportsColorGlyphs()) {
return false;
}
if (getDirectoryEntry(sbixTag) != null) {
return isSbixGlyph(glyphID);
}
return false;
}


private static final int USHORT_MASK = 0xffff;
private static final int UINT_MASK = 0xffffffff;

static class ColorGlyphStrike {

private int ppem;
private int ppi;
private int dataOffsets[];

ColorGlyphStrike(int ppem, int ppi, int[] offsets) {
this.ppem = ppem;
this.ppi = ppi ;
dataOffsets = offsets;
}

boolean hasGlyph(int gid) {
if (gid >= dataOffsets.length-1) {
return false;
}
/* Per the OpenType sbix specthere's one extra offset.
*/
return dataOffsets[gid] < dataOffsets[gid+1];
}
}

ColorGlyphStrike[] sbixStrikes = null;

private boolean isSbixGlyph(int glyphID) {
if (sbixStrikes == null) {
synchronized (this) {
buildSbixStrikeTables();
if (sbixStrikes == null) {
sbixStrikes = new ColorGlyphStrike[0];
}
}
}
for (int i=0; i<sbixStrikes.length; i++) {
if (sbixStrikes[i].hasGlyph(glyphID)) {
return true;
}
}
return false;
}

private void buildSbixStrikeTables() {

Buffer sbixTable = readTable(sbixTag);

if (sbixTable == null) {
return;
}
int sz = sbixTable.capacity();
sbixTable.skip(4); // past version and flags
int numStrikes = sbixTable.getInt() & UINT_MASK;
if (numStrikes <= 0 || numStrikes >= sz) {
return;
}
int[] strikeOffsets = new int[numStrikes];
for (int i=0; i<numStrikes; i++) {
strikeOffsets[i] = sbixTable.getInt() & UINT_MASK;
if (strikeOffsets[i] >= sz) {
return;
}
}
int numGlyphs = getNumGlyphs();
ColorGlyphStrike[] strikes = new ColorGlyphStrike[numStrikes];
for (int i=0; i<numStrikes; i++) {
if (strikeOffsets[i] + 4 + (4*(numGlyphs+1)) > sz) {
return;
}
sbixTable.position(strikeOffsets[i]);

int ppem = sbixTable.getChar() & USHORT_MASK;
int ppi = sbixTable.getChar() & USHORT_MASK;
int[] glyphDataOffsets = new int[numGlyphs+1];
for (int g=0; g<=numGlyphs; g++) {
glyphDataOffsets[g] = sbixTable.getInt() & UINT_MASK;
}
strikes[i] = new ColorGlyphStrike(ppem, ppi, glyphDataOffsets);
}
sbixStrikes = strikes;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,21 @@ Path2D getGlyphOutline(int gc, float size) {
return path;
}

@Override protected int[] createGlyphBoundingBox(int gc) {
@Override protected float getAdvanceFromPlatform(int glyphCode, float ptSize) {
CTFontStrike strike =
(CTFontStrike)getStrike(ptSize, BaseTransform.IDENTITY_TRANSFORM);
long fontRef = strike.getFontRef();
int orientation = OS.kCTFontOrientationDefault;
CGSize size = new CGSize();
return (float)OS.CTFontGetAdvancesForGlyphs(fontRef, orientation, (short)glyphCode, size);
}

@Override protected int[] createGlyphBoundingBox(int gc) {
/*
* This is being done at size 12 so that the font can cache
* bounds and scale to the required point size. But if the
* bounds do not scale linearly this will fail badly
*/
float size = 12;
CTFontStrike strike = (CTFontStrike)getStrike(size,
BaseTransform.IDENTITY_TRANSFORM);
Expand All @@ -148,13 +162,25 @@ Path2D getGlyphOutline(int gc, float size) {
* The fix is to use the 'loca' and the 'glyf' tables to determine
* the glyph bounding box (same as T2K). This implementation
* uses native code to read these tables since they can be large.
* However for color (emoji) glyphs this returns the wrong bounds,
* so use CTFontGetBoundingRectsForGlyphs anyway.
* In case it fails, or the font doesn't have a glyph table
* (CFF fonts), then the bounds of the glyph outline is used instead.
*/
if (!isCFF()) {
short format = getIndexToLocFormat();
if (OS.CTFontGetBoundingRectForGlyphUsingTables(fontRef, (short)gc, format, bb)) {
if (isColorGlyph(gc)) {
CGRect rect = OS.CTFontGetBoundingRectForGlyphs(fontRef, (short)gc);
float scale = getUnitsPerEm() / size;
bb[0] = (int)(Math.round(rect.origin.x * scale));
bb[1] = (int)(Math.round(rect.origin.y * scale));
bb[2] = (int)(Math.round((rect.origin.x + rect.size.width) * scale));
bb[3] = (int)(Math.round((rect.origin.y + rect.size.height) * scale));
return bb;
} else {
short format = getIndexToLocFormat();
if (OS.CTFontGetBoundingRectForGlyphUsingTables(fontRef, (short)gc, format, bb)) {
return bb;
}
}
}
/* Note: not using tx here as the bounds need to be y up */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,6 @@ private void checkBounds() {
xAdvance = size.width;
yAdvance = -size.height; /*Inverted coordinates system */

if (drawShapes) return;

/* Avoid CTFontGetBoundingRectsForGlyphs as it is too slow */
// bounds = OS.CTFontGetBoundingRectsForGlyphs(fontRef, orientation, (short)glyphCode, null, 1);

Expand Down Expand Up @@ -154,6 +152,41 @@ private long getCachedContext(boolean lcd) {
return cachedContextRef;
}

private synchronized byte[] getColorImage(double x, double y, int w, int h) {

if (w == 0 || h == 0) return new byte[0];

long fontRef = strike.getFontRef();
CGAffineTransform matrix = strike.matrix;
long context = createContext(true, w, h);
if (context == 0) return new byte[0];

double drawX = 0, drawY = 0;
if (matrix != null) {
OS.CGContextTranslateCTM(context, -x, -y);
} else {
drawX = x;
drawY = y;
}

OS.CTFontDrawGlyphs(fontRef, (short)glyphCode, -drawX, -drawY, context);

if (matrix != null) {
OS.CGContextTranslateCTM(context, x, y);
}

byte[] imageData = OS.CGImageContextGetData(context, w, h, 32);
if (imageData == null) {
bounds = new CGRect();
imageData = new byte[0];
}

OS.CGContextRelease(context);

return imageData;
}


private synchronized byte[] getImage(double x, double y, int w, int h, int subPixel) {

if (w == 0 || h == 0) return new byte[0];
Expand Down Expand Up @@ -213,8 +246,14 @@ private synchronized byte[] getImage(double x, double y, int w, int h, int subPi

@Override public byte[] getPixelData(int subPixel) {
checkBounds();
return getImage(bounds.origin.x, bounds.origin.y,
(int)bounds.size.width, (int)bounds.size.height, subPixel);
if (isColorGlyph()) {
return getColorImage(bounds.origin.x, bounds.origin.y,
(int)bounds.size.width, (int)bounds.size.height);
} else {
return getImage(bounds.origin.x, bounds.origin.y,
(int)bounds.size.width, (int)bounds.size.height,
subPixel);
}
}

@Override public float getAdvance() {
Expand All @@ -236,7 +275,11 @@ private synchronized byte[] getImage(double x, double y, int w, int h, int subPi
@Override public int getWidth() {
checkBounds();
int w = (int)bounds.size.width;
return isLCDGlyph() ? w * 3 : w;
if (isColorGlyph()) {
return (w * 4); // has alpha
} else {
return isLCDGlyph() ? w * 3 : w;
}
}

@Override public int getHeight() {
Expand All @@ -256,6 +299,11 @@ private synchronized byte[] getImage(double x, double y, int w, int h, int subPi
return -h - y; /*Inverted coordinates system */
}

public boolean isColorGlyph() {
CTFontFile fontResource = strike.getFontResource();
return fontResource.isColorGlyph(glyphCode);
}

@Override public boolean isLCDGlyph() {
return strike.getAAMode() == FontResource.AA_LCD;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,15 @@ static final long CFStringCreate(String string) {

/* Custom */
static final native byte[] CGBitmapContextGetData(long c, int width, int height, int bpp);
static final native byte[] CGImageContextGetData(long c, int width, int height, int bpp);
static final native void CGRectApplyAffineTransform(CGRect rect, CGAffineTransform t);
static final native Path2D CGPathApply(long path);
static final native CGRect CGPathGetPathBoundingBox(long path);
static final native long CFStringCreateWithCharacters(long alloc, char[] chars, long start, long numChars);
static final native String CTFontCopyAttributeDisplayName(long font);
static final native void CTFontDrawGlyphs(long font, short glyphs, double x, double y, long context);
static final native double CTFontGetAdvancesForGlyphs(long font, int orientation, short glyphs, CGSize advances);
static final native CGRect CTFontGetBoundingRectForGlyphs(long font, short glyph);
static final native boolean CTFontGetBoundingRectForGlyphUsingTables(long font, short glyphs, short format, int[] retArr);
static final native int CTRunGetGlyphs(long run, int slotMask, int start, int[] buffer);
static final native int CTRunGetStringIndices(long run, int start, int[] buffer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,18 @@ private FontStrike getStrike(BaseTransform xform) {
g.setNodeBounds(null);
}

/*
* drawAsShapes() is used for large glyphs to avoid blowing the cache.
* But emojis aren't (currently) cached and may not be available as shapes.
* So the drawAsShapes path results in blank space instead of a large emoji
* This check is used in renderText() where we would otherwise use shapes
* to prevent that.
*/
private boolean isEmojiRun(TextRun run, FontStrike strike) {
FontResource res = strike.getFontResource();
return strike.drawAsShapes() && res.isColorGlyph(run.getGlyphCode(0));
}

private void renderText(Graphics g, FontStrike strike, BaseBounds clipBds,
Color selectionColor, int op) {
for (int i = 0; i < runs.length; i++) {
Expand All @@ -307,7 +319,7 @@ private void renderText(Graphics g, FontStrike strike, BaseBounds clipBds,
y -= lineBounds.getMinY();

if ((op & TEXT) != 0 && run.getGlyphCount() > 0) {
if ((op & FILL) != 0) {
if (((op & FILL) != 0) || isEmojiRun(run, strike)) {
int start = run.getStart();
g.drawString(run, strike, x, y,
selectionColor,
Expand Down
Loading