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: Font.java 1827168 2018-03-19 08:49:57Z ssteiner $ */ 19 20 package org.apache.fop.fonts; 21 22 import java.util.Collections; 23 import java.util.List; 24 import java.util.Map; 25 26 import org.apache.commons.logging.Log; 27 import org.apache.commons.logging.LogFactory; 28 29 import org.apache.fop.complexscripts.fonts.Positionable; 30 import org.apache.fop.complexscripts.fonts.Substitutable; 31 import org.apache.fop.render.java2d.CustomFontMetricsMapper; 32 import org.apache.fop.util.CharUtilities; 33 34 /** 35 * This class holds font state information and provides access to the font 36 * metrics. 37 */ 38 public class Font implements Substitutable, Positionable { 39 40 /** Extra Bold font weight */ 41 public static final int WEIGHT_EXTRA_BOLD = 800; 42 43 /** Bold font weight */ 44 public static final int WEIGHT_BOLD = 700; 45 46 /** Normal font weight */ 47 public static final int WEIGHT_NORMAL = 400; 48 49 /** Light font weight */ 50 public static final int WEIGHT_LIGHT = 200; 51 52 /** Normal font style */ 53 public static final String STYLE_NORMAL = "normal"; 54 55 /** Italic font style */ 56 public static final String STYLE_ITALIC = "italic"; 57 58 /** Oblique font style */ 59 public static final String STYLE_OBLIQUE = "oblique"; 60 61 /** Inclined font style */ 62 public static final String STYLE_INCLINED = "inclined"; 63 64 /** Default selection priority */ 65 public static final int PRIORITY_DEFAULT = 0; 66 67 /** Default fallback key */ 68 public static final FontTriplet DEFAULT_FONT = new FontTriplet( 69 "any", STYLE_NORMAL, WEIGHT_NORMAL, PRIORITY_DEFAULT); 70 71 /** logger */ 72 private static Log log = LogFactory.getLog(Font.class); 73 74 private final String fontName; 75 private final FontTriplet triplet; 76 private final int fontSize; 77 78 /** 79 * normal or small-caps font 80 */ 81 //private int fontVariant; 82 83 private final FontMetrics metric; 84 85 /** 86 * Main constructor 87 * @param key key of the font 88 * @param triplet the font triplet that was used to lookup this font (may be null) 89 * @param met font metrics 90 * @param fontSize font size 91 */ Font(String key, FontTriplet triplet, FontMetrics met, int fontSize)92 public Font(String key, FontTriplet triplet, FontMetrics met, int fontSize) { 93 this.fontName = key; 94 this.triplet = triplet; 95 this.metric = met; 96 this.fontSize = fontSize; 97 } 98 99 /** 100 * Returns the associated font metrics object. 101 * @return the font metrics 102 */ getFontMetrics()103 public FontMetrics getFontMetrics() { 104 return this.metric; 105 } 106 107 /** 108 * Determines whether the font is a multibyte font. 109 * @return True if it is multibyte 110 */ isMultiByte()111 public boolean isMultiByte() { 112 return getFontMetrics().isMultiByte(); 113 } 114 115 /** 116 * Returns the font's ascender. 117 * @return the ascender 118 */ getAscender()119 public int getAscender() { 120 return metric.getAscender(fontSize) / 1000; 121 } 122 123 /** 124 * Returns the font's CapHeight. 125 * @return the capital height 126 */ getCapHeight()127 public int getCapHeight() { 128 return metric.getCapHeight(fontSize) / 1000; 129 } 130 131 /** 132 * Returns the font's Descender. 133 * @return the descender 134 */ getDescender()135 public int getDescender() { 136 return metric.getDescender(fontSize) / 1000; 137 } 138 139 /** 140 * Returns the font's name. 141 * @return the font name 142 */ getFontName()143 public String getFontName() { 144 return fontName; 145 } 146 147 /** @return the font triplet that selected this font */ getFontTriplet()148 public FontTriplet getFontTriplet() { 149 return this.triplet; 150 } 151 152 /** 153 * Returns the font size 154 * @return the font size 155 */ getFontSize()156 public int getFontSize() { 157 return fontSize; 158 } 159 160 /** 161 * Returns the XHeight 162 * @return the XHeight 163 */ getXHeight()164 public int getXHeight() { 165 return metric.getXHeight(fontSize) / 1000; 166 } 167 168 /** @return true if the font has kerning info */ hasKerning()169 public boolean hasKerning() { 170 return metric.hasKerningInfo(); 171 } 172 173 /** @return true if the font has feature (i.e., at least one lookup matches) */ hasFeature(int tableType, String script, String language, String feature)174 public boolean hasFeature(int tableType, String script, String language, String feature) { 175 return metric.hasFeature(tableType, script, language, feature); 176 } 177 178 /** 179 * Returns the font's kerning table 180 * @return the kerning table 181 */ getKerning()182 public Map<Integer, Map<Integer, Integer>> getKerning() { 183 if (metric.hasKerningInfo()) { 184 return metric.getKerningInfo(); 185 } else { 186 return Collections.emptyMap(); 187 } 188 } 189 190 /** 191 * Returns the amount of kerning between two characters. 192 * 193 * The value returned measures in pt. So it is already adjusted for font size. 194 * 195 * @param ch1 first character 196 * @param ch2 second character 197 * @return the distance to adjust for kerning, 0 if there's no kerning 198 */ getKernValue(int ch1, int ch2)199 public int getKernValue(int ch1, int ch2) { 200 // Isolate surrogate pair 201 if ((ch1 >= 0xD800) && (ch1 <= 0xE000)) { 202 return 0; 203 } else if ((ch2 >= 0xD800) && (ch2 <= 0xE000)) { 204 return 0; 205 } 206 207 Map<Integer, Integer> kernPair = getKerning().get(ch1); 208 if (kernPair != null) { 209 Integer width = kernPair.get(ch2); 210 if (width != null) { 211 return width * getFontSize() / 1000; 212 } 213 } 214 return 0; 215 } 216 217 /** 218 * Returns the width of a character 219 * @param charnum character to look up 220 * @return width of the character 221 */ getWidth(int charnum)222 public int getWidth(int charnum) { 223 // returns width of given character number in millipoints 224 return (metric.getWidth(charnum, fontSize) / 1000); 225 } 226 227 /** 228 * Map a java character (unicode) to a font character. 229 * Default uses CodePointMapping. 230 * @param c character to map 231 * @return the mapped character 232 */ mapChar(char c)233 public char mapChar(char c) { 234 235 if (metric instanceof org.apache.fop.fonts.Typeface) { 236 return ((org.apache.fop.fonts.Typeface)metric).mapChar(c); 237 } 238 239 // Use default CodePointMapping 240 char d = CodePointMapping.getMapping("WinAnsiEncoding").mapChar(c); 241 if (d != SingleByteEncoding.NOT_FOUND_CODE_POINT) { 242 c = d; 243 } else { 244 log.warn("Glyph " + (int) c + " not available in font " + fontName); 245 c = Typeface.NOT_FOUND; 246 } 247 248 return c; 249 } 250 251 /** 252 * Map a unicode code point to a font character. 253 * Default uses CodePointMapping. 254 * @param cp code point to map 255 * @return the mapped character 256 */ mapCodePoint(int cp)257 public int mapCodePoint(int cp) { 258 FontMetrics fontMetrics = getRealFontMetrics(); 259 260 if (fontMetrics instanceof CIDFont) { 261 return ((CIDFont) fontMetrics).mapCodePoint(cp); 262 } 263 264 if (CharUtilities.isBmpCodePoint(cp)) { 265 return mapChar((char) cp); 266 } 267 268 return Typeface.NOT_FOUND; 269 } 270 271 /** 272 * Determines whether this font contains a particular character/glyph. 273 * @param c character to check 274 * @return True if the character is supported, False otherwise 275 */ hasChar(char c)276 public boolean hasChar(char c) { 277 if (metric instanceof org.apache.fop.fonts.Typeface) { 278 return ((org.apache.fop.fonts.Typeface)metric).hasChar(c); 279 } else { 280 // Use default CodePointMapping 281 return (CodePointMapping.getMapping("WinAnsiEncoding").mapChar(c) > 0); 282 } 283 } 284 285 /** 286 * Determines whether this font contains a particular code point/glyph. 287 * @param cp code point to check 288 * @return True if the code point is supported, False otherwise 289 */ hasCodePoint(int cp)290 public boolean hasCodePoint(int cp) { 291 FontMetrics realFont = getRealFontMetrics(); 292 293 if (realFont instanceof CIDFont) { 294 return ((CIDFont) realFont).hasCodePoint(cp); 295 } 296 297 if (CharUtilities.isBmpCodePoint(cp)) { 298 return hasChar((char) cp); 299 } 300 301 return false; 302 } 303 304 /** 305 * Get the real underlying font if it is wrapped inside some container such as a {@link LazyFont} or a 306 * {@link CustomFontMetricsMapper}. 307 * 308 * @return instance of the font 309 */ getRealFontMetrics()310 private FontMetrics getRealFontMetrics() { 311 FontMetrics realFontMetrics = metric; 312 313 if (realFontMetrics instanceof CustomFontMetricsMapper) { 314 realFontMetrics = ((CustomFontMetricsMapper) realFontMetrics).getRealFont(); 315 } 316 317 if (realFontMetrics instanceof LazyFont) { 318 return ((LazyFont) realFontMetrics).getRealFont(); 319 } 320 321 return realFontMetrics; 322 } 323 324 /** 325 * {@inheritDoc} 326 */ 327 @Override toString()328 public String toString() { 329 StringBuffer sbuf = new StringBuffer(super.toString()); 330 sbuf.append('{'); 331 /* 332 sbuf.append(fontFamily); 333 sbuf.append(',');*/ 334 sbuf.append(fontName); 335 sbuf.append(','); 336 sbuf.append(fontSize); 337 /* 338 sbuf.append(','); 339 sbuf.append(fontStyle); 340 sbuf.append(','); 341 sbuf.append(fontWeight);*/ 342 sbuf.append('}'); 343 return sbuf.toString(); 344 } 345 346 /** 347 * Helper method for getting the width of a unicode char 348 * from the current fontstate. 349 * This also performs some guessing on widths on various 350 * versions of space that might not exists in the font. 351 * @param c character to inspect 352 * @return the width of the character or -1 if no width available 353 */ getCharWidth(char c)354 public int getCharWidth(char c) { 355 int width; 356 357 if ((c == '\n') || (c == '\r') || (c == '\t') || (c == '\u00A0')) { 358 width = getCharWidth(' '); 359 } else { 360 if (hasChar(c)) { 361 int mappedChar = mapChar(c); 362 width = getWidth(mappedChar); 363 } else { 364 width = -1; 365 } 366 if (width <= 0) { 367 // Estimate the width of spaces not represented in 368 // the font 369 int em = getFontSize(); //http://en.wikipedia.org/wiki/Em_(typography) 370 int en = em / 2; //http://en.wikipedia.org/wiki/En_(typography) 371 372 if (c == ' ') { 373 width = em; 374 } else if (c == '\u2000') { 375 width = en; 376 } else if (c == '\u2001') { 377 width = em; 378 } else if (c == '\u2002') { 379 width = em / 2; 380 } else if (c == '\u2003') { 381 width = getFontSize(); 382 } else if (c == '\u2004') { 383 width = em / 3; 384 } else if (c == '\u2005') { 385 width = em / 4; 386 } else if (c == '\u2006') { 387 width = em / 6; 388 } else if (c == '\u2007') { 389 width = getCharWidth('0'); 390 } else if (c == '\u2008') { 391 width = getCharWidth('.'); 392 } else if (c == '\u2009') { 393 width = em / 5; 394 } else if (c == '\u200A') { 395 width = em / 10; 396 } else if (c == '\u200B') { 397 width = 0; 398 } else if (c == '\u202F') { 399 width = getCharWidth(' ') / 2; 400 } else if (c == '\u2060') { 401 width = 0; 402 } else if (c == '\u3000') { 403 width = getCharWidth(' ') * 2; 404 } else if (c == '\ufeff') { 405 width = 0; 406 } else { 407 //Will be internally replaced by "#" if not found 408 width = getWidth(mapChar(c)); 409 } 410 } 411 } 412 413 return width; 414 } 415 416 /** 417 * Helper method for getting the width of a unicode char 418 * from the current fontstate. 419 * This also performs some guessing on widths on various 420 * versions of space that might not exists in the font. 421 * @param c character to inspect 422 * @return the width of the character or -1 if no width available 423 */ getCharWidth(int c)424 public int getCharWidth(int c) { 425 if (c < 0x10000) { 426 return getCharWidth((char) c); 427 } 428 429 if (hasCodePoint(c)) { 430 int mappedChar = mapCodePoint(c); 431 return getWidth(mappedChar); 432 } 433 434 return -1; 435 } 436 437 /** 438 * Calculates the word width. 439 * @param word text to get width for 440 * @return the width of the text 441 */ getWordWidth(String word)442 public int getWordWidth(String word) { 443 if (word == null) { 444 return 0; 445 } 446 int wordLength = word.length(); 447 int width = 0; 448 char[] characters = new char[wordLength]; 449 word.getChars(0, wordLength, characters, 0); 450 for (int i = 0; i < wordLength; i++) { 451 width += getCharWidth(characters[i]); 452 } 453 return width; 454 } 455 456 /** {@inheritDoc} */ performsSubstitution()457 public boolean performsSubstitution() { 458 if (metric instanceof Substitutable) { 459 Substitutable s = (Substitutable) metric; 460 return s.performsSubstitution(); 461 } else { 462 return false; 463 } 464 } 465 466 /** {@inheritDoc} */ performSubstitution(CharSequence cs, String script, String language, List associations, boolean retainControls)467 public CharSequence performSubstitution(CharSequence cs, 468 String script, String language, List associations, boolean retainControls) { 469 if (metric instanceof Substitutable) { 470 Substitutable s = (Substitutable) metric; 471 return s.performSubstitution(cs, script, language, associations, retainControls); 472 } else { 473 throw new UnsupportedOperationException(); 474 } 475 } 476 477 /** {@inheritDoc} */ reorderCombiningMarks(CharSequence cs, int[][] gpa, String script, String language, List associations)478 public CharSequence reorderCombiningMarks(CharSequence cs, int[][] gpa, 479 String script, String language, List associations) { 480 if (metric instanceof Substitutable) { 481 Substitutable s = (Substitutable) metric; 482 return s.reorderCombiningMarks(cs, gpa, script, language, associations); 483 } else { 484 throw new UnsupportedOperationException(); 485 } 486 } 487 488 /** {@inheritDoc} */ performsPositioning()489 public boolean performsPositioning() { 490 if (metric instanceof Positionable) { 491 Positionable p = (Positionable) metric; 492 return p.performsPositioning(); 493 } else { 494 return false; 495 } 496 } 497 498 /** {@inheritDoc} */ performPositioning(CharSequence cs, String script, String language, int fontSize)499 public int[][] performPositioning(CharSequence cs, String script, String language, int fontSize) { 500 if (metric instanceof Positionable) { 501 Positionable p = (Positionable) metric; 502 return p.performPositioning(cs, script, language, fontSize); 503 } else { 504 throw new UnsupportedOperationException(); 505 } 506 } 507 508 /** {@inheritDoc} */ performPositioning(CharSequence cs, String script, String language)509 public int[][] performPositioning(CharSequence cs, String script, String language) { 510 return performPositioning(cs, script, language, fontSize); 511 } 512 513 } 514