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