| 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 */  | 
 | 
 | 
 | 
package sun.font;  | 
 | 
 | 
 | 
import java.lang.ref.ReferenceQueue;  | 
 | 
import java.lang.ref.SoftReference;  | 
 | 
 | 
 | 
import java.awt.FontMetrics;  | 
 | 
import java.awt.Font;  | 
 | 
import java.awt.GraphicsEnvironment;  | 
 | 
import java.awt.geom.AffineTransform;  | 
 | 
import java.awt.geom.NoninvertibleTransformException;  | 
 | 
import java.awt.font.FontRenderContext;  | 
 | 
import java.awt.font.TextLayout;  | 
 | 
 | 
 | 
import java.io.IOException;  | 
 | 
import java.io.ObjectInputStream;  | 
 | 
import java.io.ObjectOutputStream;  | 
 | 
 | 
 | 
import java.util.concurrent.ConcurrentHashMap;  | 
 | 
 | 
 | 
import sun.java2d.Disposer;  | 
 | 
import sun.java2d.DisposerRecord;  | 
 | 
 | 
 | 
/*  | 
 | 
 * This class provides a summary of the glyph measurements  for a Font  | 
 | 
 * and a set of hints that guide their display.  It provides more metrics  | 
 | 
 * information for the Font than the java.awt.FontMetrics class. There  | 
 | 
 * is also some redundancy with that class.  | 
 | 
 * <p>  | 
 | 
 * The design metrics for a Font are obtained from Font.getDesignMetrics().  | 
 | 
 * The FontDesignMetrics object returned will be independent of the  | 
 | 
 * point size of the Font.  | 
 | 
 * Most users are familiar with the idea of using <i>point size</i> to  | 
 | 
 * specify the size of glyphs in a font. This point size defines a  | 
 | 
 * measurement between the baseline of one line to the baseline of the  | 
 | 
 * following line in a single spaced text document. The point size is  | 
 | 
 * based on <i>typographic points</i>, approximately 1/72 of an inch.  | 
 | 
 * <p>  | 
 | 
 * The Java2D API adopts the convention that one point is equivalent  | 
 | 
 * to one unit in user coordinates.  When using a normalized transform  | 
 | 
 * for converting user space coordinates to device space coordinates (see  | 
 | 
 * GraphicsConfiguration.getDefaultTransform() and  | 
 | 
 * GraphicsConfiguration.getNormalizingTransform()), 72 user space units  | 
 | 
 * equal 1 inch in device space.  In this case one point is 1/72 of an inch.  | 
 | 
 * <p>  | 
 | 
 * The FontDesignMetrics class expresses font metrics in terms of arbitrary  | 
 | 
 * <i>typographic units</i> (not points) chosen by the font supplier  | 
 | 
 * and used in the underlying platform font representations.  These units are  | 
 | 
 * defined by dividing the em-square into a grid.  The em-sqaure is the  | 
 | 
 * theoretical square whose dimensions are the full body height of the  | 
 | 
 * font.  A typographic unit is the smallest measurable unit in the  | 
 | 
 * em-square.  The number of units-per-em is determined by the font  | 
 | 
 * designer.  The greater the units-per-em, the greater the precision  | 
 | 
 * in metrics.  For example, Type 1 fonts divide the em-square into a  | 
 | 
 * 1000 x 1000 grid, while TrueType fonts typically use a 2048 x 2048  | 
 | 
 * grid.  The scale of these units can be obtained by calling  | 
 | 
 * getUnitsPerEm().  | 
 | 
 * <p>  | 
 | 
 * Typographic units are relative -- their absolute size changes as the  | 
 | 
 * size of the of the em-square changes.  An em-square is 9 points high  | 
 | 
 * in a 9-point font.  Because typographic units are relative to the  | 
 | 
 * em-square, a given location on a glyph will have the same coordinates  | 
 | 
 * in typographic units regardless of the point size.  | 
 | 
 * <p>  | 
 | 
 * Converting typographic units to pixels requires computing pixels-per-em  | 
 | 
 * (ppem).  This can be computed as:  | 
 | 
 * <pre>  | 
 | 
         ppem = device_resolution * (inches-per-point) * pointSize  | 
 | 
 * </pre>  | 
 | 
 * where device resolution could be measured in pixels/inch and the point  | 
 | 
 * size of a font is effectively points/em.  Using a normalized transform  | 
 | 
 * from user space to device space (see above), results in 1/72 inch/point.  | 
 | 
 * In this case, ppem is equal to the point size on a 72 dpi monitor, so  | 
 | 
 * that an N point font displays N pixels high.  In general,  | 
 | 
 * <pre>  | 
 | 
        pixel_units = typographic_units * (ppem / units_per_em)  | 
 | 
 * </pre>  | 
 | 
 * @see java.awt.Font  | 
 | 
 * @see java.awt.GraphicsConfiguration#getDefaultTransform  | 
 | 
 * @see java.awt.GraphicsConfiguration#getNormalizingTransform  | 
 | 
 */  | 
 | 
 | 
 | 
