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