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: LazyFont.java 1885366 2021-01-11 15:00:20Z ssteiner $ */ 19 20 package org.apache.fop.fonts; 21 import java.awt.Rectangle; 22 import java.io.InputStream; 23 import java.net.URI; 24 import java.util.List; 25 import java.util.Map; 26 import java.util.Set; 27 28 import org.xml.sax.InputSource; 29 30 import org.apache.commons.logging.Log; 31 import org.apache.commons.logging.LogFactory; 32 33 import org.apache.fop.apps.io.InternalResourceResolver; 34 import org.apache.fop.complexscripts.fonts.Positionable; 35 import org.apache.fop.complexscripts.fonts.Substitutable; 36 37 /** 38 * This class is used to defer the loading of a font until it is really used. 39 */ 40 public class LazyFont extends Typeface implements FontDescriptor, Substitutable, Positionable { 41 42 private static Log log = LogFactory.getLog(LazyFont.class); 43 44 private final FontUris fontUris; 45 46 private final boolean useKerning; 47 private final boolean useAdvanced; 48 private boolean simulateStyle; 49 private boolean embedAsType1; 50 private boolean useSVG; 51 private final EncodingMode encodingMode; 52 private final EmbeddingMode embeddingMode; 53 private final String subFontName; 54 private final boolean embedded; 55 private final InternalResourceResolver resourceResolver; 56 57 private boolean isMetricsLoaded; 58 private Typeface realFont; 59 private FontDescriptor realFontDescriptor; 60 61 /** 62 * Main constructor 63 * @param fontInfo the font info to embed 64 * @param resourceResolver the font resolver to handle font URIs 65 */ LazyFont(EmbedFontInfo fontInfo, InternalResourceResolver resourceResolver, boolean useComplexScripts)66 public LazyFont(EmbedFontInfo fontInfo, InternalResourceResolver resourceResolver, 67 boolean useComplexScripts) { 68 69 this.fontUris = fontInfo.getFontUris(); 70 this.useKerning = fontInfo.getKerning(); 71 if (resourceResolver != null) { 72 this.useAdvanced = useComplexScripts; 73 } else { 74 this.useAdvanced = fontInfo.getAdvanced(); 75 } 76 this.simulateStyle = fontInfo.getSimulateStyle(); 77 this.embedAsType1 = fontInfo.getEmbedAsType1(); 78 useSVG = fontInfo.getUseSVG(); 79 this.encodingMode = fontInfo.getEncodingMode() != null ? fontInfo.getEncodingMode() 80 : EncodingMode.AUTO; 81 this.embeddingMode = fontInfo.getEmbeddingMode() != null ? fontInfo.getEmbeddingMode() 82 : EmbeddingMode.AUTO; 83 this.subFontName = fontInfo.getSubFontName(); 84 this.embedded = fontInfo.isEmbedded(); 85 this.resourceResolver = resourceResolver; 86 } 87 88 /** {@inheritDoc} */ toString()89 public String toString() { 90 StringBuffer sbuf = new StringBuffer(super.toString()); 91 sbuf.append('{'); 92 sbuf.append("metrics-url=" + fontUris.getMetrics()); 93 sbuf.append(",embed-url=" + fontUris.getEmbed()); 94 sbuf.append(",kerning=" + useKerning); 95 sbuf.append(",advanced=" + useAdvanced); 96 sbuf.append('}'); 97 return sbuf.toString(); 98 } 99 load(boolean fail)100 private void load(boolean fail) { 101 if (!isMetricsLoaded) { 102 try { 103 if (fontUris.getMetrics() != null) { 104 // Use of XML based font metrics is DEPRECATED! 105 // @todo Possible thread problem here 106 XMLFontMetricsReader reader = null; 107 InputStream in = resourceResolver.getResource(fontUris.getMetrics()); 108 InputSource src = new InputSource(in); 109 src.setSystemId(fontUris.getMetrics().toASCIIString()); 110 reader = new XMLFontMetricsReader(src, resourceResolver); 111 reader.setKerningEnabled(useKerning); 112 reader.setAdvancedEnabled(useAdvanced); 113 if (this.embedded) { 114 reader.setFontEmbedURI(fontUris.getEmbed()); 115 } 116 realFont = reader.getFont(); 117 } else { 118 if (fontUris.getEmbed() == null) { 119 throw new RuntimeException("Cannot load font. No font URIs available."); 120 } 121 realFont = FontLoader.loadFont(fontUris, subFontName, embedded, embeddingMode, encodingMode, 122 useKerning, useAdvanced, resourceResolver, simulateStyle, embedAsType1, useSVG); 123 } 124 if (realFont instanceof FontDescriptor) { 125 realFontDescriptor = (FontDescriptor) realFont; 126 } 127 } catch (RuntimeException e) { 128 String error = "Failed to read font file " + fontUris.getEmbed() + " " + e.getMessage(); 129 throw new RuntimeException(error, e); 130 } catch (Exception e) { 131 String error = "Failed to read font file " + fontUris.getEmbed() + " " + e.getMessage(); 132 log.error(error, e); 133 if (fail) { 134 throw new RuntimeException(error, e); 135 } 136 } 137 realFont.setEventListener(this.eventListener); 138 isMetricsLoaded = true; 139 } 140 } 141 142 /** 143 * Gets the real font. 144 * @return the real font 145 */ getRealFont()146 public Typeface getRealFont() { 147 load(false); 148 return realFont; 149 } 150 151 // ---- Font ---- 152 /** {@inheritDoc} */ getEncodingName()153 public String getEncodingName() { 154 load(true); 155 return realFont.getEncodingName(); 156 } 157 158 /** 159 * {@inheritDoc} 160 */ mapChar(char c)161 public char mapChar(char c) { 162 if (!isMetricsLoaded) { 163 load(true); 164 } 165 return realFont.mapChar(c); 166 } 167 168 /** 169 * {@inheritDoc} 170 */ hadMappingOperations()171 public boolean hadMappingOperations() { 172 load(true); 173 return realFont.hadMappingOperations(); 174 } 175 176 /** 177 * {@inheritDoc} 178 */ hasChar(char c)179 public boolean hasChar(char c) { 180 if (!isMetricsLoaded) { 181 load(true); 182 } 183 return realFont.hasChar(c); 184 } 185 186 /** 187 * {@inheritDoc} 188 */ isMultiByte()189 public boolean isMultiByte() { 190 load(true); 191 return realFont.isMultiByte(); 192 } 193 194 // ---- FontMetrics interface ---- 195 /** {@inheritDoc} */ getFontURI()196 public URI getFontURI() { 197 load(true); 198 return realFont.getFontURI(); 199 } 200 201 /** {@inheritDoc} */ getFontName()202 public String getFontName() { 203 load(true); 204 return realFont.getFontName(); 205 } 206 207 /** {@inheritDoc} */ getEmbedFontName()208 public String getEmbedFontName() { 209 load(true); 210 return realFont.getEmbedFontName(); 211 } 212 213 /** {@inheritDoc} */ getFullName()214 public String getFullName() { 215 load(true); 216 return realFont.getFullName(); 217 } 218 219 /** {@inheritDoc} */ getFamilyNames()220 public Set<String> getFamilyNames() { 221 load(true); 222 return realFont.getFamilyNames(); 223 } 224 225 /** 226 * {@inheritDoc} 227 */ getMaxAscent(int size)228 public int getMaxAscent(int size) { 229 load(true); 230 return realFont.getMaxAscent(size); 231 } 232 233 /** 234 * {@inheritDoc} 235 */ getAscender(int size)236 public int getAscender(int size) { 237 load(true); 238 return realFont.getAscender(size); 239 } 240 241 /** 242 * {@inheritDoc} 243 */ getCapHeight(int size)244 public int getCapHeight(int size) { 245 load(true); 246 return realFont.getCapHeight(size); 247 } 248 249 /** 250 * {@inheritDoc} 251 */ getDescender(int size)252 public int getDescender(int size) { 253 load(true); 254 return realFont.getDescender(size); 255 } 256 257 /** 258 * {@inheritDoc} 259 */ getXHeight(int size)260 public int getXHeight(int size) { 261 load(true); 262 return realFont.getXHeight(size); 263 } 264 getUnderlinePosition(int size)265 public int getUnderlinePosition(int size) { 266 load(true); 267 return realFont.getUnderlinePosition(size); 268 } 269 getUnderlineThickness(int size)270 public int getUnderlineThickness(int size) { 271 load(true); 272 return realFont.getUnderlineThickness(size); 273 } 274 getStrikeoutPosition(int size)275 public int getStrikeoutPosition(int size) { 276 load(true); 277 return realFont.getStrikeoutPosition(size); 278 } 279 getStrikeoutThickness(int size)280 public int getStrikeoutThickness(int size) { 281 load(true); 282 return realFont.getStrikeoutThickness(size); 283 } 284 285 /** 286 * {@inheritDoc} 287 */ getWidth(int i, int size)288 public int getWidth(int i, int size) { 289 if (!isMetricsLoaded) { 290 load(true); 291 } 292 return realFont.getWidth(i, size); 293 } 294 295 /** 296 * {@inheritDoc} 297 */ getWidths()298 public int[] getWidths() { 299 load(true); 300 return realFont.getWidths(); 301 } 302 getBoundingBox(int glyphIndex, int size)303 public Rectangle getBoundingBox(int glyphIndex, int size) { 304 load(true); 305 return realFont.getBoundingBox(glyphIndex, size); 306 } 307 308 /** 309 * {@inheritDoc} 310 */ hasKerningInfo()311 public boolean hasKerningInfo() { 312 load(true); 313 return realFont.hasKerningInfo(); 314 } 315 316 /** 317 * {@inheritDoc} 318 */ getKerningInfo()319 public Map<Integer, Map<Integer, Integer>> getKerningInfo() { 320 load(true); 321 return realFont.getKerningInfo(); 322 } 323 324 /** {@inheritDoc} */ hasFeature(int tableType, String script, String language, String feature)325 public boolean hasFeature(int tableType, String script, String language, String feature) { 326 load(true); 327 return realFont.hasFeature(tableType, script, language, feature); 328 } 329 330 // ---- FontDescriptor interface ---- 331 /** 332 * {@inheritDoc} 333 */ getCapHeight()334 public int getCapHeight() { 335 load(true); 336 return realFontDescriptor.getCapHeight(); 337 } 338 339 /** 340 * {@inheritDoc} 341 */ getDescender()342 public int getDescender() { 343 load(true); 344 return realFontDescriptor.getDescender(); 345 } 346 347 /** 348 * {@inheritDoc} 349 */ getAscender()350 public int getAscender() { 351 load(true); 352 return realFontDescriptor.getAscender(); 353 } 354 355 /** {@inheritDoc} */ getFlags()356 public int getFlags() { 357 load(true); 358 return realFontDescriptor.getFlags(); 359 } 360 361 /** {@inheritDoc} */ isSymbolicFont()362 public boolean isSymbolicFont() { 363 load(true); 364 return realFontDescriptor.isSymbolicFont(); 365 } 366 367 /** 368 * {@inheritDoc} 369 */ getFontBBox()370 public int[] getFontBBox() { 371 load(true); 372 return realFontDescriptor.getFontBBox(); 373 } 374 375 /** 376 * {@inheritDoc} 377 */ getItalicAngle()378 public int getItalicAngle() { 379 load(true); 380 return realFontDescriptor.getItalicAngle(); 381 } 382 383 /** 384 * {@inheritDoc} 385 */ getStemV()386 public int getStemV() { 387 load(true); 388 return realFontDescriptor.getStemV(); 389 } 390 391 /** 392 * {@inheritDoc} 393 */ getFontType()394 public FontType getFontType() { 395 load(true); 396 return realFontDescriptor.getFontType(); 397 } 398 399 /** 400 * {@inheritDoc} 401 */ isEmbeddable()402 public boolean isEmbeddable() { 403 load(true); 404 return realFontDescriptor.isEmbeddable(); 405 } 406 407 /** 408 * {@inheritDoc} 409 */ performsSubstitution()410 public boolean performsSubstitution() { 411 load(true); 412 if (realFontDescriptor instanceof Substitutable) { 413 return ((Substitutable)realFontDescriptor).performsSubstitution(); 414 } else { 415 return false; 416 } 417 } 418 419 /** 420 * {@inheritDoc} 421 */ performSubstitution(CharSequence cs, String script, String language, List associations, boolean retainControls)422 public CharSequence performSubstitution(CharSequence cs, String script, String language, List associations, 423 boolean retainControls) { 424 load(true); 425 if (realFontDescriptor instanceof Substitutable) { 426 return ((Substitutable)realFontDescriptor).performSubstitution(cs, 427 script, language, associations, retainControls); 428 } else { 429 return cs; 430 } 431 } 432 433 /** 434 * {@inheritDoc} 435 */ reorderCombiningMarks( CharSequence cs, int[][] gpa, String script, String language, List associations)436 public CharSequence reorderCombiningMarks( 437 CharSequence cs, int[][] gpa, String script, String language, List associations) { 438 if (!isMetricsLoaded) { 439 load(true); 440 } 441 if (realFontDescriptor instanceof Substitutable) { 442 return ((Substitutable)realFontDescriptor) 443 .reorderCombiningMarks(cs, gpa, script, language, associations); 444 } else { 445 return cs; 446 } 447 } 448 449 /** 450 * {@inheritDoc} 451 */ performsPositioning()452 public boolean performsPositioning() { 453 if (!isMetricsLoaded) { 454 load(true); 455 } 456 if (realFontDescriptor instanceof Positionable) { 457 return ((Positionable)realFontDescriptor).performsPositioning(); 458 } else { 459 return false; 460 } 461 } 462 463 /** 464 * {@inheritDoc} 465 */ 466 public int[][] performPositioning(CharSequence cs, String script, String language, int fontSize)467 performPositioning(CharSequence cs, String script, String language, int fontSize) { 468 if (!isMetricsLoaded) { 469 load(true); 470 } 471 if (realFontDescriptor instanceof Positionable) { 472 return ((Positionable)realFontDescriptor) 473 .performPositioning(cs, script, language, fontSize); 474 } else { 475 return null; 476 } 477 } 478 479 /** 480 * {@inheritDoc} 481 */ 482 public int[][] performPositioning(CharSequence cs, String script, String language)483 performPositioning(CharSequence cs, String script, String language) { 484 if (!isMetricsLoaded) { 485 load(true); 486 } 487 if (realFontDescriptor instanceof Positionable) { 488 return ((Positionable)realFontDescriptor) 489 .performPositioning(cs, script, language); 490 } else { 491 return null; 492 } 493 } 494 495 /** 496 * {@inheritDoc} 497 */ isSubsetEmbedded()498 public boolean isSubsetEmbedded() { 499 load(true); 500 if (realFont.isMultiByte() && this.embeddingMode == EmbeddingMode.FULL) { 501 return false; 502 } 503 return realFont.isMultiByte(); 504 } 505 } 506 507