8353230: Emoji rendering regression after JDK-8208377
Reviewed-by: prr, honkar
This commit is contained in:
parent
b7ca672d5c
commit
94039e22bb
@ -27,6 +27,9 @@ package sun.font;
|
|||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import static sun.font.FontUtilities.isDefaultIgnorable;
|
||||||
|
import static sun.font.FontUtilities.isIgnorableWhitespace;
|
||||||
|
|
||||||
public class CCharToGlyphMapper extends CharToGlyphMapper {
|
public class CCharToGlyphMapper extends CharToGlyphMapper {
|
||||||
private static native int countGlyphs(final long nativeFontPtr);
|
private static native int countGlyphs(final long nativeFontPtr);
|
||||||
|
|
||||||
@ -47,12 +50,12 @@ public class CCharToGlyphMapper extends CharToGlyphMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean canDisplay(char ch) {
|
public boolean canDisplay(char ch) {
|
||||||
int glyph = charToGlyph(ch);
|
int glyph = charToGlyph(ch, false);
|
||||||
return glyph != missingGlyph;
|
return glyph != missingGlyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean canDisplay(int cp) {
|
public boolean canDisplay(int cp) {
|
||||||
int glyph = charToGlyph(cp);
|
int glyph = charToGlyph(cp, false);
|
||||||
return glyph != missingGlyph;
|
return glyph != missingGlyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,17 +92,17 @@ public class CCharToGlyphMapper extends CharToGlyphMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public synchronized int charToGlyph(char unicode) {
|
public synchronized int charToGlyph(char unicode) {
|
||||||
int glyph = cache.get(unicode);
|
return charToGlyph(unicode, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int charToGlyph(char unicode, boolean raw) {
|
||||||
|
int glyph = cache.get(unicode, raw);
|
||||||
if (glyph != 0) return glyph;
|
if (glyph != 0) return glyph;
|
||||||
|
|
||||||
if (FontUtilities.isDefaultIgnorable(unicode) || isIgnorableWhitespace(unicode)) {
|
|
||||||
glyph = INVISIBLE_GLYPH_ID;
|
|
||||||
} else {
|
|
||||||
final char[] unicodeArray = new char[] { unicode };
|
final char[] unicodeArray = new char[] { unicode };
|
||||||
final int[] glyphArray = new int[1];
|
final int[] glyphArray = new int[1];
|
||||||
nativeCharsToGlyphs(fFont.getNativeFontPtr(), 1, unicodeArray, glyphArray);
|
nativeCharsToGlyphs(fFont.getNativeFontPtr(), 1, unicodeArray, glyphArray);
|
||||||
glyph = glyphArray[0];
|
glyph = glyphArray[0];
|
||||||
}
|
|
||||||
|
|
||||||
cache.put(unicode, glyph);
|
cache.put(unicode, glyph);
|
||||||
|
|
||||||
@ -107,35 +110,37 @@ public class CCharToGlyphMapper extends CharToGlyphMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public synchronized int charToGlyph(int unicode) {
|
public synchronized int charToGlyph(int unicode) {
|
||||||
|
return charToGlyph(unicode, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int charToGlyphRaw(int unicode) {
|
||||||
|
return charToGlyph(unicode, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int charToGlyph(int unicode, boolean raw) {
|
||||||
if (unicode >= 0x10000) {
|
if (unicode >= 0x10000) {
|
||||||
int[] glyphs = new int[2];
|
int[] glyphs = new int[2];
|
||||||
char[] surrogates = new char[2];
|
char[] surrogates = new char[2];
|
||||||
int base = unicode - 0x10000;
|
int base = unicode - 0x10000;
|
||||||
surrogates[0] = (char)((base >>> 10) + HI_SURROGATE_START);
|
surrogates[0] = (char)((base >>> 10) + HI_SURROGATE_START);
|
||||||
surrogates[1] = (char)((base % 0x400) + LO_SURROGATE_START);
|
surrogates[1] = (char)((base % 0x400) + LO_SURROGATE_START);
|
||||||
charsToGlyphs(2, surrogates, glyphs);
|
cache.get(2, surrogates, glyphs, raw);
|
||||||
return glyphs[0];
|
return glyphs[0];
|
||||||
} else {
|
} else {
|
||||||
return charToGlyph((char)unicode);
|
return charToGlyph((char) unicode, raw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void charsToGlyphs(int count, char[] unicodes, int[] glyphs) {
|
public synchronized void charsToGlyphs(int count, char[] unicodes, int[] glyphs) {
|
||||||
cache.get(count, unicodes, glyphs);
|
cache.get(count, unicodes, glyphs, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void charsToGlyphs(int count, int[] unicodes, int[] glyphs) {
|
public synchronized void charsToGlyphs(int count, int[] unicodes, int[] glyphs) {
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
glyphs[i] = charToGlyph(unicodes[i]);
|
glyphs[i] = charToGlyph(unicodes[i], false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matches behavior in e.g. CMap.getControlCodeGlyph(int, boolean)
|
|
||||||
// and RasterPrinterJob.removeControlChars(String)
|
|
||||||
private static boolean isIgnorableWhitespace(int code) {
|
|
||||||
return code == 0x0009 || code == 0x000a || code == 0x000d;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This mapper returns either the glyph code, or if the character can be
|
// This mapper returns either the glyph code, or if the character can be
|
||||||
// replaced on-the-fly using CoreText substitution; the negative unicode
|
// replaced on-the-fly using CoreText substitution; the negative unicode
|
||||||
// value. If this "glyph code int" is treated as an opaque code, it will
|
// value. If this "glyph code int" is treated as an opaque code, it will
|
||||||
@ -159,7 +164,11 @@ public class CCharToGlyphMapper extends CharToGlyphMapper {
|
|||||||
firstLayerCache[1] = 1;
|
firstLayerCache[1] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized int get(final int index) {
|
public synchronized int get(final int index, final boolean raw) {
|
||||||
|
if (isIgnorableWhitespace(index) || (isDefaultIgnorable(index) && !raw)) {
|
||||||
|
return INVISIBLE_GLYPH_ID;
|
||||||
|
}
|
||||||
|
|
||||||
if (index < FIRST_LAYER_SIZE) {
|
if (index < FIRST_LAYER_SIZE) {
|
||||||
// catch common glyphcodes
|
// catch common glyphcodes
|
||||||
return firstLayerCache[index];
|
return firstLayerCache[index];
|
||||||
@ -230,7 +239,7 @@ public class CCharToGlyphMapper extends CharToGlyphMapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void get(int count, char[] indices, int[] values)
|
public synchronized void get(int count, char[] indices, int[] values, boolean raw)
|
||||||
{
|
{
|
||||||
// "missed" is the count of 'char' that are not mapped.
|
// "missed" is the count of 'char' that are not mapped.
|
||||||
// Surrogates count for 2.
|
// Surrogates count for 2.
|
||||||
@ -252,16 +261,13 @@ public class CCharToGlyphMapper extends CharToGlyphMapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final int value = get(code);
|
final int value = get(code, raw);
|
||||||
if (value != 0 && value != -1) {
|
if (value != 0 && value != -1) {
|
||||||
values[i] = value;
|
values[i] = value;
|
||||||
if (code >= 0x10000) {
|
if (code >= 0x10000) {
|
||||||
values[i+1] = INVISIBLE_GLYPH_ID;
|
values[i+1] = INVISIBLE_GLYPH_ID;
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
} else if (FontUtilities.isDefaultIgnorable(code) || isIgnorableWhitespace(code)) {
|
|
||||||
values[i] = INVISIBLE_GLYPH_ID;
|
|
||||||
put(code, INVISIBLE_GLYPH_ID);
|
|
||||||
} else {
|
} else {
|
||||||
values[i] = 0;
|
values[i] = 0;
|
||||||
put(code, -1);
|
put(code, -1);
|
||||||
|
@ -546,9 +546,8 @@ abstract class CMap {
|
|||||||
int index = 0;
|
int index = 0;
|
||||||
char glyphCode = 0;
|
char glyphCode = 0;
|
||||||
|
|
||||||
int controlGlyph = getControlCodeGlyph(charCode, true);
|
if (isSurrogate(charCode)) {
|
||||||
if (controlGlyph >= 0) {
|
return 0;
|
||||||
return (char)controlGlyph;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* presence of translation array indicates that this
|
/* presence of translation array indicates that this
|
||||||
@ -633,13 +632,6 @@ abstract class CMap {
|
|||||||
|
|
||||||
char getGlyph(int charCode) {
|
char getGlyph(int charCode) {
|
||||||
if (charCode < 256) {
|
if (charCode < 256) {
|
||||||
if (charCode < 0x0010) {
|
|
||||||
switch (charCode) {
|
|
||||||
case 0x0009:
|
|
||||||
case 0x000a:
|
|
||||||
case 0x000d: return CharToGlyphMapper.INVISIBLE_GLYPH_ID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (char)(0xff & cmap[charCode]);
|
return (char)(0xff & cmap[charCode]);
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
@ -778,10 +770,8 @@ abstract class CMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char getGlyph(int charCode) {
|
char getGlyph(int charCode) {
|
||||||
final int origCharCode = charCode;
|
if (isSurrogate(charCode)) {
|
||||||
int controlGlyph = getControlCodeGlyph(charCode, true);
|
return 0;
|
||||||
if (controlGlyph >= 0) {
|
|
||||||
return (char)controlGlyph;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (xlat != null) {
|
if (xlat != null) {
|
||||||
@ -858,10 +848,8 @@ abstract class CMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char getGlyph(int charCode) {
|
char getGlyph(int charCode) {
|
||||||
final int origCharCode = charCode;
|
if (isSurrogate(charCode)) {
|
||||||
int controlGlyph = getControlCodeGlyph(charCode, true);
|
return 0;
|
||||||
if (controlGlyph >= 0) {
|
|
||||||
return (char)controlGlyph;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (xlat != null) {
|
if (xlat != null) {
|
||||||
@ -1020,11 +1008,6 @@ abstract class CMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char getGlyph(int charCode) {
|
char getGlyph(int charCode) {
|
||||||
final int origCharCode = charCode;
|
|
||||||
int controlGlyph = getControlCodeGlyph(charCode, false);
|
|
||||||
if (controlGlyph >= 0) {
|
|
||||||
return (char)controlGlyph;
|
|
||||||
}
|
|
||||||
int probe = power;
|
int probe = power;
|
||||||
int range = 0;
|
int range = 0;
|
||||||
|
|
||||||
@ -1060,17 +1043,8 @@ abstract class CMap {
|
|||||||
|
|
||||||
public static final NullCMapClass theNullCmap = new NullCMapClass();
|
public static final NullCMapClass theNullCmap = new NullCMapClass();
|
||||||
|
|
||||||
final int getControlCodeGlyph(int charCode, boolean noSurrogates) {
|
private static boolean isSurrogate(int charCode) {
|
||||||
if (charCode < 0x0010) {
|
return charCode >= 0xFFFF;
|
||||||
switch (charCode) {
|
|
||||||
case 0x0009:
|
|
||||||
case 0x000a:
|
|
||||||
case 0x000d: return CharToGlyphMapper.INVISIBLE_GLYPH_ID;
|
|
||||||
}
|
|
||||||
} else if (noSurrogates && charCode >= 0xFFFF) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static class UVS {
|
static class UVS {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2003, 2006, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -86,6 +86,13 @@ public abstract class CharToGlyphMapper {
|
|||||||
return charToGlyph(unicode);
|
return charToGlyph(unicode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int charToVariationGlyphRaw(int unicode, int variationSelector) {
|
||||||
|
// Override this if variation selector is supported.
|
||||||
|
return charToGlyphRaw(unicode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract int charToGlyphRaw(int unicode);
|
||||||
|
|
||||||
public abstract int getNumGlyphs();
|
public abstract int getNumGlyphs();
|
||||||
|
|
||||||
public abstract void charsToGlyphs(int count,
|
public abstract void charsToGlyphs(int count,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -42,6 +42,9 @@ package sun.font;
|
|||||||
* this appears to cause problems.
|
* this appears to cause problems.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import static sun.font.FontUtilities.isDefaultIgnorable;
|
||||||
|
import static sun.font.FontUtilities.isIgnorableWhitespace;
|
||||||
|
|
||||||
public class CompositeGlyphMapper extends CharToGlyphMapper {
|
public class CompositeGlyphMapper extends CharToGlyphMapper {
|
||||||
|
|
||||||
public static final int SLOTMASK = 0xff000000;
|
public static final int SLOTMASK = 0xff000000;
|
||||||
@ -51,7 +54,6 @@ public class CompositeGlyphMapper extends CharToGlyphMapper {
|
|||||||
public static final int BLOCKSZ = 256;
|
public static final int BLOCKSZ = 256;
|
||||||
public static final int MAXUNICODE = NBLOCKS*BLOCKSZ;
|
public static final int MAXUNICODE = NBLOCKS*BLOCKSZ;
|
||||||
|
|
||||||
|
|
||||||
CompositeFont font;
|
CompositeFont font;
|
||||||
CharToGlyphMapper[] slotMappers;
|
CharToGlyphMapper[] slotMappers;
|
||||||
int[][] glyphMaps;
|
int[][] glyphMaps;
|
||||||
@ -117,12 +119,18 @@ public class CompositeGlyphMapper extends CharToGlyphMapper {
|
|||||||
return mapper;
|
return mapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int convertToGlyph(int unicode) {
|
private int getGlyph(int unicode, boolean raw) {
|
||||||
|
if (isIgnorableWhitespace(unicode) || (isDefaultIgnorable(unicode) && !raw)) {
|
||||||
|
return INVISIBLE_GLYPH_ID;
|
||||||
|
}
|
||||||
|
int glyphCode = getCachedGlyphCode(unicode);
|
||||||
|
if (glyphCode != UNINITIALIZED_GLYPH) {
|
||||||
|
return glyphCode;
|
||||||
|
}
|
||||||
for (int slot = 0; slot < font.numSlots; slot++) {
|
for (int slot = 0; slot < font.numSlots; slot++) {
|
||||||
if (!hasExcludes || !font.isExcludedChar(slot, unicode)) {
|
if (!hasExcludes || !font.isExcludedChar(slot, unicode)) {
|
||||||
CharToGlyphMapper mapper = getSlotMapper(slot);
|
CharToGlyphMapper mapper = getSlotMapper(slot);
|
||||||
int glyphCode = mapper.charToGlyph(unicode);
|
glyphCode = mapper.charToGlyphRaw(unicode);
|
||||||
if (glyphCode != mapper.getMissingGlyphCode()) {
|
if (glyphCode != mapper.getMissingGlyphCode()) {
|
||||||
glyphCode = compositeGlyphCode(slot, glyphCode);
|
glyphCode = compositeGlyphCode(slot, glyphCode);
|
||||||
setCachedGlyphCode(unicode, glyphCode);
|
setCachedGlyphCode(unicode, glyphCode);
|
||||||
@ -155,12 +163,13 @@ public class CompositeGlyphMapper extends CharToGlyphMapper {
|
|||||||
return numGlyphs;
|
return numGlyphs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int charToGlyph(int unicode) {
|
public int charToGlyphRaw(int unicode) {
|
||||||
|
int glyphCode = getGlyph(unicode, true);
|
||||||
int glyphCode = getCachedGlyphCode(unicode);
|
return glyphCode;
|
||||||
if (glyphCode == UNINITIALIZED_GLYPH) {
|
|
||||||
glyphCode = convertToGlyph(unicode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int charToGlyph(int unicode) {
|
||||||
|
int glyphCode = getGlyph(unicode, false);
|
||||||
return glyphCode;
|
return glyphCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,11 +185,7 @@ public class CompositeGlyphMapper extends CharToGlyphMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int charToGlyph(char unicode) {
|
public int charToGlyph(char unicode) {
|
||||||
|
int glyphCode = getGlyph(unicode, false);
|
||||||
int glyphCode = getCachedGlyphCode(unicode);
|
|
||||||
if (glyphCode == UNINITIALIZED_GLYPH) {
|
|
||||||
glyphCode = convertToGlyph(unicode);
|
|
||||||
}
|
|
||||||
return glyphCode;
|
return glyphCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,10 +211,7 @@ public class CompositeGlyphMapper extends CharToGlyphMapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int gc = glyphs[i] = getCachedGlyphCode(code);
|
glyphs[i] = getGlyph(code, false);
|
||||||
if (gc == UNINITIALIZED_GLYPH) {
|
|
||||||
glyphs[i] = convertToGlyph(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (code < FontUtilities.MIN_LAYOUT_CHARCODE) {
|
if (code < FontUtilities.MIN_LAYOUT_CHARCODE) {
|
||||||
continue;
|
continue;
|
||||||
@ -243,31 +245,21 @@ public class CompositeGlyphMapper extends CharToGlyphMapper {
|
|||||||
code = (code - HI_SURROGATE_START) *
|
code = (code - HI_SURROGATE_START) *
|
||||||
0x400 + low - LO_SURROGATE_START + 0x10000;
|
0x400 + low - LO_SURROGATE_START + 0x10000;
|
||||||
|
|
||||||
int gc = glyphs[i] = getCachedGlyphCode(code);
|
glyphs[i] = getGlyph(code, false);
|
||||||
if (gc == UNINITIALIZED_GLYPH) {
|
|
||||||
glyphs[i] = convertToGlyph(code);
|
|
||||||
}
|
|
||||||
i += 1; // Empty glyph slot after surrogate
|
i += 1; // Empty glyph slot after surrogate
|
||||||
glyphs[i] = INVISIBLE_GLYPH_ID;
|
glyphs[i] = INVISIBLE_GLYPH_ID;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int gc = glyphs[i] = getCachedGlyphCode(code);
|
glyphs[i] = getGlyph(code, false);
|
||||||
if (gc == UNINITIALIZED_GLYPH) {
|
|
||||||
glyphs[i] = convertToGlyph(code);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void charsToGlyphs(int count, int[] unicodes, int[] glyphs) {
|
public void charsToGlyphs(int count, int[] unicodes, int[] glyphs) {
|
||||||
for (int i=0; i<count; i++) {
|
for (int i=0; i<count; i++) {
|
||||||
int code = unicodes[i];
|
int code = unicodes[i];
|
||||||
|
glyphs[i] = getGlyph(code, false);
|
||||||
glyphs[i] = getCachedGlyphCode(code);
|
|
||||||
if (glyphs[i] == UNINITIALIZED_GLYPH) {
|
|
||||||
glyphs[i] = convertToGlyph(code);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -535,6 +535,14 @@ public abstract class Font2D {
|
|||||||
return getMapper().charToVariationGlyph(wchar, variationSelector);
|
return getMapper().charToVariationGlyph(wchar, variationSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int charToGlyphRaw(int wchar) {
|
||||||
|
return getMapper().charToGlyphRaw(wchar);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int charToVariationGlyphRaw(int wchar, int variationSelector) {
|
||||||
|
return getMapper().charToVariationGlyphRaw(wchar, variationSelector);
|
||||||
|
}
|
||||||
|
|
||||||
public int getMissingGlyphCode() {
|
public int getMissingGlyphCode() {
|
||||||
return getMapper().getMissingGlyphCode();
|
return getMapper().getMissingGlyphCode();
|
||||||
}
|
}
|
||||||
|
@ -369,6 +369,20 @@ public final class FontUtilities {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether or not the specified codepoint is whitespace which is
|
||||||
|
* ignorable at the shaping stage of text rendering. These ignorable
|
||||||
|
* whitespace characters should be used prior to text shaping and
|
||||||
|
* rendering to determine the position of the text, but are not themselves
|
||||||
|
* rendered.
|
||||||
|
*
|
||||||
|
* @param ch the codepoint to check
|
||||||
|
* @return whether the specified codepoint is ignorable whitespace
|
||||||
|
*/
|
||||||
|
public static boolean isIgnorableWhitespace(int ch) {
|
||||||
|
return ch == 0x0009 || ch == 0x000a || ch == 0x000d;
|
||||||
|
}
|
||||||
|
|
||||||
public static PlatformLogger getLogger() {
|
public static PlatformLogger getLogger() {
|
||||||
return logger;
|
return logger;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -338,7 +338,7 @@ public class HBShaper {
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
Font2D font2D = scopedVars.get().font();
|
Font2D font2D = scopedVars.get().font();
|
||||||
int glyphID = font2D.charToGlyph(unicode);
|
int glyphID = font2D.charToGlyphRaw(unicode);
|
||||||
@SuppressWarnings("restricted")
|
@SuppressWarnings("restricted")
|
||||||
MemorySegment glyphIDPtr = glyph.reinterpret(4);
|
MemorySegment glyphIDPtr = glyph.reinterpret(4);
|
||||||
glyphIDPtr.setAtIndex(JAVA_INT, 0, glyphID);
|
glyphIDPtr.setAtIndex(JAVA_INT, 0, glyphID);
|
||||||
@ -354,7 +354,7 @@ public class HBShaper {
|
|||||||
MemorySegment user_data /* Not used */
|
MemorySegment user_data /* Not used */
|
||||||
) {
|
) {
|
||||||
Font2D font2D = scopedVars.get().font();
|
Font2D font2D = scopedVars.get().font();
|
||||||
int glyphID = font2D.charToVariationGlyph(unicode, variation_selector);
|
int glyphID = font2D.charToVariationGlyphRaw(unicode, variation_selector);
|
||||||
@SuppressWarnings("restricted")
|
@SuppressWarnings("restricted")
|
||||||
MemorySegment glyphIDPtr = glyph.reinterpret(4);
|
MemorySegment glyphIDPtr = glyph.reinterpret(4);
|
||||||
glyphIDPtr.setAtIndex(JAVA_INT, 0, glyphID);
|
glyphIDPtr.setAtIndex(JAVA_INT, 0, glyphID);
|
||||||
|
@ -28,6 +28,9 @@ package sun.font;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import static sun.font.FontUtilities.isDefaultIgnorable;
|
||||||
|
import static sun.font.FontUtilities.isIgnorableWhitespace;
|
||||||
|
|
||||||
public class TrueTypeGlyphMapper extends CharToGlyphMapper {
|
public class TrueTypeGlyphMapper extends CharToGlyphMapper {
|
||||||
|
|
||||||
TrueTypeFont font;
|
TrueTypeFont font;
|
||||||
@ -57,8 +60,8 @@ public class TrueTypeGlyphMapper extends CharToGlyphMapper {
|
|||||||
return numGlyphs;
|
return numGlyphs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private char getGlyphFromCMAP(int charCode) {
|
private char getGlyphFromCMAP(int charCode, boolean raw) {
|
||||||
if (FontUtilities.isDefaultIgnorable(charCode)) {
|
if (isIgnorableWhitespace(charCode) || (isDefaultIgnorable(charCode) && !raw)) {
|
||||||
return INVISIBLE_GLYPH_ID;
|
return INVISIBLE_GLYPH_ID;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@ -80,11 +83,11 @@ public class TrueTypeGlyphMapper extends CharToGlyphMapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private char getGlyphFromCMAP(int charCode, int variationSelector) {
|
private char getGlyphFromCMAP(int charCode, int variationSelector, boolean raw) {
|
||||||
if (variationSelector == 0) {
|
if (variationSelector == 0) {
|
||||||
return getGlyphFromCMAP(charCode);
|
return getGlyphFromCMAP(charCode, raw);
|
||||||
}
|
}
|
||||||
if (FontUtilities.isDefaultIgnorable(charCode)) {
|
if (isIgnorableWhitespace(charCode) || (isDefaultIgnorable(charCode) && !raw)) {
|
||||||
return INVISIBLE_GLYPH_ID;
|
return INVISIBLE_GLYPH_ID;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@ -122,25 +125,36 @@ public class TrueTypeGlyphMapper extends CharToGlyphMapper {
|
|||||||
cmap = CMap.theNullCmap;
|
cmap = CMap.theNullCmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int charToGlyphRaw(int unicode) {
|
||||||
|
int glyph = getGlyphFromCMAP(unicode, true);
|
||||||
|
return glyph;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int charToVariationGlyphRaw(int unicode, int variationSelector) {
|
||||||
|
int glyph = getGlyphFromCMAP(unicode, variationSelector, true);
|
||||||
|
return glyph;
|
||||||
|
}
|
||||||
|
|
||||||
public int charToGlyph(char unicode) {
|
public int charToGlyph(char unicode) {
|
||||||
int glyph = getGlyphFromCMAP(unicode);
|
int glyph = getGlyphFromCMAP(unicode, false);
|
||||||
return glyph;
|
return glyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int charToGlyph(int unicode) {
|
public int charToGlyph(int unicode) {
|
||||||
int glyph = getGlyphFromCMAP(unicode);
|
int glyph = getGlyphFromCMAP(unicode, false);
|
||||||
return glyph;
|
return glyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int charToVariationGlyph(int unicode, int variationSelector) {
|
public int charToVariationGlyph(int unicode, int variationSelector) {
|
||||||
int glyph = getGlyphFromCMAP(unicode, variationSelector);
|
int glyph = getGlyphFromCMAP(unicode, variationSelector, false);
|
||||||
return glyph;
|
return glyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void charsToGlyphs(int count, int[] unicodes, int[] glyphs) {
|
public void charsToGlyphs(int count, int[] unicodes, int[] glyphs) {
|
||||||
for (int i=0;i<count;i++) {
|
for (int i=0;i<count;i++) {
|
||||||
glyphs[i] = getGlyphFromCMAP(unicodes[i]);
|
glyphs[i] = getGlyphFromCMAP(unicodes[i], false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,13 +172,13 @@ public class TrueTypeGlyphMapper extends CharToGlyphMapper {
|
|||||||
code = (code - HI_SURROGATE_START) *
|
code = (code - HI_SURROGATE_START) *
|
||||||
0x400 + low - LO_SURROGATE_START + 0x10000;
|
0x400 + low - LO_SURROGATE_START + 0x10000;
|
||||||
|
|
||||||
glyphs[i] = getGlyphFromCMAP(code);
|
glyphs[i] = getGlyphFromCMAP(code, false);
|
||||||
i += 1; // Empty glyph slot after surrogate
|
i += 1; // Empty glyph slot after surrogate
|
||||||
glyphs[i] = INVISIBLE_GLYPH_ID;
|
glyphs[i] = INVISIBLE_GLYPH_ID;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
glyphs[i] = getGlyphFromCMAP(code);
|
glyphs[i] = getGlyphFromCMAP(code, false);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -191,7 +205,7 @@ public class TrueTypeGlyphMapper extends CharToGlyphMapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
glyphs[i] = getGlyphFromCMAP(code);
|
glyphs[i] = getGlyphFromCMAP(code, false);
|
||||||
|
|
||||||
if (code < FontUtilities.MIN_LAYOUT_CHARCODE) {
|
if (code < FontUtilities.MIN_LAYOUT_CHARCODE) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -31,6 +31,9 @@ package sun.font;
|
|||||||
* in composites will be cached there.
|
* in composites will be cached there.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import static sun.font.FontUtilities.isDefaultIgnorable;
|
||||||
|
import static sun.font.FontUtilities.isIgnorableWhitespace;
|
||||||
|
|
||||||
public final class Type1GlyphMapper extends CharToGlyphMapper {
|
public final class Type1GlyphMapper extends CharToGlyphMapper {
|
||||||
|
|
||||||
Type1Font font;
|
Type1Font font;
|
||||||
@ -78,7 +81,7 @@ public final class Type1GlyphMapper extends CharToGlyphMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int charToGlyph(char ch) {
|
public int charToGlyph(char ch) {
|
||||||
if (FontUtilities.isDefaultIgnorable(ch) || isIgnorableWhitespace(ch)) {
|
if (isIgnorableWhitespace(ch) || isDefaultIgnorable(ch)) { // raw = false
|
||||||
return INVISIBLE_GLYPH_ID;
|
return INVISIBLE_GLYPH_ID;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@ -90,10 +93,20 @@ public final class Type1GlyphMapper extends CharToGlyphMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int charToGlyph(int ch) {
|
public int charToGlyph(int ch) {
|
||||||
|
int glyph = charToGlyph(ch, false);
|
||||||
|
return glyph;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int charToGlyphRaw(int ch) {
|
||||||
|
int glyph = charToGlyph(ch, true);
|
||||||
|
return glyph;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int charToGlyph(int ch, boolean raw) {
|
||||||
if (ch < 0 || ch > 0xffff) {
|
if (ch < 0 || ch > 0xffff) {
|
||||||
return missingGlyph;
|
return missingGlyph;
|
||||||
} else {
|
} else {
|
||||||
if (FontUtilities.isDefaultIgnorable(ch) || isIgnorableWhitespace(ch)) {
|
if (isIgnorableWhitespace(ch) || (isDefaultIgnorable(ch) && !raw)) {
|
||||||
return INVISIBLE_GLYPH_ID;
|
return INVISIBLE_GLYPH_ID;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@ -105,13 +118,6 @@ public final class Type1GlyphMapper extends CharToGlyphMapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matches behavior in e.g. CMap.getControlCodeGlyph(int, boolean)
|
|
||||||
// and RasterPrinterJob.removeControlChars(String)
|
|
||||||
// and CCharToGlyphMapper.isIgnorableWhitespace(int)
|
|
||||||
private static boolean isIgnorableWhitespace(int code) {
|
|
||||||
return code == 0x0009 || code == 0x000a || code == 0x000d;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void charsToGlyphs(int count, char[] unicodes, int[] glyphs) {
|
public void charsToGlyphs(int count, char[] unicodes, int[] glyphs) {
|
||||||
/* The conversion into surrogates is misleading.
|
/* The conversion into surrogates is misleading.
|
||||||
* The Type1 glyph mapper only accepts 16 bit unsigned shorts.
|
* The Type1 glyph mapper only accepts 16 bit unsigned shorts.
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -90,6 +90,8 @@ import javax.print.attribute.standard.RequestingUserName;
|
|||||||
import javax.print.attribute.standard.SheetCollate;
|
import javax.print.attribute.standard.SheetCollate;
|
||||||
import javax.print.attribute.standard.Sides;
|
import javax.print.attribute.standard.Sides;
|
||||||
|
|
||||||
|
import static sun.font.FontUtilities.isIgnorableWhitespace;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class which rasterizes a printer job.
|
* A class which rasterizes a printer job.
|
||||||
*
|
*
|
||||||
@ -2482,7 +2484,7 @@ public abstract class RasterPrinterJob extends PrinterJob {
|
|||||||
|
|
||||||
for (int i = 0; i < len; i++) {
|
for (int i = 0; i < len; i++) {
|
||||||
char c = in_chars[i];
|
char c = in_chars[i];
|
||||||
if (c > '\r' || c < '\t' || c == '\u000b' || c == '\u000c') {
|
if (!isIgnorableWhitespace(c)) {
|
||||||
out_chars[pos++] = c;
|
out_chars[pos++] = c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2007, 2024, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2007, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -143,9 +143,9 @@ static void initFontIDs(JNIEnv *env) {
|
|||||||
|
|
||||||
CHECK_NULL(tmpClass = (*env)->FindClass(env, "sun/font/Font2D"));
|
CHECK_NULL(tmpClass = (*env)->FindClass(env, "sun/font/Font2D"));
|
||||||
CHECK_NULL(sunFontIDs.f2dCharToGlyphMID =
|
CHECK_NULL(sunFontIDs.f2dCharToGlyphMID =
|
||||||
(*env)->GetMethodID(env, tmpClass, "charToGlyph", "(I)I"));
|
(*env)->GetMethodID(env, tmpClass, "charToGlyphRaw", "(I)I"));
|
||||||
CHECK_NULL(sunFontIDs.f2dCharToVariationGlyphMID =
|
CHECK_NULL(sunFontIDs.f2dCharToVariationGlyphMID =
|
||||||
(*env)->GetMethodID(env, tmpClass, "charToVariationGlyph", "(II)I"));
|
(*env)->GetMethodID(env, tmpClass, "charToVariationGlyphRaw", "(II)I"));
|
||||||
CHECK_NULL(sunFontIDs.getMapperMID =
|
CHECK_NULL(sunFontIDs.getMapperMID =
|
||||||
(*env)->GetMethodID(env, tmpClass, "getMapper",
|
(*env)->GetMethodID(env, tmpClass, "getMapper",
|
||||||
"()Lsun/font/CharToGlyphMapper;"));
|
"()Lsun/font/CharToGlyphMapper;"));
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2003, 2005, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -75,6 +75,10 @@ public class NativeGlyphMapper extends CharToGlyphMapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int charToGlyphRaw(int unicode) {
|
||||||
|
return charToGlyph(unicode);
|
||||||
|
}
|
||||||
|
|
||||||
public void charsToGlyphs(int count, char[] unicodes, int[] glyphs) {
|
public void charsToGlyphs(int count, char[] unicodes, int[] glyphs) {
|
||||||
for (int i=0; i<count; i++) {
|
for (int i=0; i<count; i++) {
|
||||||
char code = unicodes[i];
|
char code = unicodes[i];
|
||||||
|
164
test/jdk/java/awt/font/GlyphVector/GlyphVectorGsubTest.java
Normal file
164
test/jdk/java/awt/font/GlyphVector/GlyphVectorGsubTest.java
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @bug 8353230
|
||||||
|
* @summary Regression test for TrueType font GSUB substitutions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.font.FontRenderContext;
|
||||||
|
import java.awt.font.GlyphVector;
|
||||||
|
import java.awt.font.TextAttribute;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class GlyphVectorGsubTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Font created for this test which contains two GSUB substitutions: a
|
||||||
|
* "liga" ligature for "a" + "b" which requires that the ligature support
|
||||||
|
* be enabled, and a "ccmp" ligature for an emoji sequence which does not
|
||||||
|
* require that ligatures be explicitly enabled.
|
||||||
|
*
|
||||||
|
* <p>The following FontForge Python script was used to generate this font:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* import fontforge
|
||||||
|
* import base64
|
||||||
|
*
|
||||||
|
* def draw(glyph, width, height):
|
||||||
|
* pen = glyph.glyphPen()
|
||||||
|
* pen.moveTo((100, 100))
|
||||||
|
* pen.lineTo((100, 100 + height))
|
||||||
|
* pen.lineTo((100 + width, 100 + height))
|
||||||
|
* pen.lineTo((100 + width, 100))
|
||||||
|
* pen.closePath()
|
||||||
|
* glyph.draw(pen)
|
||||||
|
* pen = None
|
||||||
|
*
|
||||||
|
* font = fontforge.font()
|
||||||
|
* font.encoding = 'UnicodeFull'
|
||||||
|
* font.design_size = 16
|
||||||
|
* font.em = 2048
|
||||||
|
* font.ascent = 1638
|
||||||
|
* font.descent = 410
|
||||||
|
* font.familyname = 'Test'
|
||||||
|
* font.fontname = 'Test'
|
||||||
|
* font.fullname = 'Test'
|
||||||
|
* font.copyright = ''
|
||||||
|
* font.autoWidth(0, 0, 2048)
|
||||||
|
*
|
||||||
|
* font.addLookup('ligatures', 'gsub_ligature', (), (('liga',(('latn',('dflt')),)),))
|
||||||
|
* font.addLookupSubtable('ligatures', 'sub1')
|
||||||
|
*
|
||||||
|
* font.addLookup('sequences', 'gsub_ligature', (), (('ccmp',(('latn',('dflt')),)),))
|
||||||
|
* font.addLookupSubtable('sequences', 'sub2')
|
||||||
|
*
|
||||||
|
* space = font.createChar(0x20)
|
||||||
|
* space.width = 600
|
||||||
|
*
|
||||||
|
* # create glyphs: a, b, ab
|
||||||
|
*
|
||||||
|
* for char in list('ab'):
|
||||||
|
* glyph = font.createChar(ord(char))
|
||||||
|
* draw(glyph, 400, 100)
|
||||||
|
* glyph.width = 600
|
||||||
|
*
|
||||||
|
* ab = font.createChar(-1, 'ab')
|
||||||
|
* ab.addPosSub('sub1', ('a', 'b'))
|
||||||
|
* draw(ab, 400, 400)
|
||||||
|
* ab.width = 600
|
||||||
|
*
|
||||||
|
* # create glyphs for "woman" emoji sequence
|
||||||
|
*
|
||||||
|
* components = []
|
||||||
|
* woman = '\U0001F471\U0001F3FD\u200D\u2640\uFE0F'
|
||||||
|
* for char in list(woman):
|
||||||
|
* glyph = font.createChar(ord(char))
|
||||||
|
* draw(glyph, 400, 800)
|
||||||
|
* glyph.width = 600
|
||||||
|
* components.append(glyph.glyphname)
|
||||||
|
*
|
||||||
|
* del components[-1] # remove last
|
||||||
|
* seq = font.createChar(-1, 'seq')
|
||||||
|
* seq.addPosSub('sub2', components)
|
||||||
|
* draw(seq, 400, 1200)
|
||||||
|
* seq.width = 600
|
||||||
|
*
|
||||||
|
* # save font to file
|
||||||
|
*
|
||||||
|
* ttf = 'test.ttf' # TrueType
|
||||||
|
* t64 = 'test.ttf.txt' # TrueType Base64
|
||||||
|
*
|
||||||
|
* font.generate(ttf)
|
||||||
|
*
|
||||||
|
* with open(ttf, 'rb') as f1:
|
||||||
|
* encoded = base64.b64encode(f1.read())
|
||||||
|
* with open(t64, 'wb') as f2:
|
||||||
|
* f2.write(encoded)
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
private static final String TTF_BYTES = "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";
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
|
||||||
|
byte[] ttfBytes = Base64.getDecoder().decode(TTF_BYTES);
|
||||||
|
ByteArrayInputStream ttfStream = new ByteArrayInputStream(ttfBytes);
|
||||||
|
Font f1 = Font.createFont(Font.TRUETYPE_FONT, ttfStream).deriveFont(80f);
|
||||||
|
|
||||||
|
// Test emoji sequence, using "ccmp" feature and ZWJ (zero-width joiner):
|
||||||
|
// - person with blonde hair
|
||||||
|
// - emoji modifier fitzpatrick type 4
|
||||||
|
// - zero-width joiner
|
||||||
|
// - female sign
|
||||||
|
// - variation selector 16
|
||||||
|
// Does not require the use of the TextAttribute.LIGATURES_ON attribute.
|
||||||
|
char[] text1 = "\ud83d\udc71\ud83c\udffd\u200d\u2640\ufe0f".toCharArray();
|
||||||
|
FontRenderContext frc = new FontRenderContext(null, true, true);
|
||||||
|
GlyphVector gv1 = f1.layoutGlyphVector(frc, text1, 0, text1.length, 0);
|
||||||
|
checkOneGlyph(gv1, text1, 12);
|
||||||
|
|
||||||
|
// Test regular ligature, using "liga" feature: "ab" -> replacement
|
||||||
|
// Requires the use of the TextAttribute.LIGATURES_ON attribute.
|
||||||
|
char[] text2 = "ab".toCharArray();
|
||||||
|
Font f2 = f1.deriveFont(Map.of(TextAttribute.LIGATURES, TextAttribute.LIGATURES_ON));
|
||||||
|
GlyphVector gv2 = f2.layoutGlyphVector(frc, text2, 0, text2.length, 0);
|
||||||
|
checkOneGlyph(gv2, text2, 11);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkOneGlyph(GlyphVector gv, char[] text, int expectedCode) {
|
||||||
|
int glyphs = gv.getNumGlyphs();
|
||||||
|
if (glyphs != 1) {
|
||||||
|
throw new RuntimeException("Unexpected number of glyphs for text " +
|
||||||
|
new String(text) + ": " + glyphs);
|
||||||
|
}
|
||||||
|
int code = gv.getGlyphCode(0);
|
||||||
|
if (code != expectedCode) {
|
||||||
|
throw new RuntimeException("Unexpected glyph code for text " +
|
||||||
|
new String(text) + ": " + expectedCode + " != " + code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user