1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 /* $Id: Java2DFontMetrics.java 1758773 2016-09-01 13:02:29Z ssteiner $ */
19 
20 package org.apache.fop.render.java2d;
21 
22 // Java
23 import java.awt.Font;
24 import java.awt.FontMetrics;
25 import java.awt.Graphics2D;
26 import java.awt.RenderingHints;
27 import java.awt.font.LineMetrics;
28 import java.awt.font.TextAttribute;
29 import java.awt.font.TextLayout;
30 import java.awt.geom.Rectangle2D;
31 import java.awt.image.BufferedImage;
32 import java.util.Map;
33 
34 /**
35  * This is a FontMetrics to be used  for AWT  rendering.
36  * It  instanciates a font, depening on family and style
37  * values. The java.awt.FontMetrics for this font is then
38  * created to be used for the actual measurement.
39  * Since layout is word by word and since it is expected that
40  * two subsequent words often share the same style, the
41  * Font and FontMetrics is buffered and only changed if needed.
42  * <p>
43  * Since FontState and FontInfo multiply all factors by
44  * size, we assume a "standard" font of FONT_SIZE.
45  */
46 public class Java2DFontMetrics {
47 
48     /**
49      * Font size standard used for metric measurements
50      */
51     public static final int FONT_SIZE = 1;
52 
53     /**
54      * This factor multiplies the calculated values to scale
55      * to FOP internal measurements
56      */
57     public static final int FONT_FACTOR = (1000 * 1000) / FONT_SIZE;
58 
59     /**
60      * The width of all 256 character, if requested
61      */
62     private int[] width;
63 
64     /**
65      * The typical height of a small cap latter (often derived from "x", value in mpt)
66      */
67     private int xHeight;
68 
69     /**
70      * The highest point of the font above the baseline (usually derived from "d", value in mpt)
71      */
72     private int ascender;
73 
74     /**
75      * The lowest point of the font under the baseline (usually derived from "p", value in mpt)
76      */
77     private int descender;
78 
79     /**
80      * Buffered font.
81      * f1 is bufferd for metric measurements during layout.
82      * fSized is buffered for display purposes
83      */
84     private Font f1;    // , fSized;
85 
86     /**
87      * The family type of the font last used
88      */
89     private String family = "";
90 
91     /**
92      * The style of the font last used
93      */
94     private int style;
95 
96     /**
97      * The size of the font last used
98      */
99     private float size;
100 
101     /**
102      * The FontMetrics object used to calculate character width etc.
103      */
104     private FontMetrics fmt;
105 
106     /** A LineMetrics to access high-resolution metrics information. */
107     private LineMetrics lineMetrics;
108 
109     /**
110      * Temp graphics object needed to get the font metrics
111      */
112     private final Graphics2D graphics;
113 
114     /**
115      * Creates a Graphics2D object for the sole purpose of getting font metrics.
116      * @return a Graphics2D object
117      */
createFontMetricsGraphics2D()118     private static Graphics2D createFontMetricsGraphics2D() {
119         BufferedImage fontImage = new BufferedImage(100, 100,
120                 BufferedImage.TYPE_INT_RGB);
121         Graphics2D graphics2D = fontImage.createGraphics();
122         //The next line is important to get accurate font metrics!
123         graphics2D.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
124                 RenderingHints.VALUE_FRACTIONALMETRICS_ON);
125         return graphics2D;
126     }
127 
128     /**
129      * Constructs a new Font-metrics.
130      */
Java2DFontMetrics()131     public Java2DFontMetrics() {
132         this.graphics = createFontMetricsGraphics2D();
133     }
134 
135     /**
136      * Determines the font's maximum ascent of the Font described by the current
137      * FontMetrics object
138      * @param family font family (java name) to use
139      * @param style font style (java def.) to use
140      * @param size font size
141      * @return ascent in milliponts
142      */
getMaxAscent(String family, int style, int size)143     public int getMaxAscent(String family, int style, int size) {
144         setFont(family, style, size);
145         return Math.round(lineMetrics.getAscent() * FONT_FACTOR);
146     }
147 
148     /**
149      * Determines the font ascent of the Font described by this
150      * FontMetrics object
151      * @param family font family (java name) to use
152      * @param style font style (java def.) to use
153      * @param size font size
154      * @return ascent in milliponts
155      */
getAscender(String family, int style, int size)156     public int getAscender(String family, int style, int size) {
157         setFont(family, style, size);
158         return ascender * 1000;
159 
160         // workaround for sun bug on FontMetrics.getAscent()
161         // http://developer.java.sun.com/developer/bugParade/bugs/4399887.html
162         /*
163          * Bug 4399887 has status Closed, not a bug.  The comments on the bug
164          * are:
165          * The submitter is incorrectly assuming that the string he has used
166          * is displaying characters which represent those with the maximum
167          * ascent in the font. If (for example) the unicode character
168          * \u00c1 which is the A-acute character used in many European
169          * languages is placed in the bodu of the "Wrong" string it can be
170          * seen that the JDK is exactly correct in its determination of the
171          * ascent of the font.
172          * If the bounds of a particular string are interesting then the
173          * Rectangle FontMetrics.getStringBounds(..) method can be called.
174          * The y value of the rectangle is the offset from the origin
175          * (baseline) apparently needed by the sample test program
176          *
177          * xxxxx@xxxxx 2001-05-15
178          */
179         /* I don't think this is right.
180         int realAscent = fmt.getAscent()
181                          - (fmt.getDescent() + fmt.getLeading());
182         return FONT_FACTOR * realAscent;
183         */
184     }
185 
186 
187     /**
188      * The size of a capital letter measured from the font's baseline
189      * @param family font family
190      * @param style font style
191      * @param size font size
192      * @return capital height in millipoints
193      */
getCapHeight(String family, int style, int size)194     public int getCapHeight(String family, int style, int size) {
195         // currently just gets Ascent value but maybe should use
196         // getMaxAcent() at some stage
197         return getAscender(family, style, size);
198     }
199 
200     /**
201      * Determines the font descent of the Font described by this
202      * FontMetrics object
203      * @param family font family (jave name) to use
204      * @param style font style (jave def.) to use
205      * @param size font size
206      * @return descent in milliponts
207      */
getDescender(String family, int style, int size)208     public int getDescender(String family, int style, int size) {
209         setFont(family, style, size);
210         return descender * 1000;
211     }
212 
213     /**
214      * Determines the typical font height of a small cap letter
215      * FontMetrics object
216      * @param family font family (jave name) to use
217      * @param style font style (jave def.) to use
218      * @param size font size
219      * @return font height in milliponts
220      */
getXHeight(String family, int style, int size)221     public int getXHeight(String family, int style, int size) {
222         setFont(family, style, size);
223         return xHeight * 1000;
224     }
225 
getUnderlinePosition(String family, int style, int size)226     public int getUnderlinePosition(String family, int style, int size) {
227         setFont(family, style, size);
228         return -Math.round(lineMetrics.getUnderlineOffset());
229     }
230 
getUnderlineThickness(String family, int style, int size)231     public int getUnderlineThickness(String family, int style, int size) {
232         setFont(family, style, size);
233         return Math.round(lineMetrics.getUnderlineThickness());
234     }
235 
getStrikeoutPosition(String family, int style, int size)236     public int getStrikeoutPosition(String family, int style, int size) {
237         setFont(family, style, size);
238         return -Math.round(lineMetrics.getStrikethroughOffset());
239     }
240 
getStrikeoutThickness(String family, int style, int size)241     public int getStrikeoutThickness(String family, int style, int size) {
242         setFont(family, style, size);
243         return Math.round(lineMetrics.getStrikethroughThickness());
244     }
245 
246     /**
247      * Returns width (in 1/1000ths of point size) of character at
248      * code point i
249      * @param  i the character for which to get the width
250      * @param family font family (jave name) to use
251      * @param style font style (jave def.) to use
252      * @param size font size
253      * @return character width in millipoints
254      */
width(int i, String family, int style, int size)255     public int width(int i, String family, int style, int size) {
256         int w;
257         setFont(family, style, size);
258         w = internalCharWidth(i) * 1000;
259         return w;
260     }
261 
internalCharWidth(int i)262     private int internalCharWidth(int i) {
263         //w = (int)(fmt.charWidth(i) * 1000); //Not accurate enough!
264         char[] ch = {(char)i};
265         Rectangle2D rect = fmt.getStringBounds(ch, 0, 1, this.graphics);
266         return (int)Math.round(rect.getWidth() * 1000);
267     }
268 
269     /**
270      * Return widths (in 1/1000ths of point size) of all
271      * characters
272      * @param family font family (jave name) to use
273      * @param style font style (jave def.) to use
274      * @param size font size
275      * @return array of character widths in millipoints
276      */
getWidths(String family, int style, int size)277     public int[] getWidths(String family, int style, int size) {
278         int i;
279 
280         if (width == null) {
281             width = new int[256];
282         }
283         setFont(family, style, size);
284         for (i = 0; i < 256; i++) {
285             width[i] = 1000 * internalCharWidth(i);
286         }
287         return width;
288     }
289 
getBaseFont(String family, int style, float size)290     private Font getBaseFont(String family, int style, float size) {
291         Map atts = new java.util.HashMap();
292         atts.put(TextAttribute.FAMILY, family);
293         if ((style & Font.BOLD) != 0) {
294             atts.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
295         }
296         if ((style & Font.ITALIC) != 0) {
297             atts.put(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
298         }
299         atts.put(TextAttribute.SIZE, size); //size in pt
300         return new Font(atts);
301     }
302 
303     /**
304      * Checks whether the font  for which values are
305      * requested is the one used immediately before or
306      * whether it is a new one
307      * @param family font family (jave name) to use
308      * @param style font style (jave def.) to use
309      * @param size font size
310      * @return true if the font was changed, false otherwise
311      */
setFont(String family, int style, int size)312     private boolean setFont(String family, int style, int size) {
313         boolean changed = false;
314         float s = size / 1000f;
315 
316         if (f1 == null) {
317             f1 = getBaseFont(family, style, s);
318             fmt = graphics.getFontMetrics(f1);
319             changed = true;
320         } else {
321             if ((this.style != style) || !this.family.equals(family)
322                     || this.size != s) {
323                 if (family.equals(this.family)) {
324                     f1 = f1.deriveFont(style, s);
325                 } else {
326                     f1 = getBaseFont(family, style, s);
327                 }
328                 fmt = graphics.getFontMetrics(f1);
329                 changed = true;
330             }
331             // else the font is unchanged from last time
332         }
333         if (changed) {
334             //x-Height
335             TextLayout layout = new TextLayout("x", f1, graphics.getFontRenderContext());
336             Rectangle2D rect = layout.getBounds();
337             xHeight = (int)Math.round(-rect.getY() * 1000);
338 
339             //PostScript-compatible ascent
340             layout = new TextLayout("d", f1, graphics.getFontRenderContext());
341             rect = layout.getBounds();
342             ascender = (int)Math.round(-rect.getY() * 1000);
343 
344             //PostScript-compatible descent
345             layout = new TextLayout("p", f1, graphics.getFontRenderContext());
346             rect = layout.getBounds();
347             descender = (int)Math.round((rect.getY() + rect.getHeight()) * -1000);
348 
349             //Alternative way to get metrics but the ascender is again wrong for our purposes
350             lineMetrics = f1.getLineMetrics("", graphics.getFontRenderContext());
351         }
352         // save the family and style for later comparison
353         this.family = family;
354         this.style = style;
355         this.size = s;
356         return changed;
357     }
358 
359 
360     /**
361      * Returns a java.awt.Font instance for the desired
362      * family, style and size type.
363      * This is here, so that the font-mapping
364      * of FOP-defined fonts to java-fonts can be done
365      * in one place and does not need to occur in
366      * AWTFontRenderer.
367      * @param family font family (jave name) to use
368      * @param style font style (jave def.) to use
369      * @param size font size
370      * @return font with the desired characeristics.
371      */
getFont(String family, int style, int size)372     public java.awt.Font getFont(String family, int style, int size) {
373         setFont(family, style, size);
374         return f1;
375         /*
376          * if( setFont(family,style, size) ) fSized = null;
377          * if( fSized == null ||  this.size != size ) {
378          * fSized = f1.deriveFont( size / 1000f );
379          * }
380          * this.size = size;
381          * return fSized;
382          */
383     }
384 
385     /**
386      * Indicates whether the font contains a particular character/glyph.
387      * @param family font family (jave name) to use
388      * @param style font style (jave def.) to use
389      * @param size font size
390      * @param c the glyph to check
391      * @return true if the character is supported
392      */
hasChar(String family, int style, int size, char c)393     public boolean hasChar(String family, int style, int size, char c) {
394         setFont(family, style, size);
395         return f1.canDisplay(c);
396     }
397 
398 }
399 
400 
401 
402 
403 
404 
405