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 }