Path: blob/master/src/java.desktop/share/classes/sun/font/FontDesignMetrics.java
41155 views
/*1* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.font;2627import java.awt.Font;28import java.awt.FontMetrics;29import java.awt.GraphicsEnvironment;30import java.awt.font.FontRenderContext;31import java.awt.font.TextLayout;32import java.awt.geom.AffineTransform;33import java.awt.geom.Rectangle2D;34import java.io.IOException;35import java.io.ObjectInputStream;36import java.io.ObjectOutputStream;37import java.io.Serial;38import java.lang.ref.ReferenceQueue;39import java.lang.ref.SoftReference;40import java.util.concurrent.ConcurrentHashMap;4142import sun.java2d.Disposer;43import sun.java2d.DisposerRecord;4445/*46* This class provides a summary of the glyph measurements for a Font47* and a set of hints that guide their display. It provides more metrics48* information for the Font than the java.awt.FontMetrics class. There49* 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 the53* point size of the Font.54* Most users are familiar with the idea of using <i>point size</i> to55* specify the size of glyphs in a font. This point size defines a56* measurement between the baseline of one line to the baseline of the57* following line in a single spaced text document. The point size is58* 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 equivalent61* to one unit in user coordinates. When using a normalized transform62* for converting user space coordinates to device space coordinates (see63* GraphicsConfiguration.getDefaultTransform() and64* GraphicsConfiguration.getNormalizingTransform()), 72 user space units65* 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 arbitrary68* <i>typographic units</i> (not points) chosen by the font supplier69* and used in the underlying platform font representations. These units are70* defined by dividing the em-square into a grid. The em-sqaure is the71* theoretical square whose dimensions are the full body height of the72* font. A typographic unit is the smallest measurable unit in the73* em-square. The number of units-per-em is determined by the font74* designer. The greater the units-per-em, the greater the precision75* in metrics. For example, Type 1 fonts divide the em-square into a76* 1000 x 1000 grid, while TrueType fonts typically use a 2048 x 204877* grid. The scale of these units can be obtained by calling78* getUnitsPerEm().79* <p>80* Typographic units are relative -- their absolute size changes as the81* size of the of the em-square changes. An em-square is 9 points high82* in a 9-point font. Because typographic units are relative to the83* em-square, a given location on a glyph will have the same coordinates84* in typographic units regardless of the point size.85* <p>86* Converting typographic units to pixels requires computing pixels-per-em87* (ppem). This can be computed as:88* <pre>89ppem = device_resolution * (inches-per-point) * pointSize90* </pre>91* where device resolution could be measured in pixels/inch and the point92* size of a font is effectively points/em. Using a normalized transform93* 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, so95* that an N point font displays N pixels high. In general,96* <pre>97pixel_units = typographic_units * (ppem / units_per_em)98* </pre>99* @see java.awt.Font100* @see java.awt.GraphicsConfiguration#getDefaultTransform101* @see java.awt.GraphicsConfiguration#getNormalizingTransform102*/103104public final class FontDesignMetrics extends FontMetrics {105106/**107* Use serialVersionUID from JDK 1.3 for interoperability.108*/109@Serial110private static final long serialVersionUID = 4480069578560887773L;111112private static final float UNKNOWN_WIDTH = -1;113private static final int CURRENT_VERSION = 1;114115// height, ascent, descent, leading are reported to the client116// as an integer this value is added to the true fp value to117// obtain a value which is usually going to result in a round up118// to the next integer except for very marginal cases.119private static float roundingUpValue = 0.95f;120121// These fields are all part of the old serialization representation122private Font font;123private float ascent;124private float descent;125private float leading;126private float maxAdvance;127private double[] matrix;128private int[] cache; // now unused, still here only for serialization129// End legacy serialization fields130131private int serVersion = 0; // If 1 in readObject, these fields are on the input stream:132private boolean isAntiAliased;133private boolean usesFractionalMetrics;134private AffineTransform frcTx;135136private transient float[] advCache; // transient since values could change across runtimes137private transient int height = -1;138139private transient FontRenderContext frc;140141private transient double[] devmatrix = null;142143private transient FontStrike fontStrike;144145private static FontRenderContext DEFAULT_FRC = null;146147private static FontRenderContext getDefaultFrc() {148149if (DEFAULT_FRC == null) {150AffineTransform tx;151if (GraphicsEnvironment.isHeadless()) {152tx = new AffineTransform();153} else {154tx = GraphicsEnvironment155.getLocalGraphicsEnvironment()156.getDefaultScreenDevice()157.getDefaultConfiguration()158.getDefaultTransform();159}160DEFAULT_FRC = new FontRenderContext(tx, false, false);161}162return DEFAULT_FRC;163}164165/* Strongly cache up to 5 most recently requested FontMetrics objects,166* and softly cache as many as GC allows. In practice this means we167* should keep references around until memory gets low.168* We key the cache either by a Font or a combination of the Font and169* and FRC. A lot of callers use only the font so although there's code170* 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 nulled172* out we can clear the keys from the table.173*/174private static class KeyReference extends SoftReference<Object>175implements DisposerRecord, Disposer.PollDisposable {176177static ReferenceQueue<Object> queue = Disposer.getQueue();178179Object key;180181KeyReference(Object key, Object value) {182super(value, queue);183this.key = key;184Disposer.addReference(this, this);185}186187/* It is possible that since this reference object has been188* enqueued, that a new metrics has been put into the table189* for the same key value. So we'll test to see if the table maps190* 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, but192* it is unlikely and if this were a problem we would need to193* synchronize all 'put' and 'remove' accesses to the cache which194* I would prefer not to do.195*/196public void dispose() {197if (metricsCache.get(key) == this) {198metricsCache.remove(key);199}200}201}202203private static class MetricsKey {204Font font;205FontRenderContext frc;206int hash;207208MetricsKey() {209}210211MetricsKey(Font font, FontRenderContext frc) {212init(font, frc);213}214215void init(Font font, FontRenderContext frc) {216this.font = font;217this.frc = frc;218this.hash = font.hashCode() + frc.hashCode();219}220221public boolean equals(Object key) {222if (!(key instanceof MetricsKey)) {223return false;224}225return226font.equals(((MetricsKey)key).font) &&227frc.equals(((MetricsKey)key).frc);228}229230public int hashCode() {231return hash;232}233234/* Synchronize access to this on the class */235static final MetricsKey key = new MetricsKey();236}237238/* All accesses to a CHM do not in general need to be synchronized,239* as incomplete operations on another thread would just lead to240* harmless cache misses.241*/242private static final ConcurrentHashMap<Object, KeyReference>243metricsCache = new ConcurrentHashMap<Object, KeyReference>();244245private static final int MAXRECENT = 5;246private static final FontDesignMetrics[]247recentMetrics = new FontDesignMetrics[MAXRECENT];248private static int recentIndex = 0;249250public static FontDesignMetrics getMetrics(Font font) {251return getMetrics(font, getDefaultFrc());252}253254public static FontDesignMetrics getMetrics(Font font,255FontRenderContext frc) {256257258/* When using alternate composites, can't cache based just on259* the java.awt.Font. Since this is rarely used and we can still260* cache the physical fonts, its not a problem to just return a261* new instance in this case.262* Note that currently Swing native L&F composites are not handled263* by this code as they use the metrics of the physical anyway.264*/265SunFontManager fm = SunFontManager.getInstance();266if (fm.usingAlternateCompositeFonts() &&267FontUtilities.getFont2D(font) instanceof CompositeFont) {268return new FontDesignMetrics(font, frc);269}270271FontDesignMetrics m = null;272KeyReference r;273274/* 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 key277* that combines the font and FRC.278*/279boolean usefontkey = frc.equals(getDefaultFrc());280281if (usefontkey) {282r = metricsCache.get(font);283} else /* use hybrid key */ {284// NB synchronization is not needed here because of updates to285// the metrics cache but is needed for the shared key.286synchronized (MetricsKey.class) {287MetricsKey.key.init(font, frc);288r = metricsCache.get(MetricsKey.key);289}290}291292if (r != null) {293m = (FontDesignMetrics)r.get();294}295296if (m == null) {297/* either there was no reference, or it was cleared. Need a new298* metrics instance. The key to use in the map is a new299* MetricsKey instance when we've determined the FRC is300* non-default. Its constructed from local vars so we are301* thread-safe - no need to worry about the shared key changing.302*/303m = new FontDesignMetrics(font, frc);304if (usefontkey) {305metricsCache.put(font, new KeyReference(font, m));306} else /* use hybrid key */ {307MetricsKey newKey = new MetricsKey(font, frc);308metricsCache.put(newKey, new KeyReference(newKey, m));309}310}311312/* Here's where we keep the recent metrics */313for (int i=0; i<recentMetrics.length; i++) {314if (recentMetrics[i]==m) {315return m;316}317}318319synchronized (recentMetrics) {320recentMetrics[recentIndex++] = m;321if (recentIndex == MAXRECENT) {322recentIndex = 0;323}324}325return m;326}327328/*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*/333334private FontDesignMetrics(Font font) {335336this(font, getDefaultFrc());337}338339/* private to enable caching - call getMetrics() instead. */340private FontDesignMetrics(Font font, FontRenderContext frc) {341super(font);342this.font = font;343this.frc = frc;344345this.isAntiAliased = frc.isAntiAliased();346this.usesFractionalMetrics = frc.usesFractionalMetrics();347348frcTx = frc.getTransform();349350matrix = new double[4];351initMatrixAndMetrics();352353initAdvCache();354}355356private void initMatrixAndMetrics() {357358Font2D font2D = FontUtilities.getFont2D(font);359fontStrike = font2D.getStrike(font, frc);360StrikeMetrics metrics = fontStrike.getFontMetrics();361this.ascent = metrics.getAscent();362this.descent = metrics.getDescent();363this.leading = metrics.getLeading();364this.maxAdvance = metrics.getMaxAdvance();365366devmatrix = new double[4];367frcTx.getMatrix(devmatrix);368}369370private void initAdvCache() {371advCache = new float[256];372// 0 is a valid metric so force it to -1373for (int i = 0; i < 256; i++) {374advCache[i] = UNKNOWN_WIDTH;375}376}377378@Serial379private void readObject(ObjectInputStream in) throws IOException,380ClassNotFoundException {381382in.defaultReadObject();383if (serVersion != CURRENT_VERSION) {384frc = getDefaultFrc();385isAntiAliased = frc.isAntiAliased();386usesFractionalMetrics = frc.usesFractionalMetrics();387frcTx = frc.getTransform();388}389else {390frc = new FontRenderContext(frcTx, isAntiAliased, usesFractionalMetrics);391}392393// when deserialized, members are set to their default values for their type--394// not to the values assigned during initialization before the constructor395// body!396height = -1;397398cache = null;399400initMatrixAndMetrics();401initAdvCache();402}403404@Serial405private void writeObject(ObjectOutputStream out) throws IOException {406407cache = new int[256];408for (int i=0; i < 256; i++) {409cache[i] = -1;410}411serVersion = CURRENT_VERSION;412413out.defaultWriteObject();414415cache = null;416}417418private float handleCharWidth(int ch) {419return fontStrike.getCodePointAdvance(ch); // x-component of result only420}421422// Uses advCache to get character width423// It is incorrect to call this method for ch > 255424private float getLatinCharWidth(char ch) {425426float w = advCache[ch];427if (w == UNKNOWN_WIDTH) {428w = handleCharWidth(ch);429advCache[ch] = w;430}431return w;432}433434435/* Override of FontMetrics.getFontRenderContext() */436public FontRenderContext getFontRenderContext() {437return frc;438}439440public int charWidth(char ch) {441// default metrics for compatibility with legacy code442float w;443if (ch < 0x100) {444w = getLatinCharWidth(ch);445}446else {447w = handleCharWidth(ch);448}449return (int)(0.5 + w);450}451452public int charWidth(int ch) {453if (!Character.isValidCodePoint(ch)) {454ch = 0xffff;455}456457float w = handleCharWidth(ch);458459return (int)(0.5 + w);460}461462public int stringWidth(String str) {463464float width = 0;465if (font.hasLayoutAttributes()) {466/* TextLayout throws IAE for null, so throw NPE explicitly */467if (str == null) {468throw new NullPointerException("str is null");469}470if (str.length() == 0) {471return 0;472}473width = new TextLayout(str, font, frc).getAdvance();474} else {475int length = str.length();476for (int i=0; i < length; i++) {477char ch = str.charAt(i);478if (ch < 0x100) {479width += getLatinCharWidth(ch);480} else if (FontUtilities.isNonSimpleChar(ch)) {481width = new TextLayout(str, font, frc).getAdvance();482break;483} else {484width += handleCharWidth(ch);485}486}487}488489return (int) (0.5 + width);490}491492public int charsWidth(char[] data, int off, int len) {493494float width = 0;495if (font.hasLayoutAttributes()) {496if (len == 0) {497return 0;498}499String str = new String(data, off, len);500width = new TextLayout(str, font, frc).getAdvance();501} else {502/* Explicit test needed to satisfy superclass spec */503if (len < 0) {504throw new IndexOutOfBoundsException("len="+len);505}506int limit = off + len;507for (int i=off; i < limit; i++) {508char ch = data[i];509if (ch < 0x100) {510width += getLatinCharWidth(ch);511} else if (FontUtilities.isNonSimpleChar(ch)) {512String str = new String(data, off, len);513width = new TextLayout(str, font, frc).getAdvance();514break;515} else {516width += handleCharWidth(ch);517}518}519}520521return (int) (0.5 + width);522}523524/**525* This method is called from java.awt.Font only after verifying526* the arguments and that the text is simple and there are no527* layout attributes, font transform etc.528*/529public Rectangle2D getSimpleBounds(char[] data, int off, int len) {530531float width = 0;532int limit = off + len;533for (int i=off; i < limit; i++) {534char ch = data[i];535if (ch < 0x100) {536width += getLatinCharWidth(ch);537} else {538width += handleCharWidth(ch);539}540}541542float height = ascent + descent + leading;543return new Rectangle2D.Float(0f, -ascent, width, height);544}545546/**547* Gets the advance widths of the first 256 characters in the548* {@code Font}. The advance is the549* distance from the leftmost point to the rightmost point on the550* character's baseline. Note that the advance of a551* {@code String} is not necessarily the sum of the advances552* of its characters.553* @return an array storing the advance widths of the554* characters in the {@code Font}555* described by this {@code FontMetrics} object.556*/557// More efficient than base class implementation - reuses existing cache558public int[] getWidths() {559int[] widths = new int[256];560for (char ch = 0 ; ch < 256 ; ch++) {561float w = advCache[ch];562if (w == UNKNOWN_WIDTH) {563w = advCache[ch] = handleCharWidth(ch);564}565widths[ch] = (int) (0.5 + w);566}567return widths;568}569570public int getMaxAdvance() {571return (int)(0.99f + this.maxAdvance);572}573574/*575* Returns the typographic ascent of the font. This is the maximum distance576* glyphs in this font extend above the base line (measured in typographic577* units).578*/579public int getAscent() {580return (int)(roundingUpValue + this.ascent);581}582583/*584* Returns the typographic descent of the font. This is the maximum distance585* glyphs in this font extend below the base line.586*/587public int getDescent() {588return (int)(roundingUpValue + this.descent);589}590591public int getLeading() {592// nb this ensures the sum of the results of the public methods593// for leading, ascent & descent sum to height.594// if the calculations in any other methods change this needs595// to be changed too.596// the 0.95 value used here and in the other methods allows some597// tiny fraction of leeway before rouding up. A higher value (0.99)598// caused some excessive rounding up.599return600(int)(roundingUpValue + descent + leading) -601(int)(roundingUpValue + descent);602}603604// height is calculated as the sum of two separately rounded up values605// because typically clients use ascent to determine the y location to606// pass to drawString etc and we need to ensure that the height has enough607// space below the baseline to fully contain any descender.608public int getHeight() {609610if (height < 0) {611height = getAscent() + (int)(roundingUpValue + descent + leading);612}613return height;614}615}616617618