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