From d6c849a82457e342f8516818d45b7be2341e3b85 Mon Sep 17 00:00:00 2001 From: Toshio Nakamura Date: Mon, 25 Jun 2018 11:40:46 -0700 Subject: [PATCH] 8187100: Support Unicode Variation Selectors Support Unicode Variation Selectors Reviewed-by: prr, srl --- .../share/classes/sun/font/CMap.java | 114 ++++++++++++++++++ .../classes/sun/font/CharToGlyphMapper.java | 14 +++ .../sun/font/CompositeGlyphMapper.java | 3 +- .../share/classes/sun/font/Font2D.java | 4 + .../classes/sun/font/TrueTypeGlyphMapper.java | 41 ++++++- .../share/native/common/font/sunfontids.h | 1 + .../native/libfontmanager/hb-jdk-font.cc | 16 ++- .../share/native/libfontmanager/sunFont.c | 2 + .../awt/font/TextLayout/TestVS-expect.png | Bin 0 -> 13035 bytes test/jdk/java/awt/font/TextLayout/TestVS.java | 92 ++++++++++++++ .../TextLayout/VariationSelectorTest.java | 65 ++++++++++ 11 files changed, 346 insertions(+), 6 deletions(-) create mode 100644 test/jdk/java/awt/font/TextLayout/TestVS-expect.png create mode 100644 test/jdk/java/awt/font/TextLayout/TestVS.java create mode 100644 test/jdk/java/awt/font/TextLayout/VariationSelectorTest.java diff --git a/src/java.desktop/share/classes/sun/font/CMap.java b/src/java.desktop/share/classes/sun/font/CMap.java index 410280cf142..cf06337f010 100644 --- a/src/java.desktop/share/classes/sun/font/CMap.java +++ b/src/java.desktop/share/classes/sun/font/CMap.java @@ -140,6 +140,7 @@ abstract class CMap { * Using this saves running character coverters repeatedly. */ char[] xlat; + UVS uvs = null; static CMap initialize(TrueTypeFont font) { @@ -149,6 +150,7 @@ abstract class CMap { int three0=0, three1=0, three2=0, three3=0, three4=0, three5=0, three6=0, three10=0; + int zero5 = 0; // for Unicode Variation Sequences boolean threeStar = false; ByteBuffer cmapBuffer = font.getTableBuffer(TrueTypeFont.cmapTag); @@ -173,6 +175,12 @@ abstract class CMap { case 6: three6 = offset; break; // Johab case 10: three10 = offset; break; // MS Unicode surrogates } + } else if (platformID == 0) { + encodingID = cmapBuffer.getShort(); + offset = cmapBuffer.getInt(); + if (encodingID == 5) { + zero5 = offset; + } } } @@ -262,6 +270,10 @@ abstract class CMap { */ cmap = createCMap(cmapBuffer, cmapBuffer.getInt(8), null); } + // For Unicode Variation Sequences + if (cmap != null && zero5 != 0) { + cmap.createUVS(cmapBuffer, zero5); + } return cmap; } @@ -424,6 +436,25 @@ abstract class CMap { } } + private void createUVS(ByteBuffer buffer, int offset) { + int subtableFormat = buffer.getChar(offset); + if (subtableFormat == 14) { + long subtableLength = buffer.getInt(offset + 2) & INTMASK; + if (offset + subtableLength > buffer.capacity()) { + if (FontUtilities.isLogging()) { + FontUtilities.getLogger() + .warning("Cmap UVS subtable overflows buffer."); + } + } + try { + this.uvs = new UVS(buffer, offset); + } catch (Throwable t) { + t.printStackTrace(); + } + } + return; + } + /* final char charVal(byte[] cmap, int index) { return (char)(((0xff & cmap[index]) << 8)+(0xff & cmap[index+1])); @@ -1059,4 +1090,87 @@ abstract class CMap { } return -1; } + + static class UVS { + int numSelectors; + int[] selector; + + //for Non-Default UVS Table + int[] numUVSMapping; + int[][] unicodeValue; + char[][] glyphID; + + UVS(ByteBuffer buffer, int offset) { + numSelectors = buffer.getInt(offset+6); + selector = new int[numSelectors]; + numUVSMapping = new int[numSelectors]; + unicodeValue = new int[numSelectors][]; + glyphID = new char[numSelectors][]; + + for (int i = 0; i < numSelectors; i++) { + buffer.position(offset + 10 + i * 11); + selector[i] = (buffer.get() & 0xff) << 16; //UINT24 + selector[i] += (buffer.get() & 0xff) << 8; + selector[i] += buffer.get() & 0xff; + + //skip Default UVS Table + + //for Non-Default UVS Table + int tableOffset = buffer.getInt(offset + 10 + i * 11 + 7); + if (tableOffset == 0) { + numUVSMapping[i] = 0; + } else if (tableOffset > 0) { + buffer.position(offset+tableOffset); + numUVSMapping[i] = buffer.getInt() & INTMASK; + unicodeValue[i] = new int[numUVSMapping[i]]; + glyphID[i] = new char[numUVSMapping[i]]; + + for (int j = 0; j < numUVSMapping[i]; j++) { + int temp = (buffer.get() & 0xff) << 16; //UINT24 + temp += (buffer.get() & 0xff) << 8; + temp += buffer.get() & 0xff; + unicodeValue[i][j] = temp; + glyphID[i][j] = buffer.getChar(); + } + } + } + } + + static final int VS_NOGLYPH = 0; + private int getGlyph(int charCode, int variationSelector) { + int targetSelector = -1; + for (int i = 0; i < numSelectors; i++) { + if (selector[i] == variationSelector) { + targetSelector = i; + break; + } + } + if (targetSelector == -1) { + return VS_NOGLYPH; + } + if (numUVSMapping[targetSelector] > 0) { + int index = java.util.Arrays.binarySearch( + unicodeValue[targetSelector], charCode); + if (index >= 0) { + return glyphID[targetSelector][index]; + } + } + return VS_NOGLYPH; + } + } + + char getVariationGlyph(int charCode, int variationSelector) { + char glyph = 0; + if (uvs == null) { + glyph = getGlyph(charCode); + } else { + int result = uvs.getGlyph(charCode, variationSelector); + if (result > 0) { + glyph = (char)(result & 0xFFFF); + } else { + glyph = getGlyph(charCode); + } + } + return glyph; + } } diff --git a/src/java.desktop/share/classes/sun/font/CharToGlyphMapper.java b/src/java.desktop/share/classes/sun/font/CharToGlyphMapper.java index f872a8b51af..bac385d81db 100644 --- a/src/java.desktop/share/classes/sun/font/CharToGlyphMapper.java +++ b/src/java.desktop/share/classes/sun/font/CharToGlyphMapper.java @@ -36,6 +36,10 @@ public abstract class CharToGlyphMapper { public static final int HI_SURROGATE_END = 0xDBFF; public static final int LO_SURROGATE_START = 0xDC00; public static final int LO_SURROGATE_END = 0xDFFF; + public static final int VS_START = 0xFE00; + public static final int VS_END = 0xFE0F; + public static final int VSS_START = 0xE0100; + public static final int VSS_END = 0xE01FF; public static final int UNINITIALIZED_GLYPH = -1; public static final int INVISIBLE_GLYPH_ID = 0xffff; @@ -77,6 +81,11 @@ public abstract class CharToGlyphMapper { return glyphs[0]; } + public int charToVariationGlyph(int unicode, int variationSelector) { + // Override this if variation selector is supported. + return charToGlyph(unicode); + } + public abstract int getNumGlyphs(); public abstract void charsToGlyphs(int count, @@ -88,4 +97,9 @@ public abstract class CharToGlyphMapper { public abstract void charsToGlyphs(int count, int[] unicodes, int[] glyphs); + public static boolean isVariationSelector(int charCode) { + return ((charCode >= VSS_START && charCode <= VSS_END) || + (charCode >= VS_START && charCode <= VS_END)); + } + } diff --git a/src/java.desktop/share/classes/sun/font/CompositeGlyphMapper.java b/src/java.desktop/share/classes/sun/font/CompositeGlyphMapper.java index 2445c6a638e..036e2fb855a 100644 --- a/src/java.desktop/share/classes/sun/font/CompositeGlyphMapper.java +++ b/src/java.desktop/share/classes/sun/font/CompositeGlyphMapper.java @@ -214,7 +214,8 @@ public class CompositeGlyphMapper extends CharToGlyphMapper { if (code < FontUtilities.MIN_LAYOUT_CHARCODE) { continue; } - else if (FontUtilities.isComplexCharCode(code)) { + else if (FontUtilities.isComplexCharCode(code) || + CharToGlyphMapper.isVariationSelector(code)) { return true; } else if (code >= 0x10000) { diff --git a/src/java.desktop/share/classes/sun/font/Font2D.java b/src/java.desktop/share/classes/sun/font/Font2D.java index 84fc658447f..e4cdc216d30 100644 --- a/src/java.desktop/share/classes/sun/font/Font2D.java +++ b/src/java.desktop/share/classes/sun/font/Font2D.java @@ -524,6 +524,10 @@ public abstract class Font2D { return getMapper().charToGlyph(wchar); } + public int charToVariationGlyph(int wchar, int variationSelector) { + return getMapper().charToVariationGlyph(wchar, variationSelector); + } + public int getMissingGlyphCode() { return getMapper().getMissingGlyphCode(); } diff --git a/src/java.desktop/share/classes/sun/font/TrueTypeGlyphMapper.java b/src/java.desktop/share/classes/sun/font/TrueTypeGlyphMapper.java index e16c55ad477..7c628300a4f 100644 --- a/src/java.desktop/share/classes/sun/font/TrueTypeGlyphMapper.java +++ b/src/java.desktop/share/classes/sun/font/TrueTypeGlyphMapper.java @@ -93,6 +93,32 @@ public class TrueTypeGlyphMapper extends CharToGlyphMapper { } } + private char getGlyphFromCMAP(int charCode, int variationSelector) { + if (variationSelector == 0) { + return getGlyphFromCMAP(charCode); + } + try { + char glyphCode = cmap.getVariationGlyph(charCode, + variationSelector); + if (glyphCode < numGlyphs || + glyphCode >= FileFontStrike.INVISIBLE_GLYPHS) { + return glyphCode; + } else { + if (FontUtilities.isLogging()) { + FontUtilities.getLogger().warning + (font + " out of range glyph id=" + + Integer.toHexString((int)glyphCode) + + " for char " + Integer.toHexString(charCode) + + " for vs " + Integer.toHexString(variationSelector)); + } + return (char)missingGlyph; + } + } catch (Exception e) { + handleBadCMAP(); + return (char) missingGlyph; + } + } + private void handleBadCMAP() { if (FontUtilities.isLogging()) { FontUtilities.getLogger().severe("Null Cmap for " + font + @@ -136,6 +162,18 @@ public class TrueTypeGlyphMapper extends CharToGlyphMapper { return glyph; } + @Override + public int charToVariationGlyph(int unicode, int variationSelector) { + if (needsJAremapping) { + unicode = remapJAIntChar(unicode); + } + int glyph = getGlyphFromCMAP(unicode, variationSelector); + if (font.checkUseNatives() && glyph < font.glyphToCharMap.length) { + font.glyphToCharMap[glyph] = (char)unicode; + } + return glyph; + } + public void charsToGlyphs(int count, int[] unicodes, int[] glyphs) { for (int i=0;i= 0x10000) { diff --git a/src/java.desktop/share/native/common/font/sunfontids.h b/src/java.desktop/share/native/common/font/sunfontids.h index 593fd4e68c9..2301f80b5bc 100644 --- a/src/java.desktop/share/native/common/font/sunfontids.h +++ b/src/java.desktop/share/native/common/font/sunfontids.h @@ -39,6 +39,7 @@ typedef struct FontManagerNativeIDs { jmethodID getTableBytesMID; jmethodID canDisplayMID; jmethodID f2dCharToGlyphMID; + jmethodID f2dCharToVariationGlyphMID; /* sun/font/CharToGlyphMapper methods */ jmethodID charToGlyphMID; diff --git a/src/java.desktop/share/native/libfontmanager/hb-jdk-font.cc b/src/java.desktop/share/native/libfontmanager/hb-jdk-font.cc index c2ecdb77bd0..19d070a8484 100644 --- a/src/java.desktop/share/native/libfontmanager/hb-jdk-font.cc +++ b/src/java.desktop/share/native/libfontmanager/hb-jdk-font.cc @@ -48,10 +48,18 @@ hb_jdk_get_glyph (hb_font_t *font HB_UNUSED, JDKFontInfo *jdkFontInfo = (JDKFontInfo*)font_data; JNIEnv* env = jdkFontInfo->env; jobject font2D = jdkFontInfo->font2D; - hb_codepoint_t u = (variation_selector==0) ? unicode : variation_selector; - - *glyph = (hb_codepoint_t) - env->CallIntMethod(font2D, sunFontIDs.f2dCharToGlyphMID, u); + if (variation_selector == 0) { + *glyph = (hb_codepoint_t)env->CallIntMethod( + font2D, sunFontIDs.f2dCharToGlyphMID, unicode); + } else { + *glyph = (hb_codepoint_t)env->CallIntMethod( + font2D, sunFontIDs.f2dCharToVariationGlyphMID, + unicode, variation_selector); + } + if (env->ExceptionOccurred()) + { + env->ExceptionClear(); + } if ((int)*glyph < 0) { *glyph = 0; } diff --git a/src/java.desktop/share/native/libfontmanager/sunFont.c b/src/java.desktop/share/native/libfontmanager/sunFont.c index c2ddb0a9dac..f53291e9c7d 100644 --- a/src/java.desktop/share/native/libfontmanager/sunFont.c +++ b/src/java.desktop/share/native/libfontmanager/sunFont.c @@ -144,6 +144,8 @@ static void initFontIDs(JNIEnv *env) { CHECK_NULL(tmpClass = (*env)->FindClass(env, "sun/font/Font2D")); CHECK_NULL(sunFontIDs.f2dCharToGlyphMID = (*env)->GetMethodID(env, tmpClass, "charToGlyph", "(I)I")); + CHECK_NULL(sunFontIDs.f2dCharToVariationGlyphMID = + (*env)->GetMethodID(env, tmpClass, "charToVariationGlyph", "(II)I")); CHECK_NULL(sunFontIDs.getMapperMID = (*env)->GetMethodID(env, tmpClass, "getMapper", "()Lsun/font/CharToGlyphMapper;")); diff --git a/test/jdk/java/awt/font/TextLayout/TestVS-expect.png b/test/jdk/java/awt/font/TextLayout/TestVS-expect.png new file mode 100644 index 0000000000000000000000000000000000000000..ec54cb69338eb40fbc0673405a861e3a4a537b1a GIT binary patch literal 13035 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 m`O7L@eSQSk(`7Ke3Pz#V+Fl#W)%^2!nY6gPSh { + JFrame frame = new JFrame(); + frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + frame.add(new MyComponent()); + frame.setSize(200, 200); + frame.setVisible(true); + frame.setLocationRelativeTo(null); + }); + } + + private static class MyComponent extends JComponent { + @Override + protected void paintComponent(Graphics g) { + Graphics2D g2d = (Graphics2D) g; + FontRenderContext frc = g2d.getFontRenderContext(); + String text = "a"; + GlyphVector gv = FONT.layoutGlyphVector( + frc, text.toCharArray(), 0, text.length(), + Font.LAYOUT_LEFT_TO_RIGHT); + System.out.println("'a'=" + gv.getNumGlyphs()); + g2d.drawString("=" + gv.getNumGlyphs() + " ('a')", 100, 50); + g2d.drawGlyphVector(gv, 80, 50); + String text2 = "a\ufe00"; + GlyphVector gv2 = FONT.layoutGlyphVector( + frc, text2.toCharArray(), 0, text2.length(), + Font.LAYOUT_LEFT_TO_RIGHT); + g2d.drawGlyphVector(gv2, 80, 100); + System.out.println("'a'+VS=" + gv2.getNumGlyphs()); + g2d.drawString("=" + gv2.getNumGlyphs() + " ('a'+VS)", 100, 100); + if ((gv.getNumGlyphs() == 1) && (gv2.getNumGlyphs() == 1)) { + System.out.println("PASS"); + g2d.drawString("PASS", 10, 15); + } else { + System.err.println("FAIL"); + g2d.drawString("FAIL", 10, 15); + } + } + } +}