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: SingleByteFont.java 1805173 2017-08-16 10:50:04Z ssteiner $ */
19 
20 package org.apache.fop.fonts;
21 
22 import java.awt.Rectangle;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.LinkedHashMap;
26 import java.util.Map;
27 import java.util.Set;
28 import java.util.TreeSet;
29 
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32 
33 import org.apache.xmlgraphics.fonts.Glyphs;
34 
35 import org.apache.fop.apps.io.InternalResourceResolver;
36 import org.apache.fop.fonts.truetype.OpenFont.PostScriptVersion;
37 import org.apache.fop.util.CharUtilities;
38 
39 /**
40  * Generic SingleByte font
41  */
42 public class SingleByteFont extends CustomFont {
43 
44     /** logger */
45     private  static Log log = LogFactory.getLog(SingleByteFont.class);
46 
47     protected SingleByteEncoding mapping;
48     private boolean useNativeEncoding;
49 
50     protected int[] width;
51 
52     private Rectangle[] boundingBoxes;
53 
54     private Map<Character, Character> alternativeCodes;
55 
56     private PostScriptVersion ttPostScriptVersion;
57 
58     private int usedGlyphsCount;
59     private LinkedHashMap<Integer, String> usedGlyphNames;
60     private Map<Integer, Integer> usedGlyphs;
61     private Map<Integer, Character> usedCharsIndex;
62     private Map<Character, Integer> charGIDMappings;
63 
SingleByteFont(InternalResourceResolver resourceResolver)64     public SingleByteFont(InternalResourceResolver resourceResolver) {
65         super(resourceResolver);
66         setEncoding(CodePointMapping.WIN_ANSI_ENCODING);
67     }
68 
SingleByteFont(InternalResourceResolver resourceResolver, EmbeddingMode embeddingMode)69     public SingleByteFont(InternalResourceResolver resourceResolver, EmbeddingMode embeddingMode) {
70         this(resourceResolver);
71         setEmbeddingMode(embeddingMode);
72         if (embeddingMode != EmbeddingMode.FULL) {
73             usedGlyphNames = new LinkedHashMap<Integer, String>();
74             usedGlyphs = new HashMap<Integer, Integer>();
75             usedCharsIndex = new HashMap<Integer, Character>();
76             charGIDMappings = new HashMap<Character, Integer>();
77 
78             // The zeroth value is reserved for .notdef
79             usedGlyphs.put(0, 0);
80             usedGlyphsCount++;
81         }
82     }
83 
84     /** {@inheritDoc} */
isEmbeddable()85     public boolean isEmbeddable() {
86         return (!(getEmbedFileURI() == null
87                 && getEmbedResourceName() == null));
88     }
89 
90     /** {@inheritDoc} */
isSubsetEmbedded()91     public boolean isSubsetEmbedded() {
92         return getEmbeddingMode() != EmbeddingMode.FULL;
93     }
94 
95     /** {@inheritDoc} */
getEncodingName()96     public String getEncodingName() {
97         return this.mapping.getName();
98     }
99 
100     /**
101      * Returns the code point mapping (encoding) of this font.
102      * @return the code point mapping
103      */
getEncoding()104     public SingleByteEncoding getEncoding() {
105         return this.mapping;
106     }
107 
108     /** {@inheritDoc} */
getWidth(int i, int size)109     public int getWidth(int i, int size) {
110         if (i < 256) {
111             int idx = i - getFirstChar();
112             if (idx >= 0 && idx < width.length) {
113                 return size * width[idx];
114             }
115         } else if (this.additionalEncodings != null) {
116             int encodingIndex = (i / 256) - 1;
117             SimpleSingleByteEncoding encoding = getAdditionalEncoding(encodingIndex);
118             int codePoint = i % 256;
119             NamedCharacter nc = encoding.getCharacterForIndex(codePoint);
120             UnencodedCharacter uc
121                 = this.unencodedCharacters.get(nc.getSingleUnicodeValue());
122             return size * uc.getWidth();
123         }
124         return 0;
125     }
126 
127     /** {@inheritDoc} */
getWidths()128     public int[] getWidths() {
129         int[] arr = new int[width.length];
130         System.arraycopy(width, 0, arr, 0, width.length);
131         return arr;
132     }
133 
getBoundingBox(int glyphIndex, int size)134     public Rectangle getBoundingBox(int glyphIndex, int size) {
135         Rectangle bbox = null;
136         if (glyphIndex < 256) {
137             int idx = glyphIndex - getFirstChar();
138             if (idx >= 0 && idx < boundingBoxes.length) {
139                 bbox =  boundingBoxes[idx];
140             }
141         } else if (this.additionalEncodings != null) {
142             int encodingIndex = (glyphIndex / 256) - 1;
143             SimpleSingleByteEncoding encoding = getAdditionalEncoding(encodingIndex);
144             int codePoint = glyphIndex % 256;
145             NamedCharacter nc = encoding.getCharacterForIndex(codePoint);
146             UnencodedCharacter uc = this.unencodedCharacters.get(nc.getSingleUnicodeValue());
147             bbox = uc.getBBox();
148         }
149         return bbox == null ? null : new Rectangle(bbox.x * size, bbox.y * size, bbox.width * size, bbox.height * size);
150     }
151 
152     /**
153      * Lookup a character using its alternative names. If found, cache it so we
154      * can speed up lookups.
155      * @param c the character
156      * @return the suggested alternative character present in the font
157      */
findAlternative(char c)158     private char findAlternative(char c) {
159         char d;
160         if (alternativeCodes == null) {
161             alternativeCodes = new java.util.HashMap<Character, Character>();
162         } else {
163             Character alternative = alternativeCodes.get(c);
164             if (alternative != null) {
165                 return alternative;
166             }
167         }
168         String charName = Glyphs.charToGlyphName(c);
169         String[] charNameAlternatives = Glyphs.getCharNameAlternativesFor(charName);
170         if (charNameAlternatives != null && charNameAlternatives.length > 0) {
171             for (String charNameAlternative : charNameAlternatives) {
172                 if (log.isDebugEnabled()) {
173                     log.debug("Checking alternative for char " + c + " (charname="
174                             + charName + "): " + charNameAlternative);
175                 }
176                 String s = Glyphs.getUnicodeSequenceForGlyphName(charNameAlternative);
177                 if (s != null) {
178                     d = lookupChar(s.charAt(0));
179                     if (d != SingleByteEncoding.NOT_FOUND_CODE_POINT) {
180                         alternativeCodes.put(c, d);
181                         return d;
182                     }
183                 }
184             }
185         }
186 
187         return SingleByteEncoding.NOT_FOUND_CODE_POINT;
188     }
189 
lookupChar(char c)190     private char lookupChar(char c) {
191         char d = mapping.mapChar(c);
192         if (d != SingleByteEncoding.NOT_FOUND_CODE_POINT) {
193             return d;
194         }
195 
196         // Check unencoded characters which are available in the font by
197         // character name
198         d = mapUnencodedChar(c);
199         return d;
200     }
201 
isSubset()202     private boolean isSubset() {
203         return getEmbeddingMode() == EmbeddingMode.SUBSET;
204     }
205 
206     /** {@inheritDoc} */
207     @Override
mapChar(char c)208     public char mapChar(char c) {
209         notifyMapOperation();
210         char d = lookupChar(c);
211         if (d == SingleByteEncoding.NOT_FOUND_CODE_POINT) {
212             // Check for alternative
213             d = findAlternative(c);
214             if (d != SingleByteEncoding.NOT_FOUND_CODE_POINT) {
215                 return d;
216             } else {
217                 this.warnMissingGlyph(c);
218                 return Typeface.NOT_FOUND;
219             }
220         }
221         if (isEmbeddable() && isSubset()) {
222             mapChar(d, c);
223         }
224         return d;
225     }
226 
mapChar(int glyphIndex, char unicode)227     private int mapChar(int glyphIndex, char unicode) {
228         // Reencode to a new subset font or get the reencoded value
229         // IOW, accumulate the accessed characters and build a character map for them
230         Integer subsetCharSelector = usedGlyphs.get(glyphIndex);
231         if (subsetCharSelector == null) {
232             int selector = usedGlyphsCount;
233             usedGlyphs.put(glyphIndex, selector);
234             usedCharsIndex.put(selector, unicode);
235             charGIDMappings.put(unicode, glyphIndex);
236             usedGlyphsCount++;
237             return selector;
238         } else {
239             return subsetCharSelector;
240         }
241     }
242 
getUnicode(int index)243     private char getUnicode(int index) {
244         Character mapValue = usedCharsIndex.get(index);
245         if (mapValue != null) {
246             return mapValue;
247         } else {
248             return CharUtilities.NOT_A_CHARACTER;
249         }
250     }
251 
252     /** {@inheritDoc} */
253     @Override
hasChar(char c)254     public boolean hasChar(char c) {
255         char d = mapping.mapChar(c);
256         if (d != SingleByteEncoding.NOT_FOUND_CODE_POINT) {
257             return true;
258         }
259         //Check unencoded characters which are available in the font by character name
260         d = mapUnencodedChar(c);
261         if (d != SingleByteEncoding.NOT_FOUND_CODE_POINT) {
262             return true;
263         }
264         // Check if an alternative exists
265         d = findAlternative(c);
266         if (d != SingleByteEncoding.NOT_FOUND_CODE_POINT) {
267             return true;
268         }
269         return false;
270     }
271 
272     /* ---- single byte font specific setters --- */
273 
274     /**
275      * Updates the mapping variable based on the encoding.
276      * @param encoding the name of the encoding
277      */
updateMapping(String encoding)278     protected void updateMapping(String encoding) {
279         try {
280             this.mapping = CodePointMapping.getMapping(encoding);
281         } catch (UnsupportedOperationException e) {
282             log.error("Font '" + super.getFontName() + "': " + e.getMessage());
283         }
284     }
285 
286     /**
287      * Sets the encoding of the font.
288      * @param encoding the encoding (ex. "WinAnsiEncoding" or "SymbolEncoding")
289      */
setEncoding(String encoding)290     public void setEncoding(String encoding) {
291         updateMapping(encoding);
292     }
293 
294     /**
295      * Sets the encoding of the font.
296      * @param encoding the encoding information
297      */
setEncoding(CodePointMapping encoding)298     public void setEncoding(CodePointMapping encoding) {
299         this.mapping = encoding;
300     }
301 
302     /**
303      * Controls whether the font is configured to use its native encoding or if it
304      * may need to be re-encoded for the target format.
305      * @param value true indicates that the configured encoding is the font's native encoding
306      */
setUseNativeEncoding(boolean value)307     public void setUseNativeEncoding(boolean value) {
308         this.useNativeEncoding = value;
309     }
310 
311     /**
312      * Indicates whether this font is configured to use its native encoding. This
313      * method is used to determine whether the font needs to be re-encoded.
314      * @return true if the font uses its native encoding.
315      */
isUsingNativeEncoding()316     public boolean isUsingNativeEncoding() {
317         return this.useNativeEncoding;
318     }
319 
320     /**
321      * Sets a width for a character.
322      * @param index index of the character
323      * @param w the width of the character
324      */
setWidth(int index, int w)325     public void setWidth(int index, int w) {
326         if (this.width == null) {
327             this.width = new int[getLastChar() - getFirstChar() + 1];
328         }
329         this.width[index - getFirstChar()] = w;
330     }
331 
setBoundingBox(int index, Rectangle bbox)332     public void setBoundingBox(int index, Rectangle bbox) {
333         if (this.boundingBoxes == null) {
334             this.boundingBoxes = new Rectangle[getLastChar() - getFirstChar() + 1];
335         }
336         this.boundingBoxes[index - getFirstChar()] = bbox;
337     }
338 
339     /**
340      * Adds an unencoded character (one that is not supported by the primary encoding).
341      * @param ch the named character
342      * @param width the width of the character
343      */
addUnencodedCharacter(NamedCharacter ch, int width, Rectangle bbox)344     public void addUnencodedCharacter(NamedCharacter ch, int width, Rectangle bbox) {
345         if (this.unencodedCharacters == null) {
346             this.unencodedCharacters = new HashMap<Character, UnencodedCharacter>();
347         }
348         if (ch.hasSingleUnicodeValue()) {
349             UnencodedCharacter uc = new UnencodedCharacter(ch, width, bbox);
350             this.unencodedCharacters.put(ch.getSingleUnicodeValue(), uc);
351         } else {
352             //Cannot deal with unicode sequences, so ignore this character
353         }
354     }
355 
356     /**
357      * Makes all unencoded characters available through additional encodings. This method
358      * is used in cases where the fonts need to be encoded in the target format before
359      * all text of the document is processed (for example in PostScript when resource optimization
360      * is disabled).
361      */
encodeAllUnencodedCharacters()362     public void encodeAllUnencodedCharacters() {
363         if (this.unencodedCharacters != null) {
364             Set<Character> sortedKeys = new TreeSet<Character>(this.unencodedCharacters.keySet());
365             for (Character ch : sortedKeys) {
366                 char mapped = mapChar(ch);
367                 assert mapped != Typeface.NOT_FOUND;
368             }
369         }
370     }
371 
372     /**
373      * Returns an array with the widths for an additional encoding.
374      * @param index the index of the additional encoding
375      * @return the width array
376      */
getAdditionalWidths(int index)377     public int[] getAdditionalWidths(int index) {
378         SimpleSingleByteEncoding enc = getAdditionalEncoding(index);
379         int[] arr = new int[enc.getLastChar() - enc.getFirstChar() + 1];
380         for (int i = 0, c = arr.length; i < c; i++) {
381             NamedCharacter nc = enc.getCharacterForIndex(enc.getFirstChar() + i);
382             UnencodedCharacter uc = this.unencodedCharacters.get(
383                     nc.getSingleUnicodeValue());
384             arr[i] = uc.getWidth();
385         }
386         return arr;
387     }
388 
389     protected static final class UnencodedCharacter {
390 
391         private final NamedCharacter character;
392         private final int width;
393         private final Rectangle bbox;
394 
UnencodedCharacter(NamedCharacter character, int width, Rectangle bbox)395         public UnencodedCharacter(NamedCharacter character, int width, Rectangle bbox) {
396             this.character = character;
397             this.width = width;
398             this.bbox = bbox;
399         }
400 
getCharacter()401         public NamedCharacter getCharacter() {
402             return this.character;
403         }
404 
getWidth()405         public int getWidth() {
406             return this.width;
407         }
408 
getBBox()409         public Rectangle getBBox() {
410             return bbox;
411         }
412 
413         /** {@inheritDoc} */
414         @Override
toString()415         public String toString() {
416             return getCharacter().toString();
417         }
418     }
419 
420     /**
421      * Sets the version of the PostScript table stored in the TrueType font represented by
422      * this instance.
423      *
424      * @param version version of the post table
425      */
setTrueTypePostScriptVersion(PostScriptVersion version)426     public void setTrueTypePostScriptVersion(PostScriptVersion version) {
427         ttPostScriptVersion = version;
428     }
429 
430     /**
431      * Returns the version of the PostScript table stored in the TrueType font represented by
432      * this instance.
433      *
434      * @return the version of the post table
435      */
getTrueTypePostScriptVersion()436     public PostScriptVersion getTrueTypePostScriptVersion() {
437         assert getFontType() == FontType.TRUETYPE;
438         return ttPostScriptVersion;
439     }
440 
441     /**
442      * Returns a Map of used Glyphs.
443      * @return Map Map of used Glyphs
444      */
getUsedGlyphs()445     public Map<Integer, Integer> getUsedGlyphs() {
446         return Collections.unmodifiableMap(usedGlyphs);
447     }
448 
getUnicodeFromSelector(int selector)449     public char getUnicodeFromSelector(int selector) {
450         return getUnicode(selector);
451     }
452 
getGIDFromChar(char ch)453     public int getGIDFromChar(char ch) {
454         return charGIDMappings.get(ch);
455     }
456 
getUnicodeFromGID(int glyphIndex)457     public char getUnicodeFromGID(int glyphIndex) {
458         int selector = usedGlyphs.get(glyphIndex);
459         return usedCharsIndex.get(selector);
460     }
461 
mapUsedGlyphName(int gid, String value)462     public void mapUsedGlyphName(int gid, String value) {
463         usedGlyphNames.put(gid, value);
464     }
465 
getUsedGlyphNames()466     public Map<Integer, String> getUsedGlyphNames() {
467         return usedGlyphNames;
468     }
469 
getGlyphName(int idx)470     public String getGlyphName(int idx) {
471         if (idx < mapping.getCharNameMap().length) {
472             return mapping.getCharNameMap()[idx];
473         } else {
474             int selector = usedGlyphs.get(idx);
475             char theChar = usedCharsIndex.get(selector);
476             return unencodedCharacters.get(theChar).getCharacter().getName();
477         }
478     }
479 }
480 
481