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 
 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 
 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          */
 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 
 207         MetricsKey() {
 208         }
 209 
 210         MetricsKey(Font font, FontRenderContext frc) {
 211             init(font, frc);
 212         }
 213 
 214         void init(Font font, FontRenderContext frc) {
 215             this.font = font;
 216             this.frc = frc;
 217             this.hash = font.hashCode() + frc.hashCode();
 218         }
 219 
 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 
 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 
 249     public static FontDesignMetrics getMetrics(Font font) {
 250         return getMetrics(font, getDefaultFrc());
 251      }
 252 
 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 
 333     private FontDesignMetrics(Font font) {
 334 
 335         this(font, getDefaultFrc());
 336     }
 337 
 338     /* private to enable caching - call getMetrics() instead. */
 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 
 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 
 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 
 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 
 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 
 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
 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() */
 433     public FontRenderContext getFontRenderContext() {
 434         return frc;
 435     }
 436 
 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 
 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 
 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 
 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
 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 
 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    */
 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    */
 562     public int getDescent() {
 563         return (int)(roundingUpValue + this.descent);
 564     }
 565 
 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.
 583     public int getHeight() {
 584 
 585         if (height < 0) {
 586             height = getAscent() + (int)(roundingUpValue + descent + leading);
 587         }
 588         return height;
 589     }
 590 }