1 /* 2 * Copyright (c) 2011, 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.awt.Rectangle; 29 import java.awt.geom.*; 30 import java.util.*; 31 32 import sun.awt.SunHints; 33 34 public final class CStrike extends PhysicalStrike { 35 36 // Creates the native strike 37 private static native long createNativeStrikePtr(long nativeFontPtr, 38 double[] glyphTx, 39 double[] invDevTxMatrix, 40 int aaHint, 41 int fmHint); 42 43 // Disposes the native strike 44 private static native void disposeNativeStrikePtr(long nativeStrikePtr); 45 46 // Creates a StrikeMetrics from the underlying native system fonts 47 private static native StrikeMetrics getFontMetrics(long nativeStrikePtr); 48 49 // Returns native struct pointers used by the Sun 2D Renderer 50 private static native void getGlyphImagePtrsNative(long nativeStrikePtr, 51 long[] glyphInfos, 52 int[] uniCodes, int len); 53 54 // Returns the advance give a glyph code. It should be used only 55 // when the glyph code belongs to the CFont passed in. 56 private static native float getNativeGlyphAdvance(long nativeStrikePtr, 57 int glyphCode); 58 59 // Returns the outline shape of a glyph 60 private static native GeneralPath getNativeGlyphOutline(long nativeStrikePtr, 61 int glyphCode, 62 double x, 63 double y); 64 65 // returns the bounding rect for a glyph 66 private static native void getNativeGlyphImageBounds(long nativeStrikePtr, 67 int glyphCode, 68 Rectangle2D.Float result, 69 double x, double y); 70 71 private final CFont nativeFont; 72 private AffineTransform invDevTx; 73 private final GlyphInfoCache glyphInfoCache; 74 private final GlyphAdvanceCache glyphAdvanceCache; 75 private long nativeStrikePtr; 76 77 CStrike(final CFont font, final FontStrikeDesc inDesc) { 78 nativeFont = font; 79 desc = inDesc; 80 glyphInfoCache = new GlyphInfoCache(font, desc); 81 glyphAdvanceCache = new GlyphAdvanceCache(); 82 disposer = glyphInfoCache; 83 84 // Normally the device transform should be the identity transform 85 // for screen operations. The device transform only becomes 86 // interesting when we are outputting between different dpi surfaces, 87 // like when we are printing to postscript or use retina. 88 if (inDesc.devTx != null && !inDesc.devTx.isIdentity()) { 89 try { 90 invDevTx = inDesc.devTx.createInverse(); 91 } catch (NoninvertibleTransformException ignored) { 92 // ignored, since device transforms should not be that 93 // complicated, and if they are - there is nothing we can do, 94 // so we won't worry about it. 95 } 96 } 97 } 98 99 public long getNativeStrikePtr() { 100 if (nativeStrikePtr != 0) { 101 return nativeStrikePtr; 102 } 103 104 final double[] glyphTx = new double[6]; 105 desc.glyphTx.getMatrix(glyphTx); 106 107 final double[] invDevTxMatrix = new double[6]; 108 if (invDevTx == null) { 109 invDevTxMatrix[0] = 1; 110 invDevTxMatrix[3] = 1; 111 } else { 112 invDevTx.getMatrix(invDevTxMatrix); 113 } 114 115 final int aaHint = desc.aaHint; 116 final int fmHint = desc.fmHint; 117 118 synchronized (this) { 119 if (nativeStrikePtr != 0) { 120 return nativeStrikePtr; 121 } 122 nativeStrikePtr = 123 createNativeStrikePtr(nativeFont.getNativeFontPtr(), 124 glyphTx, invDevTxMatrix, aaHint, fmHint); 125 } 126 127 return nativeStrikePtr; 128 } 129 130 @SuppressWarnings("deprecation") 131 protected synchronized void finalize() throws Throwable { 132 if (nativeStrikePtr != 0) { 133 disposeNativeStrikePtr(nativeStrikePtr); 134 } 135 nativeStrikePtr = 0; 136 } 137 138 139 @Override 140 public int getNumGlyphs() { 141 return nativeFont.getNumGlyphs(); 142 } 143 144 @Override 145 StrikeMetrics getFontMetrics() { 146 if (strikeMetrics == null) { 147 StrikeMetrics metrics = getFontMetrics(getNativeStrikePtr()); 148 if (invDevTx != null) { 149 metrics.convertToUserSpace(invDevTx); 150 } 151 metrics.convertToUserSpace(desc.glyphTx); 152 strikeMetrics = metrics; 153 } 154 return strikeMetrics; 155 } 156 157 @Override 158 float getGlyphAdvance(final int glyphCode) { 159 return getCachedNativeGlyphAdvance(glyphCode); 160 } 161 162 @Override 163 float getCodePointAdvance(final int cp) { 164 return getGlyphAdvance(nativeFont.getMapper().charToGlyph(cp)); 165 } 166 167 @Override 168 Point2D.Float getCharMetrics(final char ch) { 169 return getGlyphMetrics(nativeFont.getMapper().charToGlyph(ch)); 170 } 171 172 @Override 173 Point2D.Float getGlyphMetrics(final int glyphCode) { 174 return new Point2D.Float(getGlyphAdvance(glyphCode), 0.0f); 175 } 176 177 Rectangle2D.Float getGlyphOutlineBounds(int glyphCode) { 178 GeneralPath gp = getGlyphOutline(glyphCode, 0f, 0f); 179 Rectangle2D r2d = gp.getBounds2D(); 180 Rectangle2D.Float r2df; 181 if (r2d instanceof Rectangle2D.Float) { 182 r2df = (Rectangle2D.Float)r2d; 183 } else { 184 float x = (float)r2d.getX(); 185 float y = (float)r2d.getY(); 186 float w = (float)r2d.getWidth(); 187 float h = (float)r2d.getHeight(); 188 r2df = new Rectangle2D.Float(x, y, w, h); 189 } 190 return r2df; 191 } 192 193 // pt, result in device space 194 void getGlyphImageBounds(int glyphCode, Point2D.Float pt, Rectangle result) { 195 Rectangle2D.Float floatRect = new Rectangle2D.Float(); 196 197 if (invDevTx != null) { 198 invDevTx.transform(pt, pt); 199 } 200 201 getGlyphImageBounds(glyphCode, pt.x, pt.y, floatRect); 202 203 if (floatRect.width == 0 && floatRect.height == 0) { 204 result.setRect(0, 0, -1, -1); 205 return; 206 } 207 208 result.setRect(floatRect.x + pt.x, floatRect.y + pt.y, floatRect.width, floatRect.height); 209 } 210 211 private void getGlyphImageBounds(int glyphCode, float x, float y, Rectangle2D.Float floatRect) { 212 getNativeGlyphImageBounds(getNativeStrikePtr(), glyphCode, floatRect, x, y); 213 } 214 215 GeneralPath getGlyphOutline(int glyphCode, float x, float y) { 216 return getNativeGlyphOutline(getNativeStrikePtr(), glyphCode, x, y); 217 } 218 219 // should implement, however not called though any path that is publicly exposed 220 GeneralPath getGlyphVectorOutline(int[] glyphs, float x, float y) { 221 throw new Error("not implemented yet"); 222 } 223 224 // called from the Sun2D renderer 225 long getGlyphImagePtr(int glyphCode) { 226 synchronized (glyphInfoCache) { 227 long ptr = glyphInfoCache.get(glyphCode); 228 if (ptr != 0L) return ptr; 229 230 long[] ptrs = new long[1]; 231 int[] codes = new int[1]; 232 codes[0] = glyphCode; 233 234 getGlyphImagePtrs(codes, ptrs, 1); 235 236 ptr = ptrs[0]; 237 glyphInfoCache.put(glyphCode, ptr); 238 239 return ptr; 240 } 241 } 242 243 // called from the Sun2D renderer 244 void getGlyphImagePtrs(int[] glyphCodes, long[] images, int len) { 245 synchronized (glyphInfoCache) { 246 // fill the image pointer array with existing pointers 247 // from the cache 248 int missed = 0; 249 for (int i = 0; i < len; i++) { 250 int code = glyphCodes[i]; 251 252 final long ptr = glyphInfoCache.get(code); 253 if (ptr != 0L) { 254 images[i] = ptr; 255 } else { 256 // zero this element out, because the caller does not 257 // promise to keep it clean 258 images[i] = 0L; 259 missed++; 260 } 261 } 262 263 if (missed == 0) { 264 return; // horray! we got away without touching native! 265 } 266 267 // all distinct glyph codes requested (partially filled) 268 final int[] filteredCodes = new int[missed]; 269 // indices into filteredCodes array (totally filled) 270 final int[] filteredIndicies = new int[missed]; 271 272 // scan, mark, and store the requested glyph codes again to 273 // send into native 274 int j = 0; 275 int dupes = 0; 276 for (int i = 0; i < len; i++) { 277 if (images[i] != 0L) continue; // already filled 278 279 final int code = glyphCodes[i]; 280 281 // we have already promised to strike this glyph - this is 282 // a dupe 283 if (glyphInfoCache.get(code) == -1L) { 284 filteredIndicies[j] = -1; 285 dupes++; 286 j++; 287 continue; 288 } 289 290 // this is a distinct glyph we have not struck before, or 291 // promised to strike mark this one as "promise to strike" 292 // in the global cache with a -1L 293 final int k = j - dupes; 294 filteredCodes[k] = code; 295 glyphInfoCache.put(code, -1L); 296 filteredIndicies[j] = k; 297 j++; 298 } 299 300 final int filteredRunLen = j - dupes; 301 final long[] filteredImages = new long[filteredRunLen]; 302 303 // bulk call to fill in the distinct glyph pointers from native 304 getFilteredGlyphImagePtrs(filteredImages, filteredCodes, filteredRunLen); 305 306 // scan the requested glyph list, and fill in pointers from our 307 // distinct glyph list which has been filled from native 308 j = 0; 309 for (int i = 0; i < len; i++) { 310 if (images[i] != 0L && images[i] != -1L) { 311 continue; // already placed 312 } 313 314 // index into filteredImages array 315 final int k = filteredIndicies[j]; 316 final int code = glyphCodes[i]; 317 if (k == -1L) { 318 // we should have already filled the cache with this pointer 319 images[i] = glyphInfoCache.get(code); 320 } else { 321 // fill the particular glyph code request, and store 322 // in the cache 323 final long ptr = filteredImages[k]; 324 images[i] = ptr; 325 glyphInfoCache.put(code, ptr); 326 } 327 328 j++; 329 } 330 } 331 } 332 333 private void getFilteredGlyphImagePtrs(long[] glyphInfos, 334 int[] uniCodes, int len) 335 { 336 getGlyphImagePtrsNative(getNativeStrikePtr(), glyphInfos, uniCodes, len); 337 } 338 339 private float getCachedNativeGlyphAdvance(int glyphCode) { 340 synchronized(glyphAdvanceCache) { 341 float advance = glyphAdvanceCache.get(glyphCode); 342 if (advance != 0) { 343 return advance; 344 } 345 346 advance = getNativeGlyphAdvance(getNativeStrikePtr(), glyphCode); 347 glyphAdvanceCache.put(glyphCode, advance); 348 return advance; 349 } 350 } 351 352 // This class stores glyph pointers, and is indexed based on glyph codes, 353 // and negative unicode values. See the comments in 354 // CCharToGlyphMapper for more details on our glyph code strategy. 355 private static class GlyphInfoCache extends CStrikeDisposer { 356 private static final int FIRST_LAYER_SIZE = 256; 357 private static final int SECOND_LAYER_SIZE = 16384; // 16384 = 128x128 358 359 // rdar://problem/5204197 360 private boolean disposed = false; 361 362 private final long[] firstLayerCache; 363 private SparseBitShiftingTwoLayerArray secondLayerCache; 364 private HashMap<Integer, Long> generalCache; 365 366 GlyphInfoCache(final Font2D nativeFont, final FontStrikeDesc desc) { 367 super(nativeFont, desc); 368 firstLayerCache = new long[FIRST_LAYER_SIZE]; 369 } 370 371 public synchronized long get(final int index) { 372 if (index < 0) { 373 if (-index < SECOND_LAYER_SIZE) { 374 // catch common unicodes 375 if (secondLayerCache == null) { 376 return 0L; 377 } 378 return secondLayerCache.get(-index); 379 } 380 } else { 381 if (index < FIRST_LAYER_SIZE) { 382 // catch common glyphcodes 383 return firstLayerCache[index]; 384 } 385 } 386 387 if (generalCache == null) { 388 return 0L; 389 } 390 final Long value = generalCache.get(Integer.valueOf(index)); 391 if (value == null) { 392 return 0L; 393 } 394 return value.longValue(); 395 } 396 397 public synchronized void put(final int index, final long value) { 398 if (index < 0) { 399 if (-index < SECOND_LAYER_SIZE) { 400 // catch common unicodes 401 if (secondLayerCache == null) { 402 secondLayerCache = new SparseBitShiftingTwoLayerArray(SECOND_LAYER_SIZE, 7); // 128x128 403 } 404 secondLayerCache.put(-index, value); 405 return; 406 } 407 } else { 408 if (index < FIRST_LAYER_SIZE) { 409 // catch common glyphcodes 410 firstLayerCache[index] = value; 411 return; 412 } 413 } 414 415 if (generalCache == null) { 416 generalCache = new HashMap<Integer, Long>(); 417 } 418 419 generalCache.put(Integer.valueOf(index), Long.valueOf(value)); 420 } 421 422 public synchronized void dispose() { 423 // rdar://problem/5204197 424 // Note that sun.font.Font2D.getStrike() actively disposes 425 // cleared strikeRef. We need to check the disposed flag to 426 // prevent double frees of native resources. 427 if (disposed) { 428 return; 429 } 430 431 super.dispose(); 432 433 // clean out the first array 434 disposeLongArray(firstLayerCache); 435 436 // clean out the two layer arrays 437 if (secondLayerCache != null) { 438 final long[][] secondLayerLongArrayArray = secondLayerCache.cache; 439 for (int i = 0; i < secondLayerLongArrayArray.length; i++) { 440 final long[] longArray = secondLayerLongArrayArray[i]; 441 if (longArray != null) disposeLongArray(longArray); 442 } 443 } 444 445 // clean up everyone else 446 if (generalCache != null) { 447 final Iterator<Long> i = generalCache.values().iterator(); 448 while (i.hasNext()) { 449 final long longValue = i.next().longValue(); 450 if (longValue != -1 && longValue != 0) { 451 removeGlyphInfoFromCache(longValue); 452 StrikeCache.freeLongPointer(longValue); 453 } 454 } 455 } 456 457 // rdar://problem/5204197 458 // Finally, set the flag. 459 disposed = true; 460 } 461 462 private static void disposeLongArray(final long[] longArray) { 463 for (int i = 0; i < longArray.length; i++) { 464 final long ptr = longArray[i]; 465 if (ptr != 0 && ptr != -1) { 466 removeGlyphInfoFromCache(ptr); 467 StrikeCache.freeLongPointer(ptr); // free's the native struct pointer 468 } 469 } 470 } 471 472 private static class SparseBitShiftingTwoLayerArray { 473 final long[][] cache; 474 final int shift; 475 final int secondLayerLength; 476 477 SparseBitShiftingTwoLayerArray(final int size, final int shift) { 478 this.shift = shift; 479 this.cache = new long[1 << shift][]; 480 this.secondLayerLength = size >> shift; 481 } 482 483 public long get(final int index) { 484 final int firstIndex = index >> shift; 485 final long[] firstLayerRow = cache[firstIndex]; 486 if (firstLayerRow == null) return 0L; 487 return firstLayerRow[index - (firstIndex * (1 << shift))]; 488 } 489 490 public void put(final int index, final long value) { 491 final int firstIndex = index >> shift; 492 long[] firstLayerRow = cache[firstIndex]; 493 if (firstLayerRow == null) { 494 cache[firstIndex] = firstLayerRow = new long[secondLayerLength]; 495 } 496 firstLayerRow[index - (firstIndex * (1 << shift))] = value; 497 } 498 } 499 } 500 501 private static class GlyphAdvanceCache { 502 private static final int FIRST_LAYER_SIZE = 256; 503 private static final int SECOND_LAYER_SIZE = 16384; // 16384 = 128x128 504 505 private final float[] firstLayerCache = new float[FIRST_LAYER_SIZE]; 506 private SparseBitShiftingTwoLayerArray secondLayerCache; 507 private HashMap<Integer, Float> generalCache; 508 509 // Empty non private constructor was added because access to this 510 // class shouldn't be emulated by a synthetic accessor method. 511 GlyphAdvanceCache() { 512 super(); 513 } 514 515 public synchronized float get(final int index) { 516 if (index < 0) { 517 if (-index < SECOND_LAYER_SIZE) { 518 // catch common unicodes 519 if (secondLayerCache == null) return 0; 520 return secondLayerCache.get(-index); 521 } 522 } else { 523 if (index < FIRST_LAYER_SIZE) { 524 // catch common glyphcodes 525 return firstLayerCache[index]; 526 } 527 } 528 529 if (generalCache == null) return 0; 530 final Float value = generalCache.get(Integer.valueOf(index)); 531 if (value == null) return 0; 532 return value.floatValue(); 533 } 534 535 public synchronized void put(final int index, final float value) { 536 if (index < 0) { 537 if (-index < SECOND_LAYER_SIZE) { 538 // catch common unicodes 539 if (secondLayerCache == null) { 540 secondLayerCache = new SparseBitShiftingTwoLayerArray(SECOND_LAYER_SIZE, 7); // 128x128 541 } 542 secondLayerCache.put(-index, value); 543 return; 544 } 545 } else { 546 if (index < FIRST_LAYER_SIZE) { 547 // catch common glyphcodes 548 firstLayerCache[index] = value; 549 return; 550 } 551 } 552 553 if (generalCache == null) { 554 generalCache = new HashMap<Integer, Float>(); 555 } 556 557 generalCache.put(Integer.valueOf(index), Float.valueOf(value)); 558 } 559 560 private static class SparseBitShiftingTwoLayerArray { 561 final float[][] cache; 562 final int shift; 563 final int secondLayerLength; 564 565 SparseBitShiftingTwoLayerArray(final int size, final int shift) { 566 this.shift = shift; 567 this.cache = new float[1 << shift][]; 568 this.secondLayerLength = size >> shift; 569 } 570 571 public float get(final int index) { 572 final int firstIndex = index >> shift; 573 final float[] firstLayerRow = cache[firstIndex]; 574 if (firstLayerRow == null) return 0L; 575 return firstLayerRow[index - (firstIndex * (1 << shift))]; 576 } 577 578 public void put(final int index, final float value) { 579 final int firstIndex = index >> shift; 580 float[] firstLayerRow = cache[firstIndex]; 581 if (firstLayerRow == null) { 582 cache[firstIndex] = firstLayerRow = 583 new float[secondLayerLength]; 584 } 585 firstLayerRow[index - (firstIndex * (1 << shift))] = value; 586 } 587 } 588 } 589 }