1 /* 2 * Copyright (c) 2011, 2013, 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.util.HashMap; 29 30 public class CCharToGlyphMapper extends CharToGlyphMapper { 31 private static native int countGlyphs(final long nativeFontPtr); 32 33 private Cache cache = new Cache(); 34 CFont fFont; 35 int numGlyphs = -1; 36 37 public CCharToGlyphMapper(CFont font) { 38 fFont = font; 39 missingGlyph = 0; // for getMissingGlyphCode() 40 } 41 42 public int getNumGlyphs() { 43 if (numGlyphs == -1) { 44 numGlyphs = countGlyphs(fFont.getNativeFontPtr()); 45 } 46 return numGlyphs; 47 } 48 49 public boolean canDisplay(char ch) { 50 int glyph = charToGlyph(ch); 51 return glyph != missingGlyph; 52 } 53 54 public boolean canDisplay(int cp) { 55 int glyph = charToGlyph(cp); 56 return glyph != missingGlyph; 57 } 58 59 public synchronized boolean charsToGlyphsNS(int count, 60 char[] unicodes, int[] glyphs) 61 { 62 charsToGlyphs(count, unicodes, glyphs); 63 64 // The following shaping checks are from either 65 // TrueTypeGlyphMapper or Type1GlyphMapper 66 for (int i = 0; i < count; i++) { 67 int code = unicodes[i]; 68 69 if (code >= HI_SURROGATE_START && code <= HI_SURROGATE_END && i < count - 1) { 70 char low = unicodes[i + 1]; 71 72 if (low >= LO_SURROGATE_START && low <= LO_SURROGATE_END) { 73 code = (code - HI_SURROGATE_START) * 0x400 + low - LO_SURROGATE_START + 0x10000; 74 glyphs[i + 1] = INVISIBLE_GLYPH_ID; 75 } 76 } 77 78 if (code < 0x0590) { 79 continue; 80 } else if (code <= 0x05ff) { 81 // Hebrew 0x0590->0x05ff 82 return true; 83 } else if (code >= 0x0600 && code <= 0x06ff) { 84 // Arabic 85 return true; 86 } else if (code >= 0x0900 && code <= 0x0d7f) { 87 // if Indic, assume shaping for conjuncts, reordering: 88 // 0900 - 097F Devanagari 89 // 0980 - 09FF Bengali 90 // 0A00 - 0A7F Gurmukhi 91 // 0A80 - 0AFF Gujarati 92 // 0B00 - 0B7F Oriya 93 // 0B80 - 0BFF Tamil 94 // 0C00 - 0C7F Telugu 95 // 0C80 - 0CFF Kannada 96 // 0D00 - 0D7F Malayalam 97 return true; 98 } else if (code >= 0x0e00 && code <= 0x0e7f) { 99 // if Thai, assume shaping for vowel, tone marks 100 return true; 101 } else if (code >= 0x200c && code <= 0x200d) { 102 // zwj or zwnj 103 return true; 104 } else if (code >= 0x202a && code <= 0x202e) { 105 // directional control 106 return true; 107 } else if (code >= 0x206a && code <= 0x206f) { 108 // directional control 109 return true; 110 } else if (code >= 0x10000) { 111 i += 1; // Empty glyph slot after surrogate 112 continue; 113 } 114 } 115 116 return false; 117 } 118 119 public synchronized int charToGlyph(char unicode) { 120 final int glyph = cache.get(unicode); 121 if (glyph != 0) return glyph; 122 123 final char[] unicodeArray = new char[] { unicode }; 124 final int[] glyphArray = new int[1]; 125 126 nativeCharsToGlyphs(fFont.getNativeFontPtr(), 1, unicodeArray, glyphArray); 127 cache.put(unicode, glyphArray[0]); 128 129 return glyphArray[0]; 130 } 131 132 public synchronized int charToGlyph(int unicode) { 133 if (unicode >= 0x10000) { 134 int[] glyphs = new int[2]; 135 char[] surrogates = new char[2]; 136 int base = unicode - 0x10000; 137 surrogates[0] = (char)((base >>> 10) + HI_SURROGATE_START); 138 surrogates[1] = (char)((base % 0x400) + LO_SURROGATE_START); 139 charsToGlyphs(2, surrogates, glyphs); 140 return glyphs[0]; 141 } else { 142 return charToGlyph((char)unicode); 143 } 144 } 145 146 public synchronized void charsToGlyphs(int count, char[] unicodes, int[] glyphs) { 147 cache.get(count, unicodes, glyphs); 148 } 149 150 public synchronized void charsToGlyphs(int count, int[] unicodes, int[] glyphs) { 151 for (int i = 0; i < count; i++) { 152 glyphs[i] = charToGlyph(unicodes[i]); 153 }; 154 } 155 156 // This mapper returns either the glyph code, or if the character can be 157 // replaced on-the-fly using CoreText substitution; the negative unicode 158 // value. If this "glyph code int" is treated as an opaque code, it will 159 // strike and measure exactly as a real glyph code - whether the character 160 // is present or not. Missing characters for any font on the system will 161 // be returned as 0, as the getMissingGlyphCode() function above indicates. 162 private static native void nativeCharsToGlyphs(final long nativeFontPtr, 163 int count, char[] unicodes, 164 int[] glyphs); 165 166 private class Cache { 167 private static final int FIRST_LAYER_SIZE = 256; 168 private static final int SECOND_LAYER_SIZE = 16384; // 16384 = 128x128 169 170 private final int[] firstLayerCache = new int[FIRST_LAYER_SIZE]; 171 private SparseBitShiftingTwoLayerArray secondLayerCache; 172 private HashMap<Integer, Integer> generalCache; 173 174 Cache() { 175 // <rdar://problem/5331678> need to prevent getting '-1' stuck in the cache 176 firstLayerCache[1] = 1; 177 } 178 179 public synchronized int get(final int index) { 180 if (index < FIRST_LAYER_SIZE) { 181 // catch common glyphcodes 182 return firstLayerCache[index]; 183 } 184 185 if (index < SECOND_LAYER_SIZE) { 186 // catch common unicodes 187 if (secondLayerCache == null) return 0; 188 return secondLayerCache.get(index); 189 } 190 191 if (generalCache == null) return 0; 192 final Integer value = generalCache.get(index); 193 if (value == null) return 0; 194 return value.intValue(); 195 } 196 197 public synchronized void put(final int index, final int value) { 198 if (index < FIRST_LAYER_SIZE) { 199 // catch common glyphcodes 200 firstLayerCache[index] = value; 201 return; 202 } 203 204 if (index < SECOND_LAYER_SIZE) { 205 // catch common unicodes 206 if (secondLayerCache == null) { 207 secondLayerCache = new SparseBitShiftingTwoLayerArray(SECOND_LAYER_SIZE, 7); // 128x128 208 } 209 secondLayerCache.put(index, value); 210 return; 211 } 212 213 if (generalCache == null) { 214 generalCache = new HashMap<Integer, Integer>(); 215 } 216 217 generalCache.put(index, value); 218 } 219 220 private class SparseBitShiftingTwoLayerArray { 221 final int[][] cache; 222 final int shift; 223 final int secondLayerLength; 224 225 public SparseBitShiftingTwoLayerArray(final int size, 226 final int shift) 227 { 228 this.shift = shift; 229 this.cache = new int[1 << shift][]; 230 this.secondLayerLength = size >> shift; 231 } 232 233 public int get(final int index) { 234 final int firstIndex = index >> shift; 235 final int[] firstLayerRow = cache[firstIndex]; 236 if (firstLayerRow == null) return 0; 237 return firstLayerRow[index - (firstIndex * (1 << shift))]; 238 } 239 240 public void put(final int index, final int value) { 241 final int firstIndex = index >> shift; 242 int[] firstLayerRow = cache[firstIndex]; 243 if (firstLayerRow == null) { 244 cache[firstIndex] = firstLayerRow = new int[secondLayerLength]; 245 } 246 firstLayerRow[index - (firstIndex * (1 << shift))] = value; 247 } 248 } 249 250 public synchronized void get(int count, char[] indicies, int[] values) 251 { 252 // "missed" is the count of 'char' that are not mapped. 253 // Surrogates count for 2. 254 // unmappedChars is the unique list of these chars. 255 // unmappedCharIndices is the location in the original array 256 int missed = 0; 257 char[] unmappedChars = null; 258 int [] unmappedCharIndices = null; 259 260 for (int i = 0; i < count; i++){ 261 int code = indicies[i]; 262 if (code >= HI_SURROGATE_START && 263 code <= HI_SURROGATE_END && i < count - 1) 264 { 265 char low = indicies[i + 1]; 266 if (low >= LO_SURROGATE_START && low <= LO_SURROGATE_END) { 267 code = (code - HI_SURROGATE_START) * 0x400 + 268 low - LO_SURROGATE_START + 0x10000; 269 } 270 } 271 272 final int value = get(code); 273 if (value != 0 && value != -1) { 274 values[i] = value; 275 if (code >= 0x10000) { 276 values[i+1] = INVISIBLE_GLYPH_ID; 277 i++; 278 } 279 } else { 280 values[i] = 0; 281 put(code, -1); 282 if (unmappedChars == null) { 283 // This is likely to be longer than we need, 284 // but is the simplest and cheapest option. 285 unmappedChars = new char[indicies.length]; 286 unmappedCharIndices = new int[indicies.length]; 287 } 288 unmappedChars[missed] = indicies[i]; 289 unmappedCharIndices[missed] = i; 290 if (code >= 0x10000) { // was a surrogate pair 291 unmappedChars[++missed] = indicies[++i]; 292 } 293 missed++; 294 } 295 } 296 297 if (missed == 0) { 298 return; 299 } 300 301 final int[] glyphCodes = new int[missed]; 302 303 // bulk call to fill in the unmapped code points. 304 nativeCharsToGlyphs(fFont.getNativeFontPtr(), 305 missed, unmappedChars, glyphCodes); 306 307 for (int m = 0; m < missed; m++){ 308 int i = unmappedCharIndices[m]; 309 int code = unmappedChars[m]; 310 if (code >= HI_SURROGATE_START && 311 code <= HI_SURROGATE_END && m < missed - 1) 312 { 313 char low = indicies[m + 1]; 314 if (low >= LO_SURROGATE_START && low <= LO_SURROGATE_END) { 315 code = (code - HI_SURROGATE_START) * 0x400 + 316 low - LO_SURROGATE_START + 0x10000; 317 } 318 } 319 values[i] = glyphCodes[m]; 320 put(code, values[i]); 321 if (code >= 0x10000) { 322 m++; 323 values[i + 1] = INVISIBLE_GLYPH_ID; 324 } 325 } 326 } 327 } 328 }