1 /*
2  * Copyright (c) 1997, 2006, 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.lang.ref.ReferenceQueue;
29 import java.lang.ref.SoftReference;
30 
31 import java.awt.FontMetrics;
32 import java.awt.Font;
33 import java.awt.GraphicsEnvironment;
34 import java.awt.geom.AffineTransform;
35 import java.awt.geom.NoninvertibleTransformException;
36 import java.awt.font.FontRenderContext;
37 import java.awt.font.TextLayout;
38 
39 import java.io.IOException;
40 import java.io.ObjectInputStream;
41 import java.io.ObjectOutputStream;
42 
43 import java.util.concurrent.ConcurrentHashMap;
44 
45 import sun.java2d.Disposer;
46 import sun.java2d.DisposerRecord;
47 
48 /*
49  * This class provides a summary of the glyph measurements  for a Font
50  * and a set of hints that guide their display.  It provides more metrics
51  * information for the Font than the java.awt.FontMetrics class. There
52  * is also some redundancy with that class.
53  * <p>
54  * The design metrics for a Font are obtained from Font.getDesignMetrics().
55  * The FontDesignMetrics object returned will be independent of the
56  * point size of the Font.
57  * Most users are familiar with the idea of using <i>point size</i> to
58  * specify the size of glyphs in a font. This point size defines a
59  * measurement between the baseline of one line to the baseline of the
60  * following line in a single spaced text document. The point size is
61  * based on <i>typographic points</i>, approximately 1/72 of an inch.
62  * <p>
63  * The Java2D API adopts the convention that one point is equivalent
64  * to one unit in user coordinates.  When using a normalized transform
65  * for converting user space coordinates to device space coordinates (see
66  * GraphicsConfiguration.getDefaultTransform() and
67  * GraphicsConfiguration.getNormalizingTransform()), 72 user space units
68  * equal 1 inch in device space.  In this case one point is 1/72 of an inch.
69  * <p>
70  * The FontDesignMetrics class expresses font metrics in terms of arbitrary
71  * <i>typographic units</i> (not points) chosen by the font supplier
72  * and used in the underlying platform font representations.  These units are
73  * defined by dividing the em-square into a grid.  The em-sqaure is the
74  * theoretical square whose dimensions are the full body height of the
75  * font.  A typographic unit is the smallest measurable unit in the
76  * em-square.  The number of units-per-em is determined by the font
77  * designer.  The greater the units-per-em, the greater the precision
78  * in metrics.  For example, Type 1 fonts divide the em-square into a
79  * 1000 x 1000 grid, while TrueType fonts typically use a 2048 x 2048
80  * grid.  The scale of these units can be obtained by calling
81  * getUnitsPerEm().
82  * <p>
83  * Typographic units are relative -- their absolute size changes as the
84  * size of the of the em-square changes.  An em-square is 9 points high
85  * in a 9-point font.  Because typographic units are relative to the
86  * em-square, a given location on a glyph will have the same coordinates
87  * in typographic units regardless of the point size.
88  * <p>
89  * Converting typographic units to pixels requires computing pixels-per-em
90  * (ppem).  This can be computed as:
91  * <pre>
92          ppem = device_resolution * (inches-per-point) * pointSize
93  * </pre>
94  * where device resolution could be measured in pixels/inch and the point
95  * size of a font is effectively points/em.  Using a normalized transform
96  * from user space to device space (see above), results in 1/72 inch/point.
97  * In this case, ppem is equal to the point size on a 72 dpi monitor, so
98  * that an N point font displays N pixels high.  In general,
99  * <pre>
100         pixel_units = typographic_units * (ppem / units_per_em)
101  * </pre>
102  * @see java.awt.Font
103  * @see java.awt.GraphicsConfiguration#getDefaultTransform
104  * @see java.awt.GraphicsConfiguration#getNormalizingTransform
105  */
106 
107 public final class FontDesignMetrics extends FontMetrics {
108 
109     static final long serialVersionUID = 4480069578560887773L;
110 
111     private static final float UNKNOWN_WIDTH = -1;
112     private static final int CURRENT_VERSION = 1;
113 
114     // height, ascent, descent, leading are reported to the client
115     // as an integer this value is added to the true fp value to
116     // obtain a value which is usually going to result in a round up
117     // to the next integer except for very marginal cases.
118     private static float roundingUpValue = 0.95f;
119 
120     // These fields are all part of the old serialization representation
121     private Font  font;
122     private float ascent;
123     private float descent;
124     private float leading;
125     private float maxAdvance;
126     private double[] matrix;
127     private int[] cache; // now unused, still here only for serialization
128     // End legacy serialization fields
129 
130     private int serVersion = 0;  // If 1 in readObject, these fields are on the input stream:
131     private boolean isAntiAliased;
132     private boolean usesFractionalMetrics;
133     private AffineTransform frcTx;
134 
135     private transient float[] advCache; // transient since values could change across runtimes
136     private transient int height = -1;
137 
138     private transient FontRenderContext frc;
139 
140     private transient double[] devmatrix = null;
141 
142     private transient FontStrike fontStrike;
143 
144     private static FontRenderContext DEFAULT_FRC = null;
145 
getDefaultFrc()146     private static FontRenderContext getDefaultFrc() {
147 
148         if (DEFAULT_FRC == null) {
149             AffineTransform tx;
150             if (GraphicsEnvironment.isHeadless()) {
151                 tx = new AffineTransform();
152             } else {
153                 tx =  GraphicsEnvironment
154                     .getLocalGraphicsEnvironment()
155                     .getDefaultScreenDevice()
156                     .getDefaultConfiguration()
157                     .getDefaultTransform();
158             }
159             DEFAULT_FRC = new FontRenderContext(tx, false, false);
160         }
161         return DEFAULT_FRC;
162     }
163 
164     /* Strongly cache up to 5 most recently requested FontMetrics objects,
165      * and softly cache as many as GC allows. In practice this means we
166      * should keep references around until memory gets low.
167      * We key the cache either by a Font or a combination of the Font and
168      * and FRC. A lot of callers use only the font so although there's code
169      * duplication, we allow just a font to be a key implying a default FRC.
170      * Also we put the references on a queue so that if they do get nulled
171      * out we can clear the keys from the table.
172      */
173     private static class KeyReference extends SoftReference
174         implements DisposerRecord, Disposer.PollDisposable {
175 
176         static ReferenceQueue queue = Disposer.getQueue();
177 
178         Object key;
179 
KeyReference(Object key, Object value)180         KeyReference(Object key, Object value) {
181             super(value, queue);
182             this.key = key;
183             Disposer.addReference(this, this);
184         }
185 
186         /* It is possible that since this reference object has been
187          * enqueued, that a new metrics has been put into the table
188          * for the same key value. So we'll test to see if the table maps
189          * to THIS reference. If its a new one, we'll leave it alone.
190          * It is possible that a new entry comes in after our test, but
191          * it is unlikely and if this were a problem we would need to
192          * synchronize all 'put' and 'remove' accesses to the cache which
193          * I would prefer not to do.
194          */
dispose()195         public void dispose() {
196             if (metricsCache.get(key) == this) {
197                 metricsCache.remove(key);
198             }
199         }
200     }
201 
202     private static class MetricsKey {
203         Font font;
204         FontRenderContext frc;
205         int hash;
206 
MetricsKey()207         MetricsKey() {
208         }
209 
MetricsKey(Font font, FontRenderContext frc)210         MetricsKey(Font font, FontRenderContext frc) {
211             init(font, frc);
212         }
213 
init(Font font, FontRenderContext frc)214         void init(Font font, FontRenderContext frc) {
215             this.font = font;
216             this.frc = frc;
217             this.hash = font.hashCode() + frc.hashCode();
218         }
219 
equals(Object key)220         public boolean equals(Object key) {
221             if (!(key instanceof MetricsKey)) {
222                 return false;
223             }
224             return
225                 font.equals(((MetricsKey)key).font) &&
226                 frc.equals(((MetricsKey)key).frc);
227         }
228 
hashCode()229         public int hashCode() {
230             return hash;
231         }
232 
233         /* Synchronize access to this on the class */
234         static final MetricsKey key = new MetricsKey();
235     }
236 
237     /* All accesses to a CHM do not in general need to be synchronized,
238      * as incomplete operations on another thread would just lead to
239      * harmless cache misses.
240      */
241     private static final ConcurrentHashMap<Object, KeyReference>
242         metricsCache = new ConcurrentHashMap<Object, KeyReference>();
243 
244     private static final int MAXRECENT = 5;
245     private static final FontDesignMetrics[]
246         recentMetrics = new FontDesignMetrics[MAXRECENT];
247     private static int recentIndex = 0;
248 
getMetrics(Font font)249     public static FontDesignMetrics getMetrics(Font font) {
250         return getMetrics(font, getDefaultFrc());
251      }
252 
getMetrics(Font font, FontRenderContext frc)253     public static FontDesignMetrics getMetrics(Font font,
254                                                FontRenderContext frc) {
255 
256 
257         /* When using alternate composites, can't cache based just on
258          * the java.awt.Font. Since this is rarely used and we can still
259          * cache the physical fonts, its not a problem to just return a
260          * new instance in this case.
261          * Note that currently Swing native L&F composites are not handled
262          * by this code as they use the metrics of the physical anyway.
263          */
264         SunFontManager fm = SunFontManager.getInstance();
265         if (fm.maybeUsingAlternateCompositeFonts() &&
266             FontUtilities.getFont2D(font) instanceof CompositeFont) {
267             return new FontDesignMetrics(font, frc);
268         }
269 
270         FontDesignMetrics m = null;
271         KeyReference r;
272 
273         /* There are 2 possible keys used to perform lookups in metricsCache.
274          * If the FRC is set to all defaults, we just use the font as the key.
275          * If the FRC is non-default in any way, we construct a hybrid key
276          * that combines the font and FRC.
277          */
278         boolean usefontkey = frc.equals(getDefaultFrc());
279 
280         if (usefontkey) {
281             r = metricsCache.get(font);
282         } else /* use hybrid key */ {
283             // NB synchronization is not needed here because of updates to
284             // the metrics cache but is needed for the shared key.
285             synchronized (MetricsKey.class) {
286                 MetricsKey.key.init(font, frc);
287                 r = metricsCache.get(MetricsKey.key);
288             }
289         }
290 
291         if (r != null) {
292             m = (FontDesignMetrics)r.get();
293         }
294 
295         if (m == null) {
296             /* either there was no reference, or it was cleared. Need a new
297              * metrics instance. The key to use in the map is a new
298              * MetricsKey instance when we've determined the FRC is
299              * non-default. Its constructed from local vars so we are
300              * thread-safe - no need to worry about the shared key changing.
301              */
302             m = new FontDesignMetrics(font, frc);
303             if (usefontkey) {
304                 metricsCache.put(font, new KeyReference(font, m));
305             } else /* use hybrid key */ {
306                 MetricsKey newKey = new MetricsKey(font, frc);
307                 metricsCache.put(newKey, new KeyReference(newKey, m));
308             }
309         }
310 
311         /* Here's where we keep the recent metrics */
312         for (int i=0; i<recentMetrics.length; i++) {
313             if (recentMetrics[i]==m) {
314                 return m;
315             }
316         }
317 
318         synchronized (recentMetrics) {
319             recentMetrics[recentIndex++] = m;
320             if (recentIndex == MAXRECENT) {
321                 recentIndex = 0;
322             }
323         }
324         return m;
325     }
326 
327   /*
328    * Constructs a new FontDesignMetrics object for the given Font.
329    * Its private to enable caching - call getMetrics() instead.
330    * @param font a Font object.
331    */
332 
FontDesignMetrics(Font font)333     private FontDesignMetrics(Font font) {
334 
335         this(font, getDefaultFrc());
336     }
337 
338     /* private to enable caching - call getMetrics() instead. */
FontDesignMetrics(Font font, FontRenderContext frc)339     private FontDesignMetrics(Font font, FontRenderContext frc) {
340       super(font);
341       this.font = font;
342       this.frc = frc;
343 
344       this.isAntiAliased = frc.isAntiAliased();
345       this.usesFractionalMetrics = frc.usesFractionalMetrics();
346 
347       frcTx = frc.getTransform();
348 
349       matrix = new double[4];
350       initMatrixAndMetrics();
351 
352       initAdvCache();
353     }
354 
initMatrixAndMetrics()355     private void initMatrixAndMetrics() {
356 
357         Font2D font2D = FontUtilities.getFont2D(font);
358         fontStrike = font2D.getStrike(font, frc);
359         StrikeMetrics metrics = fontStrike.getFontMetrics();
360         this.ascent = metrics.getAscent();
361         this.descent = metrics.getDescent();
362         this.leading = metrics.getLeading();
363         this.maxAdvance = metrics.getMaxAdvance();
364 
365         devmatrix = new double[4];
366         frcTx.getMatrix(devmatrix);
367     }
368 
initAdvCache()369     private void initAdvCache() {
370         advCache = new float[256];
371         // 0 is a valid metric so force it to -1
372         for (int i = 0; i < 256; i++) {
373             advCache[i] = UNKNOWN_WIDTH;
374         }
375     }
376 
readObject(ObjectInputStream in)377     private void readObject(ObjectInputStream in) throws IOException,
378                                                   ClassNotFoundException {
379 
380         in.defaultReadObject();
381         if (serVersion != CURRENT_VERSION) {
382             frc = getDefaultFrc();
383             isAntiAliased = frc.isAntiAliased();
384             usesFractionalMetrics = frc.usesFractionalMetrics();
385             frcTx = frc.getTransform();
386         }
387         else {
388             frc = new FontRenderContext(frcTx, isAntiAliased, usesFractionalMetrics);
389         }
390 
391         // when deserialized, members are set to their default values for their type--
392         // not to the values assigned during initialization before the constructor
393         // body!
394         height = -1;
395 
396         cache = null;
397 
398         initMatrixAndMetrics();
399         initAdvCache();
400     }
401 
writeObject(ObjectOutputStream out)402     private void writeObject(ObjectOutputStream out) throws IOException {
403 
404         cache = new int[256];
405         for (int i=0; i < 256; i++) {
406             cache[i] = -1;
407         }
408         serVersion = CURRENT_VERSION;
409 
410         out.defaultWriteObject();
411 
412         cache = null;
413     }
414 
handleCharWidth(int ch)415     private float handleCharWidth(int ch) {
416         return fontStrike.getCodePointAdvance(ch); // x-component of result only
417     }
418 
419     // Uses advCache to get character width
420     // It is incorrect to call this method for ch > 255
getLatinCharWidth(char ch)421     private float getLatinCharWidth(char ch) {
422 
423         float w = advCache[ch];
424         if (w == UNKNOWN_WIDTH) {
425             w = handleCharWidth(ch);
426             advCache[ch] = w;
427         }
428         return w;
429     }
430 
431 
432     /* Override of FontMetrics.getFontRenderContext() */
getFontRenderContext()433     public FontRenderContext getFontRenderContext() {
434         return frc;
435     }
436 
charWidth(char ch)437     public int charWidth(char ch) {
438         // default metrics for compatibility with legacy code
439         float w;
440         if (ch < 0x100) {
441             w = getLatinCharWidth(ch);
442         }
443         else {
444             w = handleCharWidth(ch);
445         }
446         return (int)(0.5 + w);
447     }
448 
charWidth(int ch)449     public int charWidth(int ch) {
450         if (!Character.isValidCodePoint(ch)) {
451             ch = 0xffff;
452         }
453 
454         float w = handleCharWidth(ch);
455 
456         return (int)(0.5 + w);
457     }
458 
stringWidth(String str)459     public int stringWidth(String str) {
460 
461         float width = 0;
462         if (font.hasLayoutAttributes()) {
463             /* TextLayout throws IAE for null, so throw NPE explicitly */
464             if (str == null) {
465                 throw new NullPointerException("str is null");
466             }
467             if (str.length() == 0) {
468                 return 0;
469             }
470             width = new TextLayout(str, font, frc).getAdvance();
471         } else {
472             int length = str.length();
473             for (int i=0; i < length; i++) {
474                 char ch = str.charAt(i);
475                 if (ch < 0x100) {
476                     width += getLatinCharWidth(ch);
477                 } else if (FontUtilities.isNonSimpleChar(ch)) {
478                     width = new TextLayout(str, font, frc).getAdvance();
479                     break;
480                 } else {
481                     width += handleCharWidth(ch);
482                 }
483             }
484         }
485 
486         return (int) (0.5 + width);
487     }
488 
charsWidth(char data[], int off, int len)489     public int charsWidth(char data[], int off, int len) {
490 
491         float width = 0;
492         if (font.hasLayoutAttributes()) {
493             if (len == 0) {
494                 return 0;
495             }
496             String str = new String(data, off, len);
497             width = new TextLayout(str, font, frc).getAdvance();
498         } else {
499             /* Explicit test needed to satisfy superclass spec */
500             if (len < 0) {
501                 throw new IndexOutOfBoundsException("len="+len);
502             }
503             int limit = off + len;
504             for (int i=off; i < limit; i++) {
505                 char ch = data[i];
506                 if (ch < 0x100) {
507                     width += getLatinCharWidth(ch);
508                 } else if (FontUtilities.isNonSimpleChar(ch)) {
509                     String str = new String(data, off, len);
510                     width = new TextLayout(str, font, frc).getAdvance();
511                     break;
512                 } else {
513                     width += handleCharWidth(ch);
514                 }
515             }
516         }
517 
518         return (int) (0.5 + width);
519     }
520 
521     /**
522      * Gets the advance widths of the first 256 characters in the
523      * <code>Font</code>.  The advance is the
524      * distance from the leftmost point to the rightmost point on the
525      * character's baseline.  Note that the advance of a
526      * <code>String</code> is not necessarily the sum of the advances
527      * of its characters.
528      * @return    an array storing the advance widths of the
529      *                 characters in the <code>Font</code>
530      *                 described by this <code>FontMetrics</code> object.
531      */
532     // More efficient than base class implementation - reuses existing cache
getWidths()533     public int[] getWidths() {
534         int[] widths = new int[256];
535         for (char ch = 0 ; ch < 256 ; ch++) {
536             float w = advCache[ch];
537             if (w == UNKNOWN_WIDTH) {
538                 w = advCache[ch] = handleCharWidth(ch);
539             }
540             widths[ch] = (int) (0.5 + w);
541         }
542         return widths;
543     }
544 
getMaxAdvance()545     public int getMaxAdvance() {
546         return (int)(0.99f + this.maxAdvance);
547     }
548 
549   /*
550    * Returns the typographic ascent of the font. This is the maximum distance
551    * glyphs in this font extend above the base line (measured in typographic
552    * units).
553    */
getAscent()554     public int getAscent() {
555         return (int)(roundingUpValue + this.ascent);
556     }
557 
558   /*
559    * Returns the typographic descent of the font. This is the maximum distance
560    * glyphs in this font extend below the base line.
561    */
getDescent()562     public int getDescent() {
563         return (int)(roundingUpValue + this.descent);
564     }
565 
getLeading()566     public int getLeading() {
567         // nb this ensures the sum of the results of the public methods
568         // for leading, ascent & descent sum to height.
569         // if the calculations in any other methods change this needs
570         // to be changed too.
571         // the 0.95 value used here and in the other methods allows some
572         // tiny fraction of leeway before rouding up. A higher value (0.99)
573         // caused some excessive rounding up.
574         return
575             (int)(roundingUpValue + descent + leading) -
576             (int)(roundingUpValue + descent);
577     }
578 
579     // height is calculated as the sum of two separately rounded up values
580     // because typically clients use ascent to determine the y location to
581     // pass to drawString etc and we need to ensure that the height has enough
582     // space below the baseline to fully contain any descender.
getHeight()583     public int getHeight() {
584 
585         if (height < 0) {
586             height = getAscent() + (int)(roundingUpValue + descent + leading);
587         }
588         return height;
589     }
590 }
591