public final class FontDesignMetrics extends FontMetrics { | 
 | 
 | 
 | 
    static final long serialVersionUID = 4480069578560887773L;  | 
 | 
 | 
 | 
    private static final float UNKNOWN_WIDTH = -1;  | 
 | 
    private static final int CURRENT_VERSION = 1;  | 
 | 
 | 
 | 
    // height, ascent, descent, leading are reported to the client  | 
 | 
    // as an integer this value is added to the true fp value to  | 
 | 
    // obtain a value which is usually going to result in a round up  | 
 | 
      | 
 | 
    private static float roundingUpValue = 0.95f;  | 
 | 
 | 
 | 
      | 
 | 
    private Font  font;  | 
 | 
    private float ascent;  | 
 | 
    private float descent;  | 
 | 
    private float leading;  | 
 | 
    private float maxAdvance;  | 
 | 
    private double[] matrix;  | 
 | 
    private int[] cache;   | 
 | 
    // End legacy serialization fields  | 
 | 
 | 
 | 
    private int serVersion = 0;    | 
 | 
    private boolean isAntiAliased;  | 
 | 
    private boolean usesFractionalMetrics;  | 
 | 
    private AffineTransform frcTx;  | 
 | 
 | 
 | 
    private transient float[] advCache;   | 
 | 
    private transient int height = -1;  | 
 | 
 | 
 | 
    private transient FontRenderContext frc;  | 
 | 
 | 
 | 
    private transient double[] devmatrix = null;  | 
 | 
 | 
 | 
    private transient FontStrike fontStrike;  | 
 | 
 | 
 | 
    private static FontRenderContext DEFAULT_FRC = null;  | 
 | 
 | 
 | 
    private static FontRenderContext getDefaultFrc() { | 
 | 
 | 
 | 
        if (DEFAULT_FRC == null) { | 
 | 
            AffineTransform tx;  | 
 | 
            if (GraphicsEnvironment.isHeadless()) { | 
 | 
                tx = new AffineTransform();  | 
 | 
            } else { | 
 | 
                tx =  GraphicsEnvironment  | 
 | 
                    .getLocalGraphicsEnvironment()  | 
 | 
                    .getDefaultScreenDevice()  | 
 | 
                    .getDefaultConfiguration()  | 
 | 
                    .getDefaultTransform();  | 
 | 
            }  | 
 | 
            DEFAULT_FRC = new FontRenderContext(tx, false, false);  | 
 | 
        }  | 
 | 
        return DEFAULT_FRC;  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private static class KeyReference extends SoftReference  | 
 | 
        implements DisposerRecord, Disposer.PollDisposable { | 
 | 
 | 
 | 
        static ReferenceQueue queue = Disposer.getQueue();  | 
 | 
 | 
 | 
        Object key;  | 
 | 
 | 
 | 
        KeyReference(Object key, Object value) { | 
 | 
            super(value, queue);  | 
 | 
            this.key = key;  | 
 | 
            Disposer.addReference(this, this);  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
         */  | 
 | 
        public void dispose() { | 
 | 
            if (metricsCache.get(key) == this) { | 
 | 
                metricsCache.remove(key);  | 
 | 
            }  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    private static class MetricsKey { | 
 | 
        Font font;  | 
 | 
        FontRenderContext frc;  | 
 | 
        int hash;  | 
 | 
 | 
 | 
        MetricsKey() { | 
 | 
        }  | 
 | 
 | 
 | 
        MetricsKey(Font font, FontRenderContext frc) { | 
 | 
            init(font, frc);  | 
 | 
        }  | 
 | 
 | 
 | 
        void init(Font font, FontRenderContext frc) { | 
 | 
            this.font = font;  | 
 | 
            this.frc = frc;  | 
 | 
            this.hash = font.hashCode() + frc.hashCode();  | 
 | 
        }  | 
 | 
 | 
 | 
        public boolean equals(Object key) { | 
 | 
            if (!(key instanceof MetricsKey)) { | 
 | 
                return false;  | 
 | 
            }  | 
 | 
            return  | 
 | 
                font.equals(((MetricsKey)key).font) &&  | 
 | 
                frc.equals(((MetricsKey)key).frc);  | 
 | 
        }  | 
 | 
 | 
 | 
        public int hashCode() { | 
 | 
            return hash;  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        static final MetricsKey key = new MetricsKey();  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private static final ConcurrentHashMap<Object, KeyReference>  | 
 | 
        metricsCache = new ConcurrentHashMap<Object, KeyReference>();  | 
 | 
 | 
 | 
    private static final int MAXRECENT = 5;  | 
 | 
    private static final FontDesignMetrics[]  | 
 | 
        recentMetrics = new FontDesignMetrics[MAXRECENT];  | 
 | 
    private static int recentIndex = 0;  | 
 | 
 | 
 | 
    public static FontDesignMetrics getMetrics(Font font) { | 
 | 
        return getMetrics(font, getDefaultFrc());  | 
 | 
     }  | 
 | 
 | 
 | 
    public static FontDesignMetrics getMetrics(Font font,  | 
 | 
                                               FontRenderContext frc) { | 
 | 
 | 
 | 
 | 
 | 
          | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
         */  | 
 | 
        SunFontManager fm = SunFontManager.getInstance();  | 
 | 
        if (fm.maybeUsingAlternateCompositeFonts() &&  | 
 | 
            FontUtilities.getFont2D(font) instanceof CompositeFont) { | 
 | 
            return new FontDesignMetrics(font, frc);  | 
 | 
        }  | 
 | 
 | 
 | 
        FontDesignMetrics m = null;  | 
 | 
        KeyReference r;  | 
 | 
 | 
 | 
          | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
         */  | 
 | 
        boolean usefontkey = frc.equals(getDefaultFrc());  | 
 | 
 | 
 | 
        if (usefontkey) { | 
 | 
            r = metricsCache.get(font);  | 
 | 
        } else  { | 
 | 
            // NB synchronization is not needed here because of updates to  | 
 | 
              | 
 | 
            synchronized (MetricsKey.class) { | 
 | 
                MetricsKey.key.init(font, frc);  | 
 | 
                r = metricsCache.get(MetricsKey.key);  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        if (r != null) { | 
 | 
            m = (FontDesignMetrics)r.get();  | 
 | 
        }  | 
 | 
 | 
 | 
        if (m == null) { | 
 | 
              | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
             */  | 
 | 
            m = new FontDesignMetrics(font, frc);  | 
 | 
            if (usefontkey) { | 
 | 
                metricsCache.put(font, new KeyReference(font, m));  | 
 | 
            } else  { | 
 | 
                MetricsKey newKey = new MetricsKey(font, frc);  | 
 | 
                metricsCache.put(newKey, new KeyReference(newKey, m));  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        for (int i=0; i<recentMetrics.length; i++) { | 
 | 
            if (recentMetrics[i]==m) { | 
 | 
                return m;  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        synchronized (recentMetrics) { | 
 | 
            recentMetrics[recentIndex++] = m;  | 
 | 
            if (recentIndex == MAXRECENT) { | 
 | 
                recentIndex = 0;  | 
 | 
            }  | 
 | 
        }  | 
 | 
        return m;  | 
 | 
    }  | 
 | 
 | 
 | 
  /*  | 
 | 
   * Constructs a new FontDesignMetrics object for the given Font.  | 
 | 
   * Its private to enable caching - call getMetrics() instead.  | 
 | 
   * @param font a Font object.  | 
 | 
   */  | 
 | 
 | 
 | 
    private FontDesignMetrics(Font font) { | 
 | 
 | 
 | 
        this(font, getDefaultFrc());  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
    private FontDesignMetrics(Font font, FontRenderContext frc) { | 
 | 
      super(font);  | 
 | 
      this.font = font;  | 
 | 
      this.frc = frc;  | 
 | 
 | 
 | 
      this.isAntiAliased = frc.isAntiAliased();  | 
 | 
      this.usesFractionalMetrics = frc.usesFractionalMetrics();  | 
 | 
 | 
 | 
      frcTx = frc.getTransform();  | 
 | 
 | 
 | 
      matrix = new double[4];  | 
 | 
      initMatrixAndMetrics();  | 
 | 
 | 
 | 
      initAdvCache();  | 
 | 
    }  | 
 | 
 | 
 | 
    private void initMatrixAndMetrics() { | 
 | 
 | 
 | 
        Font2D font2D = FontUtilities.getFont2D(font);  | 
 | 
        fontStrike = font2D.getStrike(font, frc);  | 
 | 
        StrikeMetrics metrics = fontStrike.getFontMetrics();  | 
 | 
        this.ascent = metrics.getAscent();  | 
 | 
        this.descent = metrics.getDescent();  | 
 | 
        this.leading = metrics.getLeading();  | 
 | 
        this.maxAdvance = metrics.getMaxAdvance();  | 
 | 
 | 
 | 
        devmatrix = new double[4];  | 
 | 
        frcTx.getMatrix(devmatrix);  | 
 | 
    }  | 
 | 
 | 
 | 
    private void initAdvCache() { | 
 | 
        advCache = new float[256];  | 
 | 
          | 
 | 
        for (int i = 0; i < 256; i++) { | 
 | 
            advCache[i] = UNKNOWN_WIDTH;  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    private void readObject(ObjectInputStream in) throws IOException,  | 
 | 
                                                  ClassNotFoundException { | 
 | 
 | 
 | 
        in.defaultReadObject();  | 
 | 
        if (serVersion != CURRENT_VERSION) { | 
 | 
            frc = getDefaultFrc();  | 
 | 
            isAntiAliased = frc.isAntiAliased();  | 
 | 
            usesFractionalMetrics = frc.usesFractionalMetrics();  | 
 | 
            frcTx = frc.getTransform();  | 
 | 
        }  | 
 | 
        else { | 
 | 
            frc = new FontRenderContext(frcTx, isAntiAliased, usesFractionalMetrics);  | 
 | 
        }  | 
 | 
 | 
 | 
        // when deserialized, members are set to their default values for their type--  | 
 | 
        // not to the values assigned during initialization before the constructor  | 
 | 
          | 
 | 
        height = -1;  | 
 | 
 | 
 | 
        cache = null;  | 
 | 
 | 
 | 
        initMatrixAndMetrics();  | 
 | 
        initAdvCache();  | 
 | 
    }  | 
 | 
 | 
 | 
    private void writeObject(ObjectOutputStream out) throws IOException { | 
 | 
 | 
 | 
        cache = new int[256];  | 
 | 
        for (int i=0; i < 256; i++) { | 
 | 
            cache[i] = -1;  | 
 | 
        }  | 
 | 
        serVersion = CURRENT_VERSION;  | 
 | 
 | 
 | 
        out.defaultWriteObject();  | 
 | 
 | 
 | 
        cache = null;  | 
 | 
    }  | 
 | 
 | 
 | 
    private float handleCharWidth(int ch) { | 
 | 
        return fontStrike.getCodePointAdvance(ch);   | 
 | 
    }  | 
 | 
 | 
 | 
    // Uses advCache to get character width  | 
 | 
      | 
 | 
    private float getLatinCharWidth(char ch) { | 
 | 
 | 
 | 
        float w = advCache[ch];  | 
 | 
        if (w == UNKNOWN_WIDTH) { | 
 | 
            w = handleCharWidth(ch);  | 
 | 
            advCache[ch] = w;  | 
 | 
        }  | 
 | 
        return w;  | 
 | 
    }  | 
 | 
 | 
 | 
 | 
 | 
      | 
 | 
    public FontRenderContext getFontRenderContext() { | 
 | 
        return frc;  | 
 | 
    }  | 
 | 
 | 
 | 
    public int charWidth(char ch) { | 
 | 
          | 
 | 
        float w;  | 
 | 
        if (ch < 0x100) { | 
 | 
            w = getLatinCharWidth(ch);  | 
 | 
        }  | 
 | 
        else { | 
 | 
            w = handleCharWidth(ch);  | 
 | 
        }  | 
 | 
        return (int)(0.5 + w);  | 
 | 
    }  | 
 | 
 | 
 | 
    public int charWidth(int ch) { | 
 | 
        if (!Character.isValidCodePoint(ch)) { | 
 | 
            ch = 0xffff;  | 
 | 
        }  | 
 | 
 | 
 | 
        float w = handleCharWidth(ch);  | 
 | 
 | 
 | 
        return (int)(0.5 + w);  | 
 | 
    }  | 
 | 
 | 
 | 
    public int stringWidth(String str) { | 
 | 
 | 
 | 
        float width = 0;  | 
 | 
        if (font.hasLayoutAttributes()) { | 
 | 
              | 
 | 
            if (str == null) { | 
 | 
                throw new NullPointerException("str is null"); | 
 | 
            }  | 
 | 
            if (str.length() == 0) { | 
 | 
                return 0;  | 
 | 
            }  | 
 | 
            width = new TextLayout(str, font, frc).getAdvance();  | 
 | 
        } else { | 
 | 
            int length = str.length();  | 
 | 
            for (int i=0; i < length; i++) { | 
 | 
                char ch = str.charAt(i);  | 
 | 
                if (ch < 0x100) { | 
 | 
                    width += getLatinCharWidth(ch);  | 
 | 
                } else if (FontUtilities.isNonSimpleChar(ch)) { | 
 | 
                    width = new TextLayout(str, font, frc).getAdvance();  | 
 | 
                    break;  | 
 | 
                } else { | 
 | 
                    width += handleCharWidth(ch);  | 
 | 
                }  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        return (int) (0.5 + width);  | 
 | 
    }  | 
 | 
 | 
 | 
    public int charsWidth(char data[], int off, int len) { | 
 | 
 | 
 | 
        float width = 0;  | 
 | 
        if (font.hasLayoutAttributes()) { | 
 | 
            if (len == 0) { | 
 | 
                return 0;  | 
 | 
            }  | 
 | 
            String str = new String(data, off, len);  | 
 | 
            width = new TextLayout(str, font, frc).getAdvance();  | 
 | 
        } else { | 
 | 
              | 
 | 
            if (len < 0) { | 
 | 
                throw new IndexOutOfBoundsException("len="+len); | 
 | 
            }  | 
 | 
            int limit = off + len;  | 
 | 
            for (int i=off; i < limit; i++) { | 
 | 
                char ch = data[i];  | 
 | 
                if (ch < 0x100) { | 
 | 
                    width += getLatinCharWidth(ch);  | 
 | 
                } else if (FontUtilities.isNonSimpleChar(ch)) { | 
 | 
                    String str = new String(data, off, len);  | 
 | 
                    width = new TextLayout(str, font, frc).getAdvance();  | 
 | 
                    break;  | 
 | 
                } else { | 
 | 
                    width += handleCharWidth(ch);  | 
 | 
                }  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        return (int) (0.5 + width);  | 
 | 
    }  | 
 | 
 | 
 | 
    /**  | 
 | 
     * Gets the advance widths of the first 256 characters in the  | 
 | 
     * <code>Font</code>.  The advance is the  | 
 | 
     * distance from the leftmost point to the rightmost point on the  | 
 | 
     * character's baseline.  Note that the advance of a  | 
 | 
     * <code>String</code> is not necessarily the sum of the advances  | 
 | 
     * of its characters.  | 
 | 
     * @return    an array storing the advance widths of the  | 
 | 
     *                 characters in the <code>Font</code>  | 
 | 
     *                 described by this <code>FontMetrics</code> object.  | 
 | 
     */  | 
 | 
      | 
 | 
    public int[] getWidths() { | 
 | 
        int[] widths = new int[256];  | 
 | 
        for (char ch = 0 ; ch < 256 ; ch++) { | 
 | 
            float w = advCache[ch];  | 
 | 
            if (w == UNKNOWN_WIDTH) { | 
 | 
                w = advCache[ch] = handleCharWidth(ch);  | 
 | 
            }  | 
 | 
            widths[ch] = (int) (0.5 + w);  | 
 | 
        }  | 
 | 
        return widths;  | 
 | 
    }  | 
 | 
 | 
 | 
    public int getMaxAdvance() { | 
 | 
        return (int)(0.99f + this.maxAdvance);  | 
 | 
    }  | 
 | 
 | 
 | 
    | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
   */  | 
 | 
    public int getAscent() { | 
 | 
        return (int)(roundingUpValue + this.ascent);  | 
 | 
    }  | 
 | 
 | 
 | 
    | 
 | 
 | 
 | 
 | 
 | 
   */  | 
 | 
    public int getDescent() { | 
 | 
        return (int)(roundingUpValue + this.descent);  | 
 | 
    }  | 
 | 
 | 
 | 
    public int getLeading() { | 
 | 
        // nb this ensures the sum of the results of the public methods  | 
 | 
        // for leading, ascent & descent sum to height.  | 
 | 
        // if the calculations in any other methods change this needs  | 
 | 
        // to be changed too.  | 
 | 
        // the 0.95 value used here and in the other methods allows some  | 
 | 
        // tiny fraction of leeway before rouding up. A higher value (0.99)  | 
 | 
          | 
 | 
        return  | 
 | 
            (int)(roundingUpValue + descent + leading) -  | 
 | 
            (int)(roundingUpValue + descent);  | 
 | 
    }  | 
 | 
 | 
 | 
    // height is calculated as the sum of two separately rounded up values  | 
 | 
    // because typically clients use ascent to determine the y location to  | 
 | 
    // pass to drawString etc and we need to ensure that the height has enough  | 
 | 
      | 
 | 
    public int getHeight() { | 
 | 
 | 
 | 
        if (height < 0) { | 
 | 
            height = getAscent() + (int)(roundingUpValue + descent + leading);  | 
 | 
        }  | 
 | 
        return height;  | 
 | 
    }  | 
 | 
}  |