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: CharacterSetBuilder.java 1761020 2016-09-16 11:17:35Z ssteiner $ */
19 
20 package org.apache.fop.afp.fonts;
21 
22 import java.awt.Rectangle;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.net.MalformedURLException;
26 import java.net.URI;
27 import java.net.URISyntaxException;
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.WeakHashMap;
34 
35 import org.apache.commons.logging.Log;
36 import org.apache.commons.logging.LogFactory;
37 
38 import org.apache.xmlgraphics.image.loader.util.SoftMapCache;
39 
40 import org.apache.fop.afp.AFPConstants;
41 import org.apache.fop.afp.AFPEventProducer;
42 import org.apache.fop.afp.util.AFPResourceAccessor;
43 import org.apache.fop.afp.util.StructuredFieldReader;
44 import org.apache.fop.apps.io.InternalResourceResolver;
45 import org.apache.fop.fonts.Typeface;
46 
47 /**
48  * The CharacterSetBuilder is responsible building the a CharacterSet instance that holds
49  * the font metric data.  The data is either read from disk and passed to a CharacterSet (*)
50  * or a FopCharacterSet is instantiated that is composed of a Typeface instance configured
51  * with this data.<br>
52  * -*- For referenced fonts CharacterSetBuilder is responsible for reading the font attributes
53  * from binary code page files and the character set metric files. In IBM font structure, a
54  * code page maps each character of text to the characters in a character set.
55  * Each character is translated into a code point. When the character is
56  * printed, each code point is matched to a character ID on the code page
57  * specified. The character ID is then matched to the image (raster pattern or
58  * outline pattern) of the character in the character set specified. The image
59  * in the character set is the image that is printed in the document. To be a
60  * valid code page for a particular character set, all character IDs in the code
61  * page must be included in that character set.<br>
62  * This class will read the font information from the binary code page files and character
63  * set metric files in order to determine the correct metrics to use when rendering the
64  * formatted object.
65  */
66 public abstract class CharacterSetBuilder {
67 
68     /**
69      * Static logging instance
70      */
71     protected static final Log LOG = LogFactory.getLog(CharacterSetBuilder.class);
72 
73     /**
74      * Template used to convert lists to arrays.
75      */
76     private static final CharacterSetOrientation[] EMPTY_CSO_ARRAY = new CharacterSetOrientation[0];
77 
78     /** Codepage MO:DCA structured field. */
79     private static final byte[] CODEPAGE_SF = new byte[] {
80         (byte) 0xD3, (byte) 0xA8, (byte) 0x87};
81 
82     /** Character table MO:DCA structured field. */
83     private static final byte[] CHARACTER_TABLE_SF = new byte[] {
84         (byte) 0xD3, (byte) 0x8C, (byte) 0x87};
85 
86     /** Font descriptor MO:DCA structured field. */
87     private static final byte[] FONT_DESCRIPTOR_SF = new byte[] {
88         (byte) 0xD3, (byte) 0xA6, (byte) 0x89 };
89 
90     /** Font control MO:DCA structured field. */
91     private static final byte[] FONT_CONTROL_SF = new byte[] {
92         (byte) 0xD3, (byte) 0xA7, (byte) 0x89 };
93 
94     /** Font orientation MO:DCA structured field. */
95     private static final byte[] FONT_ORIENTATION_SF = new byte[] {
96         (byte) 0xD3, (byte) 0xAE, (byte) 0x89 };
97 
98     /** Font position MO:DCA structured field. */
99     private static final byte[] FONT_POSITION_SF = new byte[] {
100         (byte) 0xD3, (byte) 0xAC, (byte) 0x89 };
101 
102     /** Font index MO:DCA structured field. */
103     private static final byte[] FONT_INDEX_SF = new byte[] {
104         (byte) 0xD3, (byte) 0x8C, (byte) 0x89 };
105 
106     /**
107      * The collection of code pages
108      */
109     private final Map<String, Map<String, String>> codePagesCache
110             = Collections.synchronizedMap(new WeakHashMap<String, Map<String, String>>());
111 
112     /**
113      * Cache of charactersets
114      */
115     private final SoftMapCache characterSetsCache = new SoftMapCache(true);
116 
117     /** Default constructor. */
CharacterSetBuilder()118     private CharacterSetBuilder() {
119     }
120 
121     /**
122      * Factory method for the single-byte implementation of AFPFontReader.
123      * @return AFPFontReader
124      */
getSingleByteInstance()125     public static CharacterSetBuilder getSingleByteInstance() {
126         return SingleByteLoader.getInstance();
127     }
128 
129     /**
130      * Factory method for the double-byte (CID Keyed font (Type 0)) implementation of AFPFontReader.
131      * @return AFPFontReader
132      */
getDoubleByteInstance()133     public static CharacterSetBuilder getDoubleByteInstance() {
134         return DoubleByteLoader.getInstance();
135     }
136 
137 
138     /**
139      * Returns an InputStream to a given file path and filename
140      *
141      * @param accessor the resource accessor
142      * @param uriStr the URI
143      * @param eventProducer for handling AFP related events
144      * @return an inputStream
145      * @throws IOException in the event that an I/O exception of some sort has occurred
146      */
openInputStream(AFPResourceAccessor accessor, String uriStr, AFPEventProducer eventProducer)147     private InputStream openInputStream(AFPResourceAccessor accessor, String uriStr,
148             AFPEventProducer eventProducer)
149             throws IOException {
150         URI uri;
151         try {
152             uri = InternalResourceResolver.cleanURI(uriStr.trim());
153         } catch (URISyntaxException e) {
154             throw new MalformedURLException("Invalid uri: " + uriStr + " (" + e.getMessage() + ")");
155         }
156         if (LOG.isDebugEnabled()) {
157             LOG.debug("Opening " + uri);
158         }
159         return accessor.createInputStream(uri);
160     }
161 
162     /**
163      * Closes the inputstream
164      *
165      * @param inputStream the inputstream to close
166      */
closeInputStream(InputStream inputStream)167     private void closeInputStream(InputStream inputStream) {
168         try {
169             if (inputStream != null) {
170                 inputStream.close();
171             }
172         } catch (Exception ex) {
173             // Lets log at least!
174             LOG.error(ex.getMessage());
175         }
176     }
177 
178     /**
179      * Load the font details and metrics into the CharacterSetMetric object, this will use the
180      * actual afp code page and character set files to load the object with the necessary metrics.
181      *
182      * @param characterSetName name of the characterset
183      * @param codePageName name of the code page file
184      * @param encoding encoding name
185      * @param accessor used to load codepage and characterset
186      * @param eventProducer for handling AFP related events
187      * @return CharacterSet object
188      * @throws IOException if an I/O error occurs
189      */
buildSBCS(String characterSetName, String codePageName, String encoding, AFPResourceAccessor accessor, AFPEventProducer eventProducer)190     public CharacterSet buildSBCS(String characterSetName, String codePageName, String encoding,
191             AFPResourceAccessor accessor, AFPEventProducer eventProducer) throws IOException {
192         return processFont(characterSetName, codePageName, encoding, CharacterSetType.SINGLE_BYTE,
193                 accessor, eventProducer);
194     }
195 
196     /**
197      * Load the font details and metrics into the CharacterSetMetric object, this will use the
198      * actual afp code page and character set files to load the object with the necessary metrics.
199      * This method is to be used for double byte character sets (DBCS).
200      *
201      * @param characterSetName name of the characterset
202      * @param codePageName name of the code page file
203      * @param encoding encoding name
204      * @param charsetType the characterset type
205      * @param accessor used to load codepage and characterset
206      * @param eventProducer for handling AFP related events
207      * @return CharacterSet object
208      * @throws IOException if an I/O error occurs
209      */
buildDBCS(String characterSetName, String codePageName, String encoding, CharacterSetType charsetType, AFPResourceAccessor accessor, AFPEventProducer eventProducer)210     public CharacterSet buildDBCS(String characterSetName, String codePageName, String encoding,
211             CharacterSetType charsetType, AFPResourceAccessor accessor, AFPEventProducer eventProducer)
212             throws IOException {
213         return processFont(characterSetName, codePageName, encoding, charsetType, accessor,
214                 eventProducer);
215     }
216 
217     /**
218      * Load the font details and metrics into the CharacterSetMetric object, this will use the
219      * actual afp code page and character set files to load the object with the necessary metrics.
220      *
221      * @param characterSetName the CharacterSetMetric object to populate
222      * @param codePageName the name of the code page to use
223      * @param encoding name of the encoding in use
224      * @param typeface base14 font name
225      * @param eventProducer for handling AFP related events
226      * @return CharacterSet object
227      * @throws IOException if an I/O error occurs
228      */
build(String characterSetName, String codePageName, String encoding, Typeface typeface, AFPEventProducer eventProducer)229     public CharacterSet build(String characterSetName, String codePageName, String encoding,
230             Typeface typeface, AFPEventProducer eventProducer) throws IOException {
231         return new FopCharacterSet(codePageName, encoding, characterSetName, typeface,
232                 eventProducer);
233     }
234 
build(String characterSetName, String codePageName, String encoding, Typeface typeface, AFPResourceAccessor accessor, AFPEventProducer eventProducer)235     public CharacterSet build(String characterSetName, String codePageName, String encoding,
236                               Typeface typeface, AFPResourceAccessor accessor, AFPEventProducer eventProducer)
237         throws IOException {
238         return new FopCharacterSet(codePageName, encoding, characterSetName, typeface, accessor, eventProducer);
239     }
240 
processFont(String characterSetName, String codePageName, String encoding, CharacterSetType charsetType, AFPResourceAccessor accessor, AFPEventProducer eventProducer)241     private CharacterSet processFont(String characterSetName, String codePageName, String encoding,
242             CharacterSetType charsetType, AFPResourceAccessor accessor, AFPEventProducer eventProducer)
243             throws IOException {
244         // check for cached version of the characterset
245         URI charSetURI = accessor.resolveURI(characterSetName);
246         String cacheKey = charSetURI.toASCIIString() + "_" + characterSetName + "_" + codePageName;
247         CharacterSet characterSet = (CharacterSet) characterSetsCache.get(cacheKey);
248         if (characterSet != null) {
249             return characterSet;
250         }
251 
252         // characterset not in the cache, so recreating
253         characterSet = new CharacterSet(codePageName, encoding, charsetType, characterSetName,
254                 accessor, eventProducer);
255 
256         InputStream inputStream = null;
257 
258         try {
259 
260             /**
261              * Get the code page which contains the character mapping
262              * information to map the unicode character id to the graphic
263              * chracter global identifier.
264              */
265             Map<String, String> codePage;
266             // TODO: This could have performance implications if several threads want to use the
267             // codePagesCache to retrieve different codepages.
268             synchronized (codePagesCache) {
269                 codePage = codePagesCache.get(codePageName);
270 
271                 if (codePage == null) {
272                     codePage = loadCodePage(codePageName, encoding, accessor, eventProducer);
273                     codePagesCache.put(codePageName, codePage);
274                 }
275             }
276 
277             inputStream = openInputStream(accessor, characterSetName, eventProducer);
278 
279             StructuredFieldReader structuredFieldReader = new StructuredFieldReader(inputStream);
280 
281             // Process D3A689 Font Descriptor
282             FontDescriptor fontDescriptor = processFontDescriptor(structuredFieldReader);
283             characterSet.setNominalVerticalSize(fontDescriptor.getNominalFontSizeInMillipoints());
284 
285             // Process D3A789 Font Control
286             FontControl fontControl = processFontControl(structuredFieldReader);
287 
288             if (fontControl != null) {
289                 //process D3AE89 Font Orientation
290                 CharacterSetOrientation[] characterSetOrientations
291                     = processFontOrientation(structuredFieldReader);
292 
293                 double metricNormalizationFactor;
294                 if (fontControl.isRelative()) {
295                     metricNormalizationFactor = 1;
296                 } else {
297                     int dpi = fontControl.getDpi();
298                     metricNormalizationFactor = 1000.0d * 72000.0d
299                         / fontDescriptor.getNominalFontSizeInMillipoints() / dpi;
300                 }
301                 ValueNormalizer normalizer = new ValueNormalizer(metricNormalizationFactor);
302                 //process D3AC89 Font Position
303                 processFontPosition(structuredFieldReader, characterSetOrientations, normalizer);
304                 //process D38C89 Font Index (per orientation)
305                 for (CharacterSetOrientation characterSetOrientation : characterSetOrientations) {
306                     processFontIndex(structuredFieldReader, characterSetOrientation, codePage, normalizer);
307                     characterSet.addCharacterSetOrientation(characterSetOrientation);
308                 }
309             } else {
310                 throw new IOException("Missing D3AE89 Font Control structured field.");
311             }
312 
313         } finally {
314             closeInputStream(inputStream);
315         }
316         characterSetsCache.put(cacheKey, characterSet);
317         return characterSet;
318     }
319 
320     private static class ValueNormalizer {
321 
322         private final double factor;
323 
ValueNormalizer(double factor)324         public ValueNormalizer(double factor) {
325             this.factor = factor;
326         }
327 
normalize(int value)328         public int normalize(int value) {
329             return (int) Math.round(value *  factor);
330         }
331     }
332 
333     /**
334      * Load the code page information from the appropriate file. The file name
335      * to load is determined by the code page name and the file extension 'CDP'.
336      *
337      * @param codePage
338      *            the code page identifier
339      * @param encoding
340      *            the encoding to use for the character decoding
341      * @param accessor the resource accessor
342      * @param eventProducer for handling AFP related events
343      * @return a code page mapping (key: GCGID, value: Unicode character)
344      * @throws IOException if an I/O exception of some sort has occurred.
345      */
loadCodePage(String codePage, String encoding, AFPResourceAccessor accessor, AFPEventProducer eventProducer)346     protected Map<String, String> loadCodePage(String codePage, String encoding,
347             AFPResourceAccessor accessor, AFPEventProducer eventProducer) throws IOException {
348 
349         // Create the HashMap to store code page information
350         Map<String, String> codePages = new HashMap<String, String>();
351 
352         InputStream inputStream = null;
353         try {
354             inputStream = openInputStream(accessor, codePage.trim(), eventProducer);
355         } catch (IOException e) {
356             eventProducer.codePageNotFound(this, e);
357             throw e;
358         }
359         try {
360             StructuredFieldReader structuredFieldReader = new StructuredFieldReader(inputStream);
361             byte[] data = structuredFieldReader.getNext(CHARACTER_TABLE_SF);
362 
363             int position = 0;
364             byte[] gcgiBytes = new byte[8];
365             byte[] charBytes = new byte[1];
366 
367             // Read data, ignoring bytes 0 - 2
368             for (int index = 3; index < data.length; index++) {
369                 if (position < 8) {
370                     // Build the graphic character global identifier key
371                     gcgiBytes[position] = data[index];
372                     position++;
373                 } else if (position == 9) {
374                     position = 0;
375                     // Set the character
376                     charBytes[0] = data[index];
377                     String gcgiString = new String(gcgiBytes,
378                             AFPConstants.EBCIDIC_ENCODING);
379                     //Use the 8-bit char index to find the Unicode character using the Java encoding
380                     //given in the configuration. If the code page and the Java encoding don't
381                     //match, a wrong Unicode character will be associated with the AFP GCGID.
382                     //Idea: we could use IBM's GCGID to Unicode map and build code pages ourselves.
383                     String charString = new String(charBytes, encoding);
384                     codePages.put(gcgiString, charString);
385                 } else {
386                     position++;
387                 }
388             }
389         } finally {
390             closeInputStream(inputStream);
391         }
392 
393         return codePages;
394     }
395 
396     /**
397      * Process the font descriptor details using the structured field reader.
398      *
399      * @param structuredFieldReader the structured field reader
400      * @return a class representing the font descriptor
401      * @throws IOException if an I/O exception of some sort has occurred.
402      */
processFontDescriptor( StructuredFieldReader structuredFieldReader)403     private static FontDescriptor processFontDescriptor(
404             StructuredFieldReader structuredFieldReader) throws IOException {
405 
406         byte[] fndData = structuredFieldReader.getNext(FONT_DESCRIPTOR_SF);
407         return new FontDescriptor(fndData);
408     }
409 
410     /**
411      * Process the font control details using the structured field reader.
412      *
413      * @param structuredFieldReader
414      *            the structured field reader
415      * @return the FontControl
416      * @throws IOException if an I/O exception of some sort has occurred.
417      */
processFontControl(StructuredFieldReader structuredFieldReader)418     private FontControl processFontControl(StructuredFieldReader structuredFieldReader)
419             throws IOException {
420 
421         byte[] fncData = structuredFieldReader.getNext(FONT_CONTROL_SF);
422 
423         FontControl fontControl = null;
424         if (fncData != null) {
425             fontControl = new FontControl();
426 
427             if (fncData[7] == (byte) 0x02) {
428                 fontControl.setRelative(true);
429             }
430             int metricResolution = getUBIN(fncData, 9);
431             if (metricResolution == 1000) {
432                 //Special case: 1000 units per em (rather than dpi)
433                 fontControl.setUnitsPerEm(1000);
434             } else {
435                 fontControl.setDpi(metricResolution / 10);
436             }
437         }
438         return fontControl;
439     }
440 
441     /**
442      * Process the font orientation details from using the structured field
443      * reader.
444      *
445      * @param structuredFieldReader
446      *            the structured field reader
447      * @return CharacterSetOrientation array
448      * @throws IOException if an I/O exception of some sort has occurred.
449      */
processFontOrientation( StructuredFieldReader structuredFieldReader)450     private CharacterSetOrientation[] processFontOrientation(
451         StructuredFieldReader structuredFieldReader) throws IOException {
452 
453         byte[] data = structuredFieldReader.getNext(FONT_ORIENTATION_SF);
454 
455         int position = 0;
456         byte[] fnoData = new byte[26];
457 
458         List<CharacterSetOrientation> orientations = new ArrayList<CharacterSetOrientation>();
459 
460         // Read data, ignoring bytes 0 - 2
461         for (int index = 3; index < data.length; index++) {
462             // Build the font orientation record
463             fnoData[position] = data[index];
464             position++;
465 
466             if (position == 26) {
467                 position = 0;
468 
469                 int orientation = determineOrientation(fnoData[2]);
470                 int spaceIncrement = getUBIN(fnoData, 8);
471                 int emIncrement = getUBIN(fnoData, 14);
472                 int nominalCharacterIncrement = getUBIN(fnoData, 20);
473 
474                 orientations.add(new CharacterSetOrientation(orientation, spaceIncrement,
475                         emIncrement, nominalCharacterIncrement));
476             }
477         }
478         return orientations.toArray(EMPTY_CSO_ARRAY);
479     }
480 
481     /**
482      * Populate the CharacterSetOrientation object in the suplied array with the
483      * font position details using the supplied structured field reader.
484      *
485      * @param structuredFieldReader
486      *            the structured field reader
487      * @param characterSetOrientations
488      *            the array of CharacterSetOrientation objects
489      * @param metricNormalizationFactor factor to apply to the metrics to get normalized
490      *                  font metric values
491      * @throws IOException if an I/O exception of some sort has occurred.
492      */
processFontPosition(StructuredFieldReader structuredFieldReader, CharacterSetOrientation[] characterSetOrientations, ValueNormalizer normalizer)493     private void processFontPosition(StructuredFieldReader structuredFieldReader,
494         CharacterSetOrientation[] characterSetOrientations, ValueNormalizer normalizer)
495             throws IOException {
496 
497         byte[] data = structuredFieldReader.getNext(FONT_POSITION_SF);
498 
499         int position = 0;
500         byte[] fpData = new byte[26];
501 
502         int characterSetOrientationIndex = 0;
503 
504         // Read data, ignoring bytes 0 - 2
505         for (int index = 3; index < data.length; index++) {
506             if (position < 22) {
507                 // Build the font orientation record
508                 fpData[position] = data[index];
509                 if (position == 9) {
510                     CharacterSetOrientation characterSetOrientation
511                             = characterSetOrientations[characterSetOrientationIndex];
512                     int xHeight = getSBIN(fpData, 2);
513                     int capHeight = getSBIN(fpData, 4);
514                     int ascHeight = getSBIN(fpData, 6);
515                     int dscHeight = getSBIN(fpData, 8);
516                     dscHeight = dscHeight * -1;
517                     int underscoreWidth = getUBIN(fpData, 17);
518                     int underscorePosition = getSBIN(fpData, 20);
519                     characterSetOrientation.setXHeight(normalizer.normalize(xHeight));
520                     characterSetOrientation.setCapHeight(normalizer.normalize(capHeight));
521                     characterSetOrientation.setAscender(normalizer.normalize(ascHeight));
522                     characterSetOrientation.setDescender(normalizer.normalize(dscHeight));
523                     characterSetOrientation.setUnderscoreWidth(normalizer.normalize(underscoreWidth));
524                     characterSetOrientation.setUnderscorePosition(normalizer.normalize(underscorePosition));
525                 }
526             } else if (position == 22) {
527                 position = 0;
528                 characterSetOrientationIndex++;
529                 fpData[position] = data[index];
530             }
531             position++;
532         }
533 
534     }
535 
536 
processFontIndex(StructuredFieldReader structuredFieldReader, CharacterSetOrientation cso, Map<String, String> codepage, ValueNormalizer normalizer)537     private void processFontIndex(StructuredFieldReader structuredFieldReader, CharacterSetOrientation cso,
538             Map<String, String> codepage, ValueNormalizer normalizer)
539             throws IOException {
540 
541         byte[] data = structuredFieldReader.getNext(FONT_INDEX_SF);
542 
543         int position = 0;
544 
545         byte[] gcgid = new byte[8];
546         byte[] fiData = new byte[20];
547 
548         String firstABCMismatch = null;
549 
550         // Read data, ignoring bytes 0 - 2
551         for (int index = 3; index < data.length; index++) {
552             if (position < 8) {
553                 gcgid[position] = data[index];
554                 position++;
555             } else if (position < 27) {
556                 fiData[position - 8] = data[index];
557                 position++;
558             } else if (position == 27) {
559 
560                 fiData[position - 8] = data[index];
561 
562                 position = 0;
563 
564                 String gcgiString = new String(gcgid, AFPConstants.EBCIDIC_ENCODING);
565 
566                 String idx = codepage.get(gcgiString);
567 
568                 if (idx != null) {
569 
570                     char cidx = idx.charAt(0);
571                     int width = getUBIN(fiData, 0);
572                     int ascendHt = getSBIN(fiData, 2);
573                     int descendDp = getSBIN(fiData, 4);
574                     int a = getSBIN(fiData, 10);
575                     int b = getUBIN(fiData, 12);
576                     int c = getSBIN(fiData, 14);
577                     int abc = a + b + c;
578                     int diff = Math.abs(abc - width);
579                     if (diff != 0 && width != 0) {
580                         double diffPercent = 100 * diff / (double) width;
581                         if (diffPercent > 2) {
582                             if (LOG.isTraceEnabled()) {
583                                 LOG.trace(gcgiString + ": "
584                                         + a + " + " + b + " + " + c + " = " + (a + b + c)
585                                         + " but found: " + width);
586                             }
587                             if (firstABCMismatch == null) {
588                                 firstABCMismatch = gcgiString;
589                             }
590                         }
591                     }
592                     int normalizedWidth = normalizer.normalize(width);
593                     int x0 = normalizer.normalize(a);
594                     int y0 = normalizer.normalize(-descendDp);
595                     int dx = normalizer.normalize(b);
596                     int dy = normalizer.normalize(ascendHt + descendDp);
597                     cso.setCharacterMetrics(cidx, normalizedWidth, new Rectangle(x0, y0, dx, dy));
598                 }
599             }
600         }
601 
602         if (LOG.isDebugEnabled() && firstABCMismatch != null) {
603             //Debug level because it usually is no problem.
604             LOG.debug("Font has metrics inconsitencies where A+B+C doesn't equal the"
605                     + " character increment. The first such character found: "
606                     + firstABCMismatch);
607         }
608     }
609 
getUBIN(byte[] data, int start)610     private static int getUBIN(byte[] data, int start) {
611         return ((data[start] & 0xFF) << 8) + (data[start + 1] & 0xFF);
612     }
613 
getSBIN(byte[] data, int start)614     private static int getSBIN(byte[] data, int start) {
615         int ubin = ((data[start] & 0xFF) << 8) + (data[start + 1] & 0xFF);
616         if ((ubin & 0x8000) != 0) {
617             //extend sign
618             return ubin | 0xFFFF0000;
619         } else {
620             return ubin;
621         }
622     }
623 
624     private class FontControl {
625 
626         private int dpi;
627         private int unitsPerEm;
628 
629         private boolean isRelative;
630 
getDpi()631         public int getDpi() {
632             return dpi;
633         }
634 
setDpi(int i)635         public void setDpi(int i) {
636             dpi = i;
637         }
638 
getUnitsPerEm()639         public int getUnitsPerEm() {
640             return this.unitsPerEm;
641         }
642 
setUnitsPerEm(int value)643         public void setUnitsPerEm(int value) {
644             this.unitsPerEm = value;
645         }
646 
isRelative()647         public boolean isRelative() {
648             return isRelative;
649         }
650 
setRelative(boolean b)651         public void setRelative(boolean b) {
652             isRelative = b;
653         }
654     }
655 
656     private static class FontDescriptor {
657 
658         private byte[] data;
659 
FontDescriptor(byte[] data)660         public FontDescriptor(byte[] data) {
661             this.data = data;
662         }
663 
getNominalFontSizeInMillipoints()664         public int getNominalFontSizeInMillipoints() {
665             int nominalFontSize = 100 * getUBIN(data, 39);
666             return nominalFontSize;
667         }
668     }
669 
670     private static final class SingleByteLoader extends CharacterSetBuilder {
671 
672         private static final SingleByteLoader INSTANCE = new SingleByteLoader();
673 
SingleByteLoader()674         private SingleByteLoader() {
675             super();
676         }
677 
getInstance()678         private static SingleByteLoader getInstance() {
679             return INSTANCE;
680         }
681     }
682 
683     /**
684      * Double-byte (CID Keyed font (Type 0)) implementation of AFPFontReader.
685      */
686     private static final class DoubleByteLoader extends CharacterSetBuilder {
687 
688         private static final DoubleByteLoader INSTANCE = new DoubleByteLoader();
689 
DoubleByteLoader()690         private DoubleByteLoader() {
691         }
692 
getInstance()693         static DoubleByteLoader getInstance() {
694             return INSTANCE;
695         }
696 
697         @Override
loadCodePage(String codePage, String encoding, AFPResourceAccessor accessor, AFPEventProducer eventProducer)698         protected Map<String, String> loadCodePage(String codePage, String encoding,
699                 AFPResourceAccessor accessor, AFPEventProducer eventProducer) throws IOException {
700             // Create the HashMap to store code page information
701             Map<String, String> codePages = new HashMap<String, String>();
702             InputStream inputStream = null;
703             try {
704                 inputStream = super.openInputStream(accessor, codePage.trim(), eventProducer);
705             } catch (IOException e) {
706                 eventProducer.codePageNotFound(this, e);
707                 throw e;
708             }
709             try {
710                 StructuredFieldReader structuredFieldReader = new StructuredFieldReader(inputStream);
711                 byte[] data;
712                 while ((data = structuredFieldReader.getNext(CHARACTER_TABLE_SF)) != null) {
713                     int position = 0;
714                     byte[] gcgiBytes = new byte[8];
715                     byte[] charBytes = new byte[2];
716                     // Read data, ignoring bytes 0 - 2
717                     for (int index = 3; index < data.length; index++) {
718 
719                         if (position < 8) {
720                             // Build the graphic character global identifier key
721                             gcgiBytes[position] = data[index];
722                             position++;
723                         } else if (position == 9) {
724                             // Set the character
725                             charBytes[0] = data[index];
726                             position++;
727                         } else if (position == 10) {
728                             position = 0;
729                             // Set the character
730                             charBytes[1] = data[index];
731 
732                             String gcgiString = new String(gcgiBytes,
733                                     AFPConstants.EBCIDIC_ENCODING);
734                             String charString = new String(charBytes, encoding);
735                             codePages.put(gcgiString, charString);
736                         } else {
737                             position++;
738                         }
739                     }
740                 }
741             } finally {
742                 super.closeInputStream(inputStream);
743             }
744             return codePages;
745         }
746 
747     }
748 
determineOrientation(byte orientation)749     private static int determineOrientation(byte orientation) {
750         int degrees = 0;
751 
752         switch (orientation) {
753         case 0x00:
754             degrees = 0;
755             break;
756         case 0x2D:
757             degrees = 90;
758             break;
759         case 0x5A:
760             degrees = 180;
761             break;
762         case (byte) 0x87:
763             degrees = 270;
764             break;
765         default:
766             throw new IllegalStateException("Invalid orientation: " + orientation);
767         }
768         return degrees;
769     }
770 }
771