From 55d85b5175296346266ebb78adbb038807a28f3f Mon Sep 17 00:00:00 2001 From: Akira Nakajima Date: Fri, 20 Jul 2018 12:07:21 +0900 Subject: [PATCH 1/2] rendering variation sequences (for Win,MAC only) --- .../com/sun/javafx/text/ScriptMapper.java | 12 ++- tests/manual/text/VariationSelectorsTest.java | 70 ++++++++++++++++++ .../text/VariationSelectorsTest_Expected.png | Bin 0 -> 13035 bytes 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 tests/manual/text/VariationSelectorsTest.java create mode 100644 tests/manual/text/VariationSelectorsTest_Expected.png diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/ScriptMapper.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/ScriptMapper.java index c439d13138..d847e63a06 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/ScriptMapper.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/ScriptMapper.java @@ -24,6 +24,7 @@ */ package com.sun.javafx.text; +import com.sun.javafx.PlatformUtil; public class ScriptMapper { @@ -80,7 +81,8 @@ public static synchronized int getScript(int codePoint) { * in the case where the caller interprets 'layout' to mean where * one 'char' (ie the java type char) does not map to one glyph */ - private static final int MAX_LAYOUT_CHARCODE = 0x206F; + private static final int MAX_LAYOUT_CHARCODE + = (PlatformUtil.isWindows() || PlatformUtil.isMac()) ? 0xE01EF : 0x206F; /* If the character code falls into any of a number of unicode ranges * where we know that simple left->right layout mapping chars to glyphs @@ -166,6 +168,14 @@ else if (code >= 0x202a && code <= 0x202e) { // directional control else if (code >= 0x206a && code <= 0x206f) { // directional control return true; } + else if (code >= 0xfe00 && code <= 0xfe0f) { + // SVS (Standardized Variation Sequence) + return true; + } + else if (code >= 0xe0100 && code <= 0xe01ef) { + // IVS (Ideographic Variation Sequence) + return true; + } return false; } diff --git a/tests/manual/text/VariationSelectorsTest.java b/tests/manual/text/VariationSelectorsTest.java new file mode 100644 index 0000000000..83b18531bd --- /dev/null +++ b/tests/manual/text/VariationSelectorsTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +import javafx.application.Application; +import static javafx.application.Application.launch; +import javafx.scene.Group; +import javafx.scene.Scene; +import javafx.scene.layout.VBox; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.stage.Stage; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; + +public class VariationSelectorsTest extends Application { + + public void start(Stage stage) { + final String fontName = "ipaexm.ttf"; + // download from https://ipafont.ipa.go.jp/node26#en + // and place in {user.home}/fonts/ + + final String base = System.getProperty("user.home")+"/fonts/"; + Font font = Font.loadFont("file://"+base+fontName, 48); + if (font == null || !"IPAexMincho".equals(font.getName())) { + System.err.println("# You need to place "+fontName+" in "+base); + System.exit(0); + } + + stage.setWidth(260); + stage.setHeight(150); + Group g = new Group(); + final Scene scene = new Scene(new Group()); + VBox box = new VBox(); + ((Group)scene.getRoot()).getChildren().add(box); + stage.setScene(scene); + + Text txt = new Text("\u845b\udb40\udd00\u845b\udb40\udd01"); + txt.setFont(font); + box.getChildren().add(txt); + + Image img = new Image("VariationSelectorsTest_Expected.png"); + ImageView iv = new ImageView(); + iv.setImage(img); + box.getChildren().add(iv); + + stage.show(); + } +} diff --git a/tests/manual/text/VariationSelectorsTest_Expected.png b/tests/manual/text/VariationSelectorsTest_Expected.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 Date: Tue, 7 Aug 2018 07:59:25 +0900 Subject: [PATCH 2/2] rendering variation sequences (for Linux) --- .../main/java/com/sun/javafx/font/CMap.java | 180 +++++++++++++++++- .../sun/javafx/font/CharToGlyphMapper.java | 113 ++++++++--- .../sun/javafx/font/CompositeGlyphMapper.java | 47 ++++- .../sun/javafx/font/OpenTypeGlyphMapper.java | 39 +++- .../java/com/sun/javafx/text/GlyphLayout.java | 2 +- 5 files changed, 339 insertions(+), 42 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/font/CMap.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/font/CMap.java index 233a633859..98befe8b71 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/font/CMap.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/font/CMap.java @@ -42,12 +42,13 @@ abstract class CMap { static final char noSuchChar = (char)0xfffd; + static final int BYTEMASK = 0x000000ff; static final int SHORTMASK = 0x0000ffff; static final int INTMASK = 0xffffffff; private static final int MAX_CODE_POINTS = 0x10ffff; - static CMap initialize(PrismFontFile font) { + static CMap initialize(PrismFontFile font, int[] offset_format, int create_cmap) { CMap cmap = null; @@ -59,6 +60,11 @@ static CMap initialize(PrismFontFile font) { Buffer cmapBuffer = font.readTable(FontConstants.cmapTag); short numberSubTables = cmapBuffer.getShort(2); + /* create CMap14 */ + if (create_cmap == 14 && offset_format[0] != 0) { + return createCMap(cmapBuffer, offset_format[0]); + } + /* Locate the offsets of supported 3,* Microsoft platform encodings, * and any 0,* Unicode platform encoding. The latter is used by * all current OS X fonts that don't have a Microsoft cmap. @@ -76,6 +82,9 @@ static CMap initialize(PrismFontFile font) { zeroStar = true; encodingID = cmapBuffer.getShort(); zeroStarOffset = cmapBuffer.getInt(); + if (encodingID == 5) { + offset_format[0] = zeroStarOffset; + } } else if (platformID == 3) { threeStar = true; @@ -133,6 +142,7 @@ static CMap createCMap(Buffer buffer, int offset) { case 8: return new CMapFormat8(buffer, offset); case 10: return new CMapFormat10(buffer, offset); case 12: return new CMapFormat12(buffer, offset); + case 14: return new CMapFormat14(buffer, offset); default: throw new RuntimeException("Cmap format unimplemented: " + (int)buffer.getChar(offset)); } @@ -140,6 +150,13 @@ static CMap createCMap(Buffer buffer, int offset) { abstract char getGlyph(int charCode); + char getGlyph(int charCode, int vs) { + return getGlyph(charCode); + } + + void setDefCMap(CMap defCmap) { + } + /* Format 4 Header is * ushort format (off=0) * ushort length (off=2) @@ -591,6 +608,167 @@ char getGlyph(int charCode) { } + // Format 14: Table for Variation Selector (SVS and IVS) + static class CMapFormat14 extends CMap { + + Buffer buffer; + int offset; + + int numSelector; + int[] varSelector; + + /* default glyphs */ + int[] defaultOff, numRanges; + int[][] defUniStart; + short[][] additionalCnt; + + /* non default glyphs */ + int[] nonDefOff, numMappings; + int[][] uniStart; + char[][] glyphID; + /* e.g. + * uniStart[numSelector-1] = U+fe00(=VS1) + * uniStart[numSelector-1][numMappings-1] = U+795e + * glyphID[numSelector-1][numMappings-1] = 12345 + */ + + CMap defCmap; + void setDefCMap(CMap cmap) { + this.defCmap = cmap; + } + + CMapFormat14(Buffer buffer, int offset) { + this.buffer = buffer; + this.offset = offset; + + buffer.position(offset+6); + /* get count of Variation Selector */ + numSelector = buffer.getInt(); + + varSelector = new int[numSelector]; // e.g. {0xfe00,0xfe01,0xe0100} + defaultOff = new int[numSelector]; + nonDefOff = new int[numSelector]; + + /* get Variation Selector and Table offset */ + for (int i=0; i vs) break; + + /* non default glyphs table */ + if (numMappings[v] > 0) { + if (uniStart[v] == null || glyphID[v] == null) { + try { + initNonDef(v); + } catch (Exception e) { + return 0; + } + } + + /* search non default glyphs table */ + c = java.util.Arrays.binarySearch(uniStart[v], charCode); + if (c >= 0) { + return glyphID[v][c]; + } + } + + /* default glyphs table */ + if (defCmap == null) break; + if (numRanges[v] > 0) { + if (defUniStart[v] == null || additionalCnt[v] == null) { + try { + initDef(v); + } catch (Exception e) { + return 0; + } + } + + /* search default glyphs table */ + c = java.util.Arrays.binarySearch(defUniStart[v], charCode); + if (c <= -2) { + c = -c - 2; + if (charCode >= defUniStart[v][c] && + charCode <= defUniStart[v][c]+additionalCnt[v][c]) { + return defCmap.getGlyph(charCode); + } + } else if (c >= 0) { + return defCmap.getGlyph(charCode); + } + } + + break; + } + return 0; + } + } + /* Used to substitute for bad Cmaps. */ static class NullCMapClass extends CMap { diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/font/CharToGlyphMapper.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/font/CharToGlyphMapper.java index b78e26bdf2..84f16be237 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/font/CharToGlyphMapper.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/font/CharToGlyphMapper.java @@ -25,6 +25,8 @@ package com.sun.javafx.font; +import com.sun.javafx.text.GlyphLayout; + /* * NB the versions that take a char as an int are used by the opentype * layout engine. If that remains in native these methods may not be @@ -32,18 +34,30 @@ */ public abstract class CharToGlyphMapper { - public static final int HI_SURROGATE_SHIFT = 10; - public static final int HI_SURROGATE_START = 0xD800; - 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 SURROGATES_START = 0x10000; - public static final int MISSING_GLYPH = 0; public static final int INVISIBLE_GLYPH_ID = 0xffff; + public static final int SVS_START = 0xFE00; // VS1 + public static final int SVS_END = 0xFE0F; // VS16 + public static final int IVS_START = 0xE0100; // VS17 + public static final int IVS_END = 0xE01EF; // VS256 + public static final int FVS_START = 0x180B; // FVS1 + public static final int FVS_END = 0x180D; // FVS3 + protected int missingGlyph = MISSING_GLYPH; + public static boolean isVS(int code) { + return (isIVS(code) || isSVS(code)); + } + + public static boolean isSVS(int code) { + return (code >= SVS_START && code <= SVS_END); + } + + public static boolean isIVS(int code) { + return (code >= IVS_START && code <= IVS_END); + } + public boolean canDisplay(char cp) { int glyph = charToGlyph(cp); return glyph != missingGlyph; @@ -53,35 +67,82 @@ public int getMissingGlyphCode() { return missingGlyph; } - public abstract int getGlyphCode(int charCode); + public abstract int getGlyphCode(int charCode, int vs); - public int charToGlyph(char unicode) { - return getGlyphCode(unicode); + public final int charToGlyph(char unicode) { + return getGlyphCode(unicode, (char)0); } - public int charToGlyph(int unicode) { - return getGlyphCode(unicode); + public final int charToGlyph(int unicode) { + return getGlyphCode(unicode, 0); + } + + public int charToGlyph(char unicode, char vs) { + return getGlyphCode(unicode, vs); + } + + public int charToGlyph(int unicode, int vs) { + return getGlyphCode(unicode, vs); } public void charsToGlyphs(int start, int count, char[] unicodes, int[] glyphs, int glyphStart) { + + /* implement following patterns + * (A) Normal char (All chars except SurrogatePair, IVS, SVS) + * (B) Surrogate_high + Surrogate_low + * + * (C) CJK + IVS_high + IVS_low + * (D) IVS_high + IVS_low (IVS only, not CJK + IVS) + * (E) Surrogate_high + Surrogate_low + IVS_high + IVS_low + * + * (F) CJK + SVS + * (G) SVS (SVS only, not CJK + SVS) + * (H) Surrogate_high + Surrogate_low + SVS + */ + int prevSurrogate = 0; // store surrogate pair to handle (E)(H) for (int i=0; i= HI_SURROGATE_START && - code <= HI_SURROGATE_END && i + 1 < count) { - char low = unicodes[start + i + 1]; - - if (low >= LO_SURROGATE_START && - low <= LO_SURROGATE_END) { - code = ((code - HI_SURROGATE_START) << HI_SURROGATE_SHIFT) + - low - LO_SURROGATE_START + SURROGATES_START; - glyphs[glyphStart + i] = getGlyphCode(code); - i += 1; // Empty glyph slot after surrogate - glyphs[glyphStart + i] = INVISIBLE_GLYPH_ID; - continue; + int st = start + i; + int code = unicodes[st]; // char is unsigned. + boolean isSURROGATE = false; + + if (Character.isHighSurrogate(unicodes[st]) && + i + 1 < count && Character.isLowSurrogate(unicodes[st + 1])) { + code = Character.toCodePoint(unicodes[st], unicodes[st + 1]); + isSURROGATE = true; + } + + if (isSURROGATE == false && isSVS(code) == false) { + glyphs[glyphStart + i] = getGlyphCode(code, 0); // (A) ASCII etc + prevSurrogate = 0; + } else if (isSURROGATE && isIVS(code) == false) { + glyphs[glyphStart + i] = getGlyphCode(code, 0); // (B) Surrogate + prevSurrogate = code; // store surrogate pair + } else { // == else if (isIVS || isSVS) + int glSt; + glSt = glyphStart + i; + if (prevSurrogate == 0) { + if (i > 0 && GlyphLayout.isIdeographic(unicodes[st - 1])) { + glyphs[glSt - 1] = + getGlyphCode(unicodes[st - 1], code); // (C) (F) VS + glyphs[glSt] = INVISIBLE_GLYPH_ID; + } else { + glyphs[glSt] = getGlyphCode(code, 0); // (D) (G) VS only + } + } else { // Surrogate + VS + glyphs[glSt - 2] = + getGlyphCode(prevSurrogate, code); // (E) (H) + glyphs[glSt - 1] = INVISIBLE_GLYPH_ID; + glyphs[glSt] = INVISIBLE_GLYPH_ID; + prevSurrogate = 0; } } - glyphs[glyphStart + i] = getGlyphCode(code); + + if (isSURROGATE) { + i += 1; // Empty glyph slot after surrogate + glyphs[glyphStart + i] = INVISIBLE_GLYPH_ID; + continue; + } } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/font/CompositeGlyphMapper.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/font/CompositeGlyphMapper.java index b5d2cfef77..e223373471 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/font/CompositeGlyphMapper.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/font/CompositeGlyphMapper.java @@ -31,6 +31,7 @@ public class CompositeGlyphMapper extends CharToGlyphMapper { public static final int SLOTMASK = 0xff000000; public static final int GLYPHMASK = 0x00ffffff; + public static final long LONGMASK = 0x00000000ffffffffL; public static final int NBLOCKS = 216; public static final int BLOCKSZ = 256; @@ -54,11 +55,17 @@ public class CompositeGlyphMapper extends CharToGlyphMapper { * the caching ? So a variety of strategies are possible. */ HashMap glyphMap; + HashMap glyphMapVS; // HashMap for Variation Selector + + public static long shiftVS_for_HashMap(int code) { + return (long)code << 32; + } public CompositeGlyphMapper(CompositeFontResource compFont) { font = compFont; missingGlyph = 0; // TrueType font standard, avoids lookup. glyphMap = new HashMap(); + glyphMapVS = new HashMap(); slotMappers = new CharToGlyphMapper[compFont.getNumSlots()]; asciiCacheOK = true; } @@ -90,17 +97,37 @@ public final int compositeGlyphCode(int slot, int glyphCode) { return ((slot) << 24 | (glyphCode & GLYPHMASK)); } - private final int convertToGlyph(int unicode) { + public static final int compGlyphToSlot(int compGlyphCode) { + return (compGlyphCode >> 24); + } + + public static final int compGlyphToGlyph(int compGlyphCode) { + return (compGlyphCode & GLYPHMASK); + } + + private final int convertToGlyph(int unicode, int vs) { for (int slot = 0; slot < font.getNumSlots(); slot++) { CharToGlyphMapper mapper = getSlotMapper(slot); - int glyphCode = mapper.charToGlyph(unicode); + int glyphCode = mapper.charToGlyph(unicode, vs); + if (glyphCode != mapper.getMissingGlyphCode()) { glyphCode = compositeGlyphCode(slot, glyphCode); - glyphMap.put(unicode, glyphCode); + if (vs == 0 || CharToGlyphMapper.isVS(vs) == false) { + glyphMap.put(unicode, glyphCode); + } else { + glyphMapVS.put( + shiftVS_for_HashMap(vs) | (unicode & LONGMASK), glyphCode); + } return glyphCode; } } - glyphMap.put(unicode, missingGlyph); + if (vs == 0 || CharToGlyphMapper.isVS(vs) == false) { + glyphMap.put(unicode, missingGlyph); + } else { + glyphMapVS.put( + shiftVS_for_HashMap(vs) | (unicode & LONGMASK), missingGlyph); + } + return missingGlyph; } @@ -137,18 +164,24 @@ private int getAsciiGlyphCode(int charCode) { return charToGlyph[index]; } - public int getGlyphCode(int charCode) { + public int getGlyphCode(int charCode, int vs) { // If ASCII then array lookup, else use glyphMap int retVal = getAsciiGlyphCode(charCode); if (retVal >= 0) { return retVal; } - Integer codeInt = glyphMap.get(charCode); + Integer codeInt; + if (vs == 0 || CharToGlyphMapper.isVS(vs) == false) { + codeInt = glyphMap.get(charCode); + } else { + codeInt = glyphMapVS.get( + shiftVS_for_HashMap(vs) | (charCode & LONGMASK)); + } if (codeInt != null) { return codeInt.intValue(); } else { - return convertToGlyph(charCode); + return convertToGlyph(charCode, vs); } } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/font/OpenTypeGlyphMapper.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/font/OpenTypeGlyphMapper.java index c212c4a30f..bebbc92aa1 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/font/OpenTypeGlyphMapper.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/font/OpenTypeGlyphMapper.java @@ -29,11 +29,14 @@ public class OpenTypeGlyphMapper extends CharToGlyphMapper { PrismFontFile font; CMap cmap; + CMap cmap14; + int offset_format[] = {0}; // offset of format14 public OpenTypeGlyphMapper(PrismFontFile font) { this.font = font; + offset_format[0] = 0; try { - cmap = CMap.initialize(font); + cmap = CMap.initialize(font, offset_format, -1); } catch (Exception e) { cmap = null; } @@ -43,13 +46,35 @@ public OpenTypeGlyphMapper(PrismFontFile font) { missingGlyph = 0; /* standard for TrueType fonts */ } - public int getGlyphCode(int charCode) { - try { - return cmap.getGlyph(charCode); - } catch(Exception e) { - handleBadCMAP(); - return missingGlyph; + public CMap createCMap14() { + if (cmap14 == null && offset_format[0] != 0) { + try { + cmap14 = CMap.initialize(font, offset_format, 14); + cmap14.setDefCMap(this.cmap); + } catch (Exception e) { + cmap14 = CMap.theNullCmap; + } + offset_format[0] = 0; + } + return cmap14; + } + + public int getGlyphCode(int charCode, int vs) { + if (vs == 0) { + try { + return cmap.getGlyph(charCode); + } catch(Exception e) { + handleBadCMAP(); + return missingGlyph; + } + } else if (createCMap14() != null) { + try { + return cmap14.getGlyph(charCode, vs); + } catch(Exception e) { + return missingGlyph; + } } + return missingGlyph; } private void handleBadCMAP() { diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/GlyphLayout.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/GlyphLayout.java index 588fc97360..dcb7dc276f 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/text/GlyphLayout.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/text/GlyphLayout.java @@ -405,7 +405,7 @@ public void dispose() { } } - private static boolean isIdeographic(int codePoint) { + public static boolean isIdeographic(int codePoint) { if (isIdeographicMethod != null) { try { return (boolean) isIdeographicMethod.invoke(null, codePoint);