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
createNativeStrikePtr(long nativeFontPtr, double[] glyphTx, double[] invDevTxMatrix, int aaHint, int fmHint)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
disposeNativeStrikePtr(long nativeStrikePtr)44     private static native void disposeNativeStrikePtr(long nativeStrikePtr);
45 
46     // Creates a StrikeMetrics from the underlying native system fonts
getFontMetrics(long nativeStrikePtr)47     private static native StrikeMetrics getFontMetrics(long nativeStrikePtr);
48 
49     // Returns native struct pointers used by the Sun 2D Renderer
getGlyphImagePtrsNative(long nativeStrikePtr, long[] glyphInfos, int[] uniCodes, int len)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.
getNativeGlyphAdvance(long nativeStrikePtr, int glyphCode)56     private static native float getNativeGlyphAdvance(long nativeStrikePtr,
57                                                       int glyphCode);
58 
59     // Returns the outline shape of a glyph
getNativeGlyphOutline(long nativeStrikePtr, int glyphCode, double x, double y)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
getNativeGlyphImageBounds(long nativeStrikePtr, int glyphCode, Rectangle2D.Float result, double x, double y)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 
CStrike(final CFont font, final FontStrikeDesc inDesc)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 
getNativeStrikePtr()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")
finalize()131     protected synchronized void finalize() throws Throwable {
132         if (nativeStrikePtr != 0) {
133             disposeNativeStrikePtr(nativeStrikePtr);
134         }
135         nativeStrikePtr = 0;
136     }
137 
138 
139     @Override
getNumGlyphs()140     public int getNumGlyphs() {
141         return nativeFont.getNumGlyphs();
142     }
143 
144     @Override
getFontMetrics()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
getGlyphAdvance(final int glyphCode)158     float getGlyphAdvance(final int glyphCode) {
159         return getCachedNativeGlyphAdvance(glyphCode);
160     }
161 
162     @Override
getCodePointAdvance(final int cp)163     float getCodePointAdvance(final int cp) {
164         return getGlyphAdvance(nativeFont.getMapper().charToGlyph(cp));
165     }
166 
167     @Override
getCharMetrics(final char ch)168     Point2D.Float getCharMetrics(final char ch) {
169         return getGlyphMetrics(nativeFont.getMapper().charToGlyph(ch));
170     }
171 
172     @Override
getGlyphMetrics(final int glyphCode)173     Point2D.Float getGlyphMetrics(final int glyphCode) {
174         return new Point2D.Float(getGlyphAdvance(glyphCode), 0.0f);
175     }
176 
getGlyphOutlineBounds(int glyphCode)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
getGlyphImageBounds(int glyphCode, Point2D.Float pt, Rectangle result)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 
getGlyphImageBounds(int glyphCode, float x, float y, Rectangle2D.Float floatRect)211     private void getGlyphImageBounds(int glyphCode, float x, float y, Rectangle2D.Float floatRect) {
212         getNativeGlyphImageBounds(getNativeStrikePtr(), glyphCode, floatRect, x, y);
213     }
214 
getGlyphOutline(int glyphCode, float x, float y)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
getGlyphVectorOutline(int[] glyphs, float x, float y)220     GeneralPath getGlyphVectorOutline(int[] glyphs, float x, float y) {
221         throw new Error("not implemented yet");
222     }
223 
224     // called from the Sun2D renderer
getGlyphImagePtr(int glyphCode)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
getGlyphImagePtrs(int[] glyphCodes, long[] images, int len)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 
getFilteredGlyphImagePtrs(long[] glyphInfos, int[] uniCodes, int len)333     private void getFilteredGlyphImagePtrs(long[] glyphInfos,
334                                            int[] uniCodes, int len)
335     {
336         getGlyphImagePtrsNative(getNativeStrikePtr(), glyphInfos, uniCodes, len);
337     }
338 
getCachedNativeGlyphAdvance(int glyphCode)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 
GlyphInfoCache(final Font2D nativeFont, final FontStrikeDesc desc)366         GlyphInfoCache(final Font2D nativeFont, final FontStrikeDesc desc) {
367             super(nativeFont, desc);
368             firstLayerCache = new long[FIRST_LAYER_SIZE];
369         }
370 
get(final int index)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 
put(final int index, final long value)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 
dispose()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 
disposeLongArray(final long[] longArray)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 
SparseBitShiftingTwoLayerArray(final int size, final int shift)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 
get(final int index)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 
put(final int index, final long value)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.
GlyphAdvanceCache()511         GlyphAdvanceCache() {
512             super();
513         }
514 
get(final int index)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 
put(final int index, final float value)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 
SparseBitShiftingTwoLayerArray(final int size, final int shift)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 
get(final int index)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 
put(final int index, final float value)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 }
590