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 }