1 /* 2 * Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.font; 27 28 import java.nio.ByteBuffer; 29 import java.util.Locale; 30 31 public class TrueTypeGlyphMapper extends CharToGlyphMapper { 32 33 static final char REVERSE_SOLIDUS = 0x005c; // the backslash char. 34 static final char JA_YEN = 0x00a5; 35 36 /* if running on Solaris and default Locale is ja_JP then 37 * we map need to remap reverse solidus (backslash) to Yen as 38 * apparently expected there. 39 */ 40 static final boolean isJAlocale = Locale.JAPAN.equals(Locale.getDefault()); 41 private final boolean needsJAremapping; 42 43 TrueTypeFont font; 44 CMap cmap; 45 int numGlyphs; 46 47 public TrueTypeGlyphMapper(TrueTypeFont font) { 48 this.font = font; 49 try { 50 cmap = CMap.initialize(font); 51 } catch (Exception e) { 52 cmap = null; 53 } 54 if (cmap == null) { 55 handleBadCMAP(); 56 } 57 missingGlyph = 0; /* standard for TrueType fonts */ 58 ByteBuffer buffer = font.getTableBuffer(TrueTypeFont.maxpTag); 59 if (buffer != null && buffer.capacity() >= 6) { 60 numGlyphs = buffer.getChar(4); // offset 4 bytes in MAXP table. 61 } else { 62 handleBadCMAP(); 63 } 64 if (FontUtilities.isSolaris && isJAlocale && font.supportsJA()) { 65 needsJAremapping = true; 66 } else { 67 needsJAremapping = false; 68 } 69 } 70 71 public int getNumGlyphs() { 72 return numGlyphs; 73 } 74 75 private char getGlyphFromCMAP(int charCode) { 76 try { 77 char glyphCode = cmap.getGlyph(charCode); 78 if (glyphCode < numGlyphs || 79 glyphCode >= FileFontStrike.INVISIBLE_GLYPHS) { 80 return glyphCode; 81 } else { 82 if (FontUtilities.isLogging()) { 83 FontUtilities.getLogger().warning 84 (font + " out of range glyph id=" + 85 Integer.toHexString((int)glyphCode) + 86 " for char " + Integer.toHexString(charCode)); 87 } 88 return (char)missingGlyph; 89 } 90 } catch(Exception e) { 91 handleBadCMAP(); 92 return (char) missingGlyph; 93 } 94 } 95 96 private void handleBadCMAP() { 97 if (FontUtilities.isLogging()) { 98 FontUtilities.getLogger().severe("Null Cmap for " + font + 99 "substituting for this font"); 100 } 101 SunFontManager.getInstance().deRegisterBadFont(font); 102 /* The next line is not really a solution, but might 103 * reduce the exceptions until references to this font2D 104 * are gone. 105 */ 106 cmap = CMap.theNullCmap; 107 } 108 109 private char remapJAChar(char unicode) { 110 return (unicode == REVERSE_SOLIDUS) ? JA_YEN : unicode; 111 } 112 113 private int remapJAIntChar(int unicode) { 114 return (unicode == REVERSE_SOLIDUS) ? JA_YEN : unicode; 115 } 116 117 public int charToGlyph(char unicode) { 118 if (needsJAremapping) { 119 unicode = remapJAChar(unicode); 120 } 121 int glyph = getGlyphFromCMAP(unicode); 122 if (font.checkUseNatives() && glyph < font.glyphToCharMap.length) { 123 font.glyphToCharMap[glyph] = unicode; 124 } 125 return glyph; 126 } 127 128 public int charToGlyph(int unicode) { 129 if (needsJAremapping) { 130 unicode = remapJAIntChar(unicode); 131 } 132 int glyph = getGlyphFromCMAP(unicode); 133 if (font.checkUseNatives() && glyph < font.glyphToCharMap.length) { 134 font.glyphToCharMap[glyph] = (char)unicode; 135 } 136 return glyph; 137 } 138 139 public void charsToGlyphs(int count, int[] unicodes, int[] glyphs) { 140 for (int i=0;i<count;i++) { 141 if (needsJAremapping) { 142 glyphs[i] = getGlyphFromCMAP(remapJAIntChar(unicodes[i])); 143 } else { 144 glyphs[i] = getGlyphFromCMAP(unicodes[i]); 145 } 146 if (font.checkUseNatives() && 147 glyphs[i] < font.glyphToCharMap.length) { 148 font.glyphToCharMap[glyphs[i]] = (char)unicodes[i]; 149 } 150 } 151 } 152 153 public void charsToGlyphs(int count, char[] unicodes, int[] glyphs) { 154 155 for (int i=0; i<count; i++) { 156 int code; 157 if (needsJAremapping) { 158 code = remapJAChar(unicodes[i]); 159 } else { 160 code = unicodes[i]; // char is unsigned. 161 } 162 163 if (code >= HI_SURROGATE_START && 164 code <= HI_SURROGATE_END && i < count - 1) { 165 char low = unicodes[i + 1]; 166 167 if (low >= LO_SURROGATE_START && 168 low <= LO_SURROGATE_END) { 169 code = (code - HI_SURROGATE_START) * 170 0x400 + low - LO_SURROGATE_START + 0x10000; 171 172 glyphs[i] = getGlyphFromCMAP(code); 173 i += 1; // Empty glyph slot after surrogate 174 glyphs[i] = INVISIBLE_GLYPH_ID; 175 continue; 176 } 177 } 178 glyphs[i] = getGlyphFromCMAP(code); 179 180 if (font.checkUseNatives() && 181 glyphs[i] < font.glyphToCharMap.length) { 182 font.glyphToCharMap[glyphs[i]] = (char)code; 183 } 184 185 } 186 } 187 188 /* This variant checks if shaping is needed and immediately 189 * returns true if it does. A caller of this method should be expecting 190 * to check the return type because it needs to know how to handle 191 * the character data for display. 192 */ 193 public boolean charsToGlyphsNS(int count, char[] unicodes, int[] glyphs) { 194 195 for (int i=0; i<count; i++) { 196 int code; 197 if (needsJAremapping) { 198 code = remapJAChar(unicodes[i]); 199 } else { 200 code = unicodes[i]; // char is unsigned. 201 } 202 203 if (code >= HI_SURROGATE_START && 204 code <= HI_SURROGATE_END && i < count - 1) { 205 char low = unicodes[i + 1]; 206 207 if (low >= LO_SURROGATE_START && 208 low <= LO_SURROGATE_END) { 209 code = (code - HI_SURROGATE_START) * 210 0x400 + low - LO_SURROGATE_START + 0x10000; 211 glyphs[i + 1] = INVISIBLE_GLYPH_ID; 212 } 213 } 214 215 glyphs[i] = getGlyphFromCMAP(code); 216 if (font.checkUseNatives() && 217 glyphs[i] < font.glyphToCharMap.length) { 218 font.glyphToCharMap[glyphs[i]] = (char)code; 219 } 220 221 if (code < FontUtilities.MIN_LAYOUT_CHARCODE) { 222 continue; 223 } 224 else if (FontUtilities.isComplexCharCode(code)) { 225 return true; 226 } 227 else if (code >= 0x10000) { 228 i += 1; // Empty glyph slot after surrogate 229 continue; 230 } 231 } 232 233 return false; 234 } 235 236 /* A pretty good heuristic is that the cmap we are using 237 * supports 32 bit character codes. 238 */ 239 boolean hasSupplementaryChars() { 240 return 241 cmap instanceof CMap.CMapFormat8 || 242 cmap instanceof CMap.CMapFormat10 || 243 cmap instanceof CMap.CMapFormat12; 244 } 245 }