1 /* 2 * Copyright (c) 2011, 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 class CStrike extends FontStrike { 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 CFont nativeFont; 72 private AffineTransform invDevTx; 73 private GlyphInfoCache glyphInfoCache; 74 private 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. 88 if (inDesc.devTx != null && !inDesc.devTx.isIdentity()) { 89 try { 90 invDevTx = inDesc.devTx.createInverse(); 91 } catch (NoninvertibleTransformException e) { 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 protected synchronized void finalize() throws Throwable { 131 if (nativeStrikePtr != 0) { 132 disposeNativeStrikePtr(nativeStrikePtr); 133 } 134 nativeStrikePtr = 0; 135 } 136 137 // the fractional metrics default on our platform is OFF 138 private boolean useFractionalMetrics() { 139 return desc.fmHint == SunHints.INTVAL_FRACTIONALMETRICS_ON; 140 } 141 142 public int getNumGlyphs() { 143 return nativeFont.getNumGlyphs(); 144 } 145 146 StrikeMetrics getFontMetrics() { 147 if (strikeMetrics == null) { 148 StrikeMetrics metrics = getFontMetrics(getNativeStrikePtr()); 149 if (invDevTx != null) { 150 metrics.convertToUserSpace(invDevTx); 151 } 152 metrics.convertToUserSpace(desc.glyphTx); 153 strikeMetrics = metrics; 154 } 155 return strikeMetrics; 156 } 157 158 float getGlyphAdvance(int glyphCode) { 159 return getScaledAdvanceForAdvance(getCachedNativeGlyphAdvance(glyphCode)); 160 } 161 162 float getCodePointAdvance(int cp) { 163 float advance = getCachedNativeGlyphAdvance(nativeFont.getMapper().charToGlyph(cp)); 164 165 double glyphScaleX = desc.glyphTx.getScaleX(); 166 double devScaleX = desc.devTx.getScaleX(); 167 168 if (devScaleX == 0) { 169 glyphScaleX = Math.sqrt(desc.glyphTx.getDeterminant()); 170 devScaleX = Math.sqrt(desc.devTx.getDeterminant()); 171 } 172 173 if (devScaleX == 0) { 174 devScaleX = Double.NaN; // this an undefined graphics state 175 } 176 advance = (float) (advance * glyphScaleX / devScaleX); 177 return useFractionalMetrics() ? advance : Math.round(advance); 178 } 179 180 // calculate an advance, and round if not using fractional metrics 181 private float getScaledAdvanceForAdvance(float advance) { 182 if (invDevTx != null) { 183 advance *= invDevTx.getScaleX(); 184 } 185 advance *= desc.glyphTx.getScaleX(); 186 return useFractionalMetrics() ? advance : Math.round(advance); 187 } 188 189 Point2D.Float getCharMetrics(char ch) { 190 return getScaledPointForAdvance(getCachedNativeGlyphAdvance(nativeFont.getMapper().charToGlyph(ch))); 191 } 192 193 Point2D.Float getGlyphMetrics(int glyphCode) { 194 return getScaledPointForAdvance(getCachedNativeGlyphAdvance(glyphCode)); 195 } 196 197 // calculate an advance point, and round if not using fractional metrics 198 private Point2D.Float getScaledPointForAdvance(float advance) { 199 Point2D.Float pt = new Point2D.Float(advance, 0); 200 201 if (!desc.glyphTx.isIdentity()) { 202 return scalePoint(pt); 203 } 204 205 if (!useFractionalMetrics()) { 206 pt.x = Math.round(pt.x); 207 } 208 return pt; 209 } 210 211 private Point2D.Float scalePoint(Point2D.Float pt) { 212 if (invDevTx != null) { 213 // transform the point out of the device space first 214 invDevTx.transform(pt, pt); 215 } 216 desc.glyphTx.transform(pt, pt); 217 pt.x -= desc.glyphTx.getTranslateX(); 218 pt.y -= desc.glyphTx.getTranslateY(); 219 220 if (!useFractionalMetrics()) { 221 pt.x = Math.round(pt.x); 222 pt.y = Math.round(pt.y); 223 } 224 225 return pt; 226 } 227 228 Rectangle2D.Float getGlyphOutlineBounds(int glyphCode) { 229 GeneralPath gp = getGlyphOutline(glyphCode, 0f, 0f); 230 Rectangle2D r2d = gp.getBounds2D(); 231 Rectangle2D.Float r2df; 232 if (r2d instanceof Rectangle2D.Float) { 233 r2df = (Rectangle2D.Float)r2d; 234 } else { 235 float x = (float)r2d.getX(); 236 float y = (float)r2d.getY(); 237 float w = (float)r2d.getWidth(); 238 float h = (float)r2d.getHeight(); 239 r2df = new Rectangle2D.Float(x, y, w, h); 240 } 241 return r2df; 242 } 243 244 // pt, result in device space 245 void getGlyphImageBounds(int glyphCode, Point2D.Float pt, Rectangle result) { 246 Rectangle2D.Float floatRect = new Rectangle2D.Float(); 247 248 if (invDevTx != null) { 249 invDevTx.transform(pt, pt); 250 } 251 252 getGlyphImageBounds(glyphCode, pt.x, pt.y, floatRect); 253 254 if (floatRect.width == 0 && floatRect.height == 0) { 255 result.setRect(0, 0, -1, -1); 256 return; 257 } 258 259 result.setRect(floatRect.x + pt.x, floatRect.y + pt.y, floatRect.width, floatRect.height); 260 } 261 262 private void getGlyphImageBounds(int glyphCode, float x, float y, Rectangle2D.Float floatRect) { 263 getNativeGlyphImageBounds(getNativeStrikePtr(), glyphCode, floatRect, x, y); 264 } 265 266 GeneralPath getGlyphOutline(int glyphCode, float x, float y) { 267 return getNativeGlyphOutline(getNativeStrikePtr(), glyphCode, x, y); 268 } 269 270 // should implement, however not called though any path that is publicly exposed 271 GeneralPath getGlyphVectorOutline(int[] glyphs, float x, float y) { 272 throw new Error("not implemented yet"); 273 } 274 275 // called from the Sun2D renderer 276 long getGlyphImagePtr(int glyphCode) { 277 synchronized (glyphInfoCache) { 278 long ptr = glyphInfoCache.get(glyphCode); 279 if (ptr != 0L) return ptr; 280 281 long[] ptrs = new long[1]; 282 int[] codes = new int[1]; 283 codes[0] = glyphCode; 284 285 getGlyphImagePtrs(codes, ptrs, 1); 286 287 ptr = ptrs[0]; 288 glyphInfoCache.put(glyphCode, ptr); 289 290 return ptr; 291 } 292 } 293 294 // called from the Sun2D renderer 295 void getGlyphImagePtrs(int[] glyphCodes, long[] images, int len) { 296 synchronized (glyphInfoCache) { 297 // fill the image pointer array with existing pointers 298 // from the cache 299 int missed = 0; 300 for (int i = 0; i < len; i++) { 301 int code = glyphCodes[i]; 302 303 final long ptr = glyphInfoCache.get(code); 304 if (ptr != 0L) { 305 images[i] = ptr; 306 } else { 307 // zero this element out, because the caller does not 308 // promise to keep it clean 309 images[i] = 0L; 310 missed++; 311 } 312 } 313 314 if (missed == 0) { 315 return; // horray! we got away without touching native! 316 } 317 318 // all distinct glyph codes requested (partially filled) 319 final int[] filteredCodes = new int[missed]; 320 // indices into filteredCodes array (totally filled) 321 final int[] filteredIndicies = new int[missed]; 322 323 // scan, mark, and store the requested glyph codes again to 324 // send into native 325 int j = 0; 326 int dupes = 0; 327 for (int i = 0; i < len; i++) { 328 if (images[i] != 0L) continue; // already filled 329 330 final int code = glyphCodes[i]; 331 332 // we have already promised to strike this glyph - this is 333 // a dupe 334 if (glyphInfoCache.get(code) == -1L) { 335 filteredIndicies[j] = -1; 336 dupes++; 337 j++; 338 continue; 339 } 340 341 // this is a distinct glyph we have not struck before, or 342 // promised to strike mark this one as "promise to strike" 343 // in the global cache with a -1L 344 final int k = j - dupes; 345 filteredCodes[k] = code; 346 glyphInfoCache.put(code, -1L); 347 filteredIndicies[j] = k; 348 j++; 349 } 350 351 final int filteredRunLen = j - dupes; 352 final long[] filteredImages = new long[filteredRunLen]; 353 354 // bulk call to fill in the distinct glyph pointers from native 355 getFilteredGlyphImagePtrs(filteredImages, filteredCodes, filteredRunLen); 356 357 // scan the requested glyph list, and fill in pointers from our 358 // distinct glyph list which has been filled from native 359 j = 0; 360 for (int i = 0; i < len; i++) { 361 if (images[i] != 0L && images[i] != -1L) { 362 continue; // already placed 363 } 364 365 // index into filteredImages array 366 final int k = filteredIndicies[j]; 367 final int code = glyphCodes[i]; 368 if (k == -1L) { 369 // we should have already filled the cache with this pointer 370 images[i] = glyphInfoCache.get(code); 371 } else { 372 // fill the particular glyph code request, and store 373 // in the cache 374 final long ptr = filteredImages[k]; 375 images[i] = ptr; 376 glyphInfoCache.put(code, ptr); 377 } 378 379 j++; 380 } 381 } 382 } 383 384 private void getFilteredGlyphImagePtrs(long[] glyphInfos, 385 int[] uniCodes, int len) 386 { 387 getGlyphImagePtrsNative(getNativeStrikePtr(), glyphInfos, uniCodes, len); 388 } 389 390 private float getCachedNativeGlyphAdvance(int glyphCode) { 391 synchronized(glyphAdvanceCache) { 392 float advance = glyphAdvanceCache.get(glyphCode); 393 if (advance != 0) { 394 return advance; 395 } 396 397 advance = getNativeGlyphAdvance(getNativeStrikePtr(), glyphCode); 398 glyphAdvanceCache.put(glyphCode, advance); 399 return advance; 400 } 401 } 402 403 // This class stores glyph pointers, and is indexed based on glyph codes, 404 // and negative unicode values. See the comments in 405 // CCharToGlyphMapper for more details on our glyph code strategy. 406 private static class GlyphInfoCache extends CStrikeDisposer { 407 private static final int FIRST_LAYER_SIZE = 256; 408 private static final int SECOND_LAYER_SIZE = 16384; // 16384 = 128x128 409 410 // rdar://problem/5204197 411 private boolean disposed = false; 412 413 private final long[] firstLayerCache; 414 private SparseBitShiftingTwoLayerArray secondLayerCache; 415 private HashMap<Integer, Long> generalCache; 416 417 public GlyphInfoCache(final Font2D nativeFont, 418 final FontStrikeDesc desc) 419 { 420 super(nativeFont, desc); 421 firstLayerCache = new long[FIRST_LAYER_SIZE]; 422 } 423 424 public synchronized long get(final int index) { 425 if (index < 0) { 426 if (-index < SECOND_LAYER_SIZE) { 427 // catch common unicodes 428 if (secondLayerCache == null) { 429 return 0L; 430 } 431 return secondLayerCache.get(-index); 432 } 433 } else { 434 if (index < FIRST_LAYER_SIZE) { 435 // catch common glyphcodes 436 return firstLayerCache[index]; 437 } 438 } 439 440 if (generalCache == null) { 441 return 0L; 442 } 443 final Long value = generalCache.get(new Integer(index)); 444 if (value == null) { 445 return 0L; 446 } 447 return value.longValue(); 448 } 449 450 public synchronized void put(final int index, final long value) { 451 if (index < 0) { 452 if (-index < SECOND_LAYER_SIZE) { 453 // catch common unicodes 454 if (secondLayerCache == null) { 455 secondLayerCache = new SparseBitShiftingTwoLayerArray(SECOND_LAYER_SIZE, 7); // 128x128 456 } 457 secondLayerCache.put(-index, value); 458 return; 459 } 460 } else { 461 if (index < FIRST_LAYER_SIZE) { 462 // catch common glyphcodes 463 firstLayerCache[index] = value; 464 return; 465 } 466 } 467 468 if (generalCache == null) { 469 generalCache = new HashMap<Integer, Long>(); 470 } 471 472 generalCache.put(new Integer(index), new Long(value)); 473 } 474 475 public synchronized void dispose() { 476 // rdar://problem/5204197 477 // Note that sun.font.Font2D.getStrike() actively disposes 478 // cleared strikeRef. We need to check the disposed flag to 479 // prevent double frees of native resources. 480 if (disposed) { 481 return; 482 } 483 484 super.dispose(); 485 486 // clean out the first array 487 disposeLongArray(firstLayerCache); 488 489 // clean out the two layer arrays 490 if (secondLayerCache != null) { 491 final long[][] secondLayerLongArrayArray = secondLayerCache.cache; 492 for (int i = 0; i < secondLayerLongArrayArray.length; i++) { 493 final long[] longArray = secondLayerLongArrayArray[i]; 494 if (longArray != null) disposeLongArray(longArray); 495 } 496 } 497 498 // clean up everyone else 499 if (generalCache != null) { 500 final Iterator<Long> i = generalCache.values().iterator(); 501 while (i.hasNext()) { 502 final long longValue = i.next().longValue(); 503 if (longValue != -1 && longValue != 0) { 504 removeGlyphInfoFromCache(longValue); 505 StrikeCache.freeLongPointer(longValue); 506 } 507 } 508 } 509 510 // rdar://problem/5204197 511 // Finally, set the flag. 512 disposed = true; 513 } 514 515 private static void disposeLongArray(final long[] longArray) { 516 for (int i = 0; i < longArray.length; i++) { 517 final long ptr = longArray[i]; 518 if (ptr != 0 && ptr != -1) { 519 removeGlyphInfoFromCache(ptr); 520 StrikeCache.freeLongPointer(ptr); // free's the native struct pointer 521 } 522 } 523 } 524 525 private static class SparseBitShiftingTwoLayerArray { 526 final long[][] cache; 527 final int shift; 528 final int secondLayerLength; 529 530 public SparseBitShiftingTwoLayerArray(final int size, final int shift) { 531 this.shift = shift; 532 this.cache = new long[1 << shift][]; 533 this.secondLayerLength = size >> shift; 534 } 535 536 public long get(final int index) { 537 final int firstIndex = index >> shift; 538 final long[] firstLayerRow = cache[firstIndex]; 539 if (firstLayerRow == null) return 0L; 540 return firstLayerRow[index - (firstIndex * (1 << shift))]; 541 } 542 543 public void put(final int index, final long value) { 544 final int firstIndex = index >> shift; 545 long[] firstLayerRow = cache[firstIndex]; 546 if (firstLayerRow == null) { 547 cache[firstIndex] = firstLayerRow = new long[secondLayerLength]; 548 } 549 firstLayerRow[index - (firstIndex * (1 << shift))] = value; 550 } 551 } 552 } 553 554 private static class GlyphAdvanceCache { 555 private static final int FIRST_LAYER_SIZE = 256; 556 private static final int SECOND_LAYER_SIZE = 16384; // 16384 = 128x128 557 558 private final float[] firstLayerCache = new float[FIRST_LAYER_SIZE]; 559 private SparseBitShiftingTwoLayerArray secondLayerCache; 560 private HashMap<Integer, Float> generalCache; 561 562 public synchronized float get(final int index) { 563 if (index < 0) { 564 if (-index < SECOND_LAYER_SIZE) { 565 // catch common unicodes 566 if (secondLayerCache == null) return 0; 567 return secondLayerCache.get(-index); 568 } 569 } else { 570 if (index < FIRST_LAYER_SIZE) { 571 // catch common glyphcodes 572 return firstLayerCache[index]; 573 } 574 } 575 576 if (generalCache == null) return 0; 577 final Float value = generalCache.get(new Integer(index)); 578 if (value == null) return 0; 579 return value.floatValue(); 580 } 581 582 public synchronized void put(final int index, final float value) { 583 if (index < 0) { 584 if (-index < SECOND_LAYER_SIZE) { 585 // catch common unicodes 586 if (secondLayerCache == null) { 587 secondLayerCache = new SparseBitShiftingTwoLayerArray(SECOND_LAYER_SIZE, 7); // 128x128 588 } 589 secondLayerCache.put(-index, value); 590 return; 591 } 592 } else { 593 if (index < FIRST_LAYER_SIZE) { 594 // catch common glyphcodes 595 firstLayerCache[index] = value; 596 return; 597 } 598 } 599 600 if (generalCache == null) { 601 generalCache = new HashMap<Integer, Float>(); 602 } 603 604 generalCache.put(new Integer(index), new Float(value)); 605 } 606 607 private static class SparseBitShiftingTwoLayerArray { 608 final float[][] cache; 609 final int shift; 610 final int secondLayerLength; 611 612 public SparseBitShiftingTwoLayerArray(final int size, 613 final int shift) 614 { 615 this.shift = shift; 616 this.cache = new float[1 << shift][]; 617 this.secondLayerLength = size >> shift; 618 } 619 620 public float get(final int index) { 621 final int firstIndex = index >> shift; 622 final float[] firstLayerRow = cache[firstIndex]; 623 if (firstLayerRow == null) return 0L; 624 return firstLayerRow[index - (firstIndex * (1 << shift))]; 625 } 626 627 public void put(final int index, final float value) { 628 final int firstIndex = index >> shift; 629 float[] firstLayerRow = cache[firstIndex]; 630 if (firstLayerRow == null) { 631 cache[firstIndex] = firstLayerRow = 632 new float[secondLayerLength]; 633 } 634 firstLayerRow[index - (firstIndex * (1 << shift))] = value; 635 } 636 } 637 } 638 }