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 }