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