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