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$ */
19 
20 package org.apache.fop.fonts.cff;
21 
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.LinkedHashMap;
25 import java.util.List;
26 import java.util.Map;
27 
28 import org.apache.fontbox.cff.CFFDataInput;
29 import org.apache.fontbox.cff.CFFOperator;
30 
31 import org.apache.fop.fonts.truetype.FontFileReader;
32 import org.apache.fop.fonts.truetype.OTFFile;
33 
34 /**
35  * A class to read the CFF data from an OTF CFF font file.
36  */
37 public class CFFDataReader {
38     private CFFDataInput cffData;
39 
40     private byte[] header;
41     private CFFIndexData nameIndex;
42     private CFFIndexData topDICTIndex;
43     private CFFIndexData stringIndex;
44     private CFFIndexData charStringIndex;
45     private CFFIndexData globalIndexSubr;
46     private CFFIndexData localIndexSubr;
47     private CustomEncoding encoding;
48     private FDSelect fdSelect;
49     private List<FontDict> fdFonts;
50 
51     private static final int DOUBLE_BYTE_OPERATOR = 12;
52     private static final int NUM_STANDARD_STRINGS = 391;
53 
54     /** Commonly used parsed dictionaries */
55     private LinkedHashMap<String, DICTEntry> topDict;
56 
CFFDataReader()57     public CFFDataReader() {
58 
59     }
60 
61     /**
62      * Constructor for the CFF data reader which accepts the CFF byte data
63      * as an argument.
64      * @param cffDataArray A byte array which holds the CFF data
65      */
CFFDataReader(byte[] cffDataArray)66     public CFFDataReader(byte[] cffDataArray) throws IOException {
67         cffData = new CFFDataInput(cffDataArray);
68         readCFFData();
69     }
70 
71     /**
72      * Constructor for the CFF data reader which accepts a FontFileReader object
73      * which points to the original font file as an argument.
74      * @param fontFile The font file as represented by a FontFileReader object
75      */
CFFDataReader(FontFileReader fontFile)76     public CFFDataReader(FontFileReader fontFile) throws IOException {
77         cffData = new CFFDataInput(OTFFile.getCFFData(fontFile));
78         readCFFData();
79     }
80 
readCFFData()81     private void readCFFData() throws IOException {
82         header = readHeader();
83         nameIndex = readIndex();
84         topDICTIndex = readIndex();
85         topDict = parseDictData(topDICTIndex.getData());
86         stringIndex = readIndex();
87         globalIndexSubr = readIndex();
88         charStringIndex = readCharStringIndex();
89         encoding = readEncoding();
90         fdSelect = readFDSelect();
91         localIndexSubr = readLocalIndexSubrs();
92         fdFonts = parseCIDData();
93     }
94 
getPrivateDict(DICTEntry privateEntry)95     public Map<String, DICTEntry> getPrivateDict(DICTEntry privateEntry) throws IOException {
96         return parseDictData(getPrivateDictBytes(privateEntry));
97     }
98 
getPrivateDictBytes(DICTEntry privateEntry)99     public byte[] getPrivateDictBytes(DICTEntry privateEntry) throws IOException {
100         int privateLength = privateEntry.getOperands().get(0).intValue();
101         int privateOffset = privateEntry.getOperands().get(1).intValue();
102         return getCFFOffsetBytes(privateOffset, privateLength);
103     }
104 
105     /**
106      * Retrieves a number of bytes from the CFF data stream
107      * @param offset The offset of the bytes to retrieve
108      * @param length The number of bytes to retrieve
109      * @return Returns a byte array of requested bytes
110      * @throws IOException Throws an IO Exception if an error occurs
111      */
getCFFOffsetBytes(int offset, int length)112     private byte[] getCFFOffsetBytes(int offset, int length) throws IOException {
113         cffData.setPosition(offset);
114         return cffData.readBytes(length);
115     }
116 
117     /**
118      * Parses the dictionary data and returns a map of objects for each entry
119      * @param dictData The data for the dictionary data
120      * @return Returns a map of type DICTEntry identified by the operand name
121      * @throws IOException Throws an IO Exception if an error occurs
122      */
parseDictData(byte[] dictData)123     public LinkedHashMap<String, DICTEntry> parseDictData(byte[] dictData) throws IOException {
124         LinkedHashMap<String, DICTEntry> dictEntries = new LinkedHashMap<String, DICTEntry>();
125         List<Number> operands = new ArrayList<Number>();
126         List<Integer> operandLengths = new ArrayList<Integer>();
127         int lastOperandLength = 0;
128         for (int i = 0; i < dictData.length; i++) {
129             int readByte = dictData[i] & 0xFF;
130             if (readByte < 28) {
131                 int[] operator = new int[(readByte == DOUBLE_BYTE_OPERATOR) ? 2 : 1];
132                 if (readByte == DOUBLE_BYTE_OPERATOR) {
133                     operator[0] = dictData[i];
134                     operator[1] = dictData[i + 1];
135                     i++;
136                 } else {
137                     operator[0] = dictData[i];
138                 }
139                 String operatorName = "";
140                 CFFOperator tempOp = null;
141                 if (operator.length > 1) {
142                     tempOp = CFFOperator.getOperator(new CFFOperator.Key(operator[0], operator[1]));
143                 } else {
144                     tempOp = CFFOperator.getOperator(new CFFOperator.Key(operator[0]));
145                 }
146                 if (tempOp != null) {
147                     operatorName = tempOp.getName();
148                 }
149                 DICTEntry newEntry = new DICTEntry();
150                 newEntry.setOperator(operator);
151                 newEntry.setOperands(new ArrayList<Number>(operands));
152                 newEntry.setOperatorName(operatorName);
153                 newEntry.setOffset(i - lastOperandLength);
154                 newEntry.setOperandLength(lastOperandLength);
155                 newEntry.setOperandLengths(new ArrayList<Integer>(operandLengths));
156                 byte[] byteData = new byte[lastOperandLength + operator.length];
157                 System.arraycopy(dictData, i - operator.length - (lastOperandLength - 1),
158                         byteData, 0, operator.length + lastOperandLength);
159                 newEntry.setByteData(byteData);
160                 dictEntries.put(operatorName, newEntry);
161                 operands.clear();
162                 operandLengths.clear();
163                 lastOperandLength = 0;
164             } else {
165                 if (readByte >= 32 && readByte <= 246) {
166                     operands.add(readByte - 139);
167                     lastOperandLength += 1;
168                     operandLengths.add(1);
169                 } else if (readByte >= 247 && readByte <= 250) {
170                     operands.add((readByte - 247) * 256 + (dictData[i + 1] & 0xFF) + 108);
171                     lastOperandLength += 2;
172                     operandLengths.add(2);
173                     i++;
174                 } else if (readByte >= 251 && readByte <= 254) {
175                     operands.add(-(readByte - 251) * 256 - (dictData[i + 1] & 0xFF) - 108);
176                     lastOperandLength += 2;
177                     operandLengths.add(2);
178                     i++;
179                 } else if (readByte == 28) {
180                     operands.add((dictData[i + 1] & 0xFF) << 8 | (dictData[i + 2] & 0xFF));
181                     lastOperandLength += 3;
182                     operandLengths.add(3);
183                     i += 2;
184                 } else if (readByte == 29) {
185                     operands.add((dictData[i + 1] & 0xFF) << 24 | (dictData[i + 2] & 0xFF) << 16
186                             | (dictData[i + 3] & 0xFF) << 8 | (dictData[i + 4] & 0xFF));
187                     lastOperandLength += 5;
188                     operandLengths.add(5);
189                     i += 4;
190                 } else if (readByte == 30) {
191                     boolean terminatorFound = false;
192                     StringBuilder realNumber = new StringBuilder();
193                     int byteCount = 1;
194                     do {
195                         byte nibblesByte = dictData[++i];
196                         byteCount++;
197                         terminatorFound = readNibble(realNumber, (nibblesByte >> 4) & 0x0F);
198                         if (!terminatorFound) {
199                             terminatorFound = readNibble(realNumber, nibblesByte & 0x0F);
200                         }
201                     } while (!terminatorFound);
202                     operands.add(Double.valueOf(realNumber.toString()));
203                     lastOperandLength += byteCount;
204                     operandLengths.add(byteCount);
205                 }
206             }
207         }
208         return dictEntries;
209     }
210 
readNibble(StringBuilder realNumber, int nibble)211     private boolean readNibble(StringBuilder realNumber, int nibble) {
212         if (nibble <= 0x9) {
213             realNumber.append(nibble);
214         } else {
215             switch (nibble) {
216             case 0xa: realNumber.append("."); break;
217             case 0xb: realNumber.append("E"); break;
218             case 0xc: realNumber.append("E-"); break;
219             case 0xd: break;
220             case 0xe: realNumber.append("-"); break;
221             case 0xf: return true;
222             default:  throw new AssertionError("Unexpected nibble value");
223             }
224         }
225         return false;
226     }
227 
228     /**
229      * A class containing data for a dictionary entry
230      */
231     public static class DICTEntry {
232         private int[] operator;
233         private List<Number> operands;
234         private List<Integer> operandLengths;
235         private String operatorName;
236         private int offset;
237         private int operandLength;
238         private byte[] data = new byte[0];
239 
setOperator(int[] operator)240         public void setOperator(int[] operator) {
241             this.operator = operator;
242         }
243 
getOperator()244         public int[] getOperator() {
245             return this.operator;
246         }
247 
setOperands(List<Number> operands)248         public void setOperands(List<Number> operands) {
249             this.operands = operands;
250         }
251 
getOperands()252         public List<Number> getOperands() {
253             return this.operands;
254         }
255 
setOperatorName(String operatorName)256         public void setOperatorName(String operatorName) {
257             this.operatorName = operatorName;
258         }
259 
getOperatorName()260         public String getOperatorName() {
261             return this.operatorName;
262         }
263 
setOffset(int offset)264         public void setOffset(int offset) {
265             this.offset = offset;
266         }
267 
getOffset()268         public int getOffset() {
269             return this.offset;
270         }
271 
setOperandLength(int operandLength)272         public void setOperandLength(int operandLength) {
273             this.operandLength = operandLength;
274         }
275 
getOperandLength()276         public int getOperandLength() {
277             return this.operandLength;
278         }
279 
setByteData(byte[] data)280         public void setByteData(byte[] data) {
281             this.data = data.clone();
282         }
283 
getByteData()284         public byte[] getByteData() {
285             return data.clone();
286         }
287 
setOperandLengths(List<Integer> operandLengths)288         public void setOperandLengths(List<Integer> operandLengths) {
289             this.operandLengths = operandLengths;
290         }
291 
getOperandLengths()292         public List<Integer> getOperandLengths() {
293             return operandLengths;
294         }
295     }
296 
readHeader()297     private byte[] readHeader() throws IOException {
298         //Read known header
299         byte[] fixedHeader = cffData.readBytes(4);
300         int hdrSize = (fixedHeader[2] & 0xFF);
301         byte[] extra = cffData.readBytes(hdrSize - 4);
302         byte[] header = new byte[hdrSize];
303         for (int i = 0; i < fixedHeader.length; i++) {
304             header[i] = fixedHeader[i];
305         }
306         for (int i = 4; i < extra.length; i++) {
307             header[i] = extra[i - 4];
308         }
309         return header;
310     }
311 
312     /**
313      * Reads a CFF index object are the specified offset position
314      * @param offset The position of the index object to read
315      * @return Returns an object representing the index
316      * @throws IOException Throws an IO Exception if an error occurs
317      */
readIndex(int offset)318     public CFFIndexData readIndex(int offset) throws IOException {
319         cffData.setPosition(offset);
320         return readIndex();
321     }
322 
readIndex()323     private CFFIndexData readIndex() throws IOException {
324         return readIndex(cffData);
325     }
326 
327     /**
328      * Reads an index from the current position of the CFFDataInput object
329      * @param input The object holding the CFF byte data
330      * @return Returns an object representing the index
331      * @throws IOException Throws an IO Exception if an error occurs
332      */
readIndex(CFFDataInput input)333     public CFFIndexData readIndex(CFFDataInput input) throws IOException {
334         CFFIndexData nameIndex = new CFFIndexData();
335         if (input != null) {
336             int origPos = input.getPosition();
337             nameIndex.parseIndexHeader(input);
338             int tableSize = input.getPosition() - origPos;
339             nameIndex.setByteData(input.getPosition() - tableSize, tableSize);
340         }
341         return nameIndex;
342     }
343 
344     /**
345      * Retrieves the SID for the given GID object
346      * @param charsetOffset The offset of the charset data
347      * @param gid The GID for which to retrieve the SID
348      * @return Returns the SID as an integer
349      */
getSIDFromGID(int charsetOffset, int gid)350     public int getSIDFromGID(int charsetOffset, int gid) throws IOException {
351         if (gid == 0) {
352             return 0;
353         }
354         cffData.setPosition(charsetOffset);
355         int charsetFormat = cffData.readCard8();
356         switch (charsetFormat) {
357         case 0: //Adjust for .notdef character
358                  cffData.setPosition(cffData.getPosition() + (--gid * 2));
359                  return cffData.readSID();
360         case 1: return getSIDFromGIDFormat(gid, 1);
361         case 2: return getSIDFromGIDFormat(gid, 2);
362         default: return 0;
363         }
364     }
365 
getSIDFromGIDFormat(int gid, int format)366     private int getSIDFromGIDFormat(int gid, int format) throws IOException {
367         int glyphCount = 0;
368         while (true) {
369             int oldGlyphCount = glyphCount;
370             int start = cffData.readSID();
371             glyphCount += ((format == 1) ? cffData.readCard8() : cffData.readCard16()) + 1;
372             if (gid <= glyphCount) {
373                 return start + (gid - oldGlyphCount) - 1;
374             }
375         }
376     }
377 
getHeader()378     public byte[] getHeader() {
379         return header.clone();
380     }
381 
getNameIndex()382     public CFFIndexData getNameIndex() {
383         return nameIndex;
384     }
385 
getTopDictIndex()386     public CFFIndexData getTopDictIndex() {
387         return topDICTIndex;
388     }
389 
getTopDictEntries()390     public LinkedHashMap<String, DICTEntry> getTopDictEntries() {
391         return topDict;
392     }
393 
getStringIndex()394     public CFFIndexData getStringIndex() {
395         return stringIndex;
396     }
397 
getGlobalIndexSubr()398     public CFFIndexData getGlobalIndexSubr() {
399         return globalIndexSubr;
400     }
401 
getLocalIndexSubr()402     public CFFIndexData getLocalIndexSubr() {
403         return localIndexSubr;
404     }
405 
getCharStringIndex()406     public CFFIndexData getCharStringIndex() {
407         return charStringIndex;
408     }
409 
getCFFData()410     public CFFDataInput getCFFData() {
411         return cffData;
412     }
413 
getEncoding()414     public CustomEncoding getEncoding() {
415         return encoding;
416     }
417 
getFDSelect()418     public FDSelect getFDSelect() {
419         return fdSelect;
420     }
421 
getFDFonts()422     public List<FontDict> getFDFonts() {
423         return fdFonts;
424     }
425 
getLocalSubrsForGlyph(int glyph)426     public CFFDataInput getLocalSubrsForGlyph(int glyph) throws IOException {
427         //Subsets are currently written using a Format0 FDSelect
428         FDSelect fontDictionary = getFDSelect();
429         if (fontDictionary instanceof Format0FDSelect) {
430             Format0FDSelect fdSelect = (Format0FDSelect)fontDictionary;
431             int found = fdSelect.getFDIndexes()[glyph];
432             FontDict font = getFDFonts().get(found);
433             byte[] localSubrData = font.getLocalSubrData().getByteData();
434             if (localSubrData != null) {
435                 return new CFFDataInput(localSubrData);
436             } else {
437                 return null;
438             }
439         } else if (fontDictionary instanceof Format3FDSelect) {
440             Format3FDSelect fdSelect = (Format3FDSelect)fontDictionary;
441             int index = 0;
442             for (int first : fdSelect.getRanges().keySet()) {
443                 if (first > glyph) {
444                     break;
445                 }
446                 index++;
447             }
448             FontDict font = getFDFonts().get(index);
449             byte[] localSubrsData = font.getLocalSubrData().getByteData();
450             if (localSubrsData != null) {
451                 return new CFFDataInput(localSubrsData);
452             } else {
453                 return null;
454             }
455         }
456         return null;
457     }
458 
459     /**
460      * Parses the char string index from the CFF byte data
461      * @return Returns the char string index object
462      * @throws IOException Throws an IO Exception if an error occurs
463      */
readCharStringIndex()464     public CFFIndexData readCharStringIndex() throws IOException {
465         int offset = topDict.get("CharStrings").getOperands().get(0).intValue();
466         cffData.setPosition(offset);
467         return readIndex();
468     }
469 
readEncoding()470     private CustomEncoding readEncoding() throws IOException {
471         CustomEncoding foundEncoding = null;
472         if (topDict.get("Encoding") != null) {
473             int offset = topDict.get("Encoding").getOperands().get(0).intValue();
474             if (offset != 0 && offset != 1) {
475                 //No need to set the offset as we are reading the data sequentially.
476                 int format = cffData.readCard8();
477                 int numEntries = cffData.readCard8();
478                 switch (format) {
479                 case 0:
480                     foundEncoding = readFormat0Encoding(format, numEntries);
481                     break;
482                 case 1:
483                     foundEncoding = readFormat1Encoding(format, numEntries);
484                     break;
485                 default: break;
486                 }
487             }
488         }
489         return foundEncoding;
490     }
491 
readFormat0Encoding(int format, int numEntries)492     private Format0Encoding readFormat0Encoding(int format, int numEntries)
493             throws IOException {
494         Format0Encoding newEncoding = new Format0Encoding();
495         newEncoding.setFormat(format);
496         newEncoding.setNumEntries(numEntries);
497         int[] codes = new int[numEntries];
498         for (int i = 0; i < numEntries; i++) {
499             codes[i] = cffData.readCard8();
500         }
501         newEncoding.setCodes(codes);
502         return newEncoding;
503     }
504 
readFormat1Encoding(int format, int numEntries)505     private Format1Encoding readFormat1Encoding(int format, int numEntries)
506             throws IOException {
507         Format1Encoding newEncoding = new Format1Encoding();
508         newEncoding.setFormat(format);
509         newEncoding.setNumEntries(numEntries);
510         Map<Integer, Integer> ranges = new LinkedHashMap<Integer, Integer>();
511         for (int i = 0; i < numEntries; i++) {
512             int first = cffData.readCard8();
513             int left = cffData.readCard8();
514             ranges.put(first, left);
515         }
516         newEncoding.setRanges(ranges);
517         return newEncoding;
518     }
519 
readFDSelect()520     private FDSelect readFDSelect() throws IOException {
521         FDSelect fdSelect = null;
522         DICTEntry fdSelectEntry = topDict.get("FDSelect");
523         if (fdSelectEntry != null) {
524             int fdOffset = fdSelectEntry.getOperands().get(0).intValue();
525             cffData.setPosition(fdOffset);
526             int format = cffData.readCard8();
527             switch (format) {
528             case 0:
529                 fdSelect = readFormat0FDSelect();
530                 break;
531             case 3:
532                 fdSelect = readFormat3FDSelect();
533                 break;
534             default:
535             }
536         }
537         return fdSelect;
538     }
539 
readFormat0FDSelect()540     private Format0FDSelect readFormat0FDSelect() throws IOException {
541         Format0FDSelect newFDs = new Format0FDSelect();
542         newFDs.setFormat(0);
543         int glyphCount = charStringIndex.getNumObjects();
544         int[] fds = new int[glyphCount];
545         for (int i = 0; i < glyphCount; i++) {
546             fds[i] = cffData.readCard8();
547         }
548         newFDs.setFDIndexes(fds);
549         return newFDs;
550     }
551 
readFormat3FDSelect()552     private Format3FDSelect readFormat3FDSelect() throws IOException {
553         Format3FDSelect newFDs = new Format3FDSelect();
554         newFDs.setFormat(3);
555         int rangeCount = cffData.readCard16();
556         newFDs.setRangeCount(rangeCount);
557         Map<Integer, Integer> ranges = new LinkedHashMap<Integer, Integer>();
558         for (int i = 0; i < rangeCount; i++) {
559             int first = cffData.readCard16();
560             int fd = cffData.readCard8();
561             ranges.put(first, fd);
562         }
563         newFDs.setRanges(ranges);
564         newFDs.setSentinelGID(cffData.readCard16());
565         return newFDs;
566     }
567 
parseCIDData()568     private List<FontDict> parseCIDData() throws IOException {
569         List<FontDict> fdFonts = new ArrayList<FontDict>();
570         if (topDict.get("ROS") != null) {
571             DICTEntry fdArray = topDict.get("FDArray");
572             if (fdArray != null) {
573                 int fdIndex = fdArray.getOperands().get(0).intValue();
574                 CFFIndexData fontDicts = readIndex(fdIndex);
575                 for (int i = 0; i < fontDicts.getNumObjects(); i++) {
576                     FontDict newFontDict = new FontDict();
577 
578                     byte[] fdData = fontDicts.getValue(i);
579                     Map<String, DICTEntry> fdEntries = parseDictData(fdData);
580                     newFontDict.setByteData(fontDicts.getValuePosition(i), fontDicts.getValueLength(i));
581                     DICTEntry fontFDEntry = fdEntries.get("FontName");
582                     if (fontFDEntry != null) {
583                         newFontDict.setFontName(getString(fontFDEntry.getOperands().get(0).intValue()));
584                     }
585                     DICTEntry privateFDEntry = fdEntries.get("Private");
586                     if (privateFDEntry != null) {
587                         newFontDict = setFDData(privateFDEntry, newFontDict);
588                     }
589 
590                     fdFonts.add(newFontDict);
591                 }
592             }
593         }
594         return fdFonts;
595     }
596 
setFDData(DICTEntry privateFDEntry, FontDict newFontDict)597     private FontDict setFDData(DICTEntry privateFDEntry, FontDict newFontDict) throws IOException {
598         int privateFDLength = privateFDEntry.getOperands().get(0).intValue();
599         int privateFDOffset = privateFDEntry.getOperands().get(1).intValue();
600         cffData.setPosition(privateFDOffset);
601         byte[] privateDict = cffData.readBytes(privateFDLength);
602         newFontDict.setPrivateDictData(privateFDOffset, privateFDLength);
603         Map<String, DICTEntry> privateEntries = parseDictData(privateDict);
604         DICTEntry subroutines = privateEntries.get("Subrs");
605         if (subroutines != null) {
606             CFFIndexData localSubrs = readIndex(privateFDOffset
607                     + subroutines.getOperands().get(0).intValue());
608             newFontDict.setLocalSubrData(localSubrs);
609         } else {
610             newFontDict.setLocalSubrData(new CFFIndexData());
611         }
612         return newFontDict;
613     }
614 
getString(int sid)615     private String getString(int sid) throws IOException {
616         return new String(stringIndex.getValue(sid - NUM_STANDARD_STRINGS));
617     }
618 
readLocalIndexSubrs()619     private CFFIndexData readLocalIndexSubrs() throws IOException {
620         CFFIndexData localSubrs = null;
621         DICTEntry privateEntry = topDict.get("Private");
622         if (privateEntry != null) {
623             int length = privateEntry.getOperands().get(0).intValue();
624             int offset = privateEntry.getOperands().get(1).intValue();
625             cffData.setPosition(offset);
626             byte[] privateData = cffData.readBytes(length);
627             Map<String, DICTEntry> privateDict = parseDictData(privateData);
628             DICTEntry localSubrsEntry = privateDict.get("Subrs");
629             if (localSubrsEntry != null) {
630                 int localOffset = offset + localSubrsEntry.getOperands().get(0).intValue();
631                 cffData.setPosition(localOffset);
632                 localSubrs = readIndex();
633             }
634         }
635         return localSubrs;
636     }
637 
638     /**
639      * Parent class which provides the ability to retrieve byte data from
640      * a sub-table.
641      */
642     public class CFFSubTable {
643         private DataLocation dataLocation = new DataLocation();
644 
setByteData(int position, int length)645         public void setByteData(int position, int length) {
646             dataLocation = new DataLocation(position, length);
647         }
648 
getByteData()649         public byte[] getByteData() throws IOException {
650             int oldPos = cffData.getPosition();
651             try {
652                 cffData.setPosition(dataLocation.getDataPosition());
653                 return cffData.readBytes(dataLocation.getDataLength());
654             } finally {
655                 cffData.setPosition(oldPos);
656             }
657         }
658     }
659 
660     /**
661      * An object used to hold index data from the CFF data
662      */
663     public class CFFIndexData extends CFFSubTable {
664         private int numObjects;
665         private int offSize;
666         private int[] offsets = new int[0];
667         private DataLocation dataLocation = new DataLocation();
668 
setNumObjects(int numObjects)669         public void setNumObjects(int numObjects) {
670             this.numObjects = numObjects;
671         }
672 
getNumObjects()673         public int getNumObjects() {
674             return this.numObjects;
675         }
676 
setOffSize(int offSize)677         public void setOffSize(int offSize) {
678             this.offSize = offSize;
679         }
680 
getOffSize()681         public int getOffSize() {
682             return this.offSize;
683         }
684 
setOffsets(int[] offsets)685         public void setOffsets(int[] offsets) {
686             this.offsets = offsets.clone();
687         }
688 
getOffsets()689         public int[] getOffsets() {
690             return offsets.clone();
691         }
692 
setData(int position, int length)693         public void setData(int position, int length) {
694             dataLocation = new DataLocation(position, length);
695         }
696 
getData()697         public byte[] getData() throws IOException {
698             int origPos = cffData.getPosition();
699             try {
700                 cffData.setPosition(dataLocation.getDataPosition());
701                 return cffData.readBytes(dataLocation.getDataLength());
702             } finally {
703                 cffData.setPosition(origPos);
704             }
705         }
706 
707         /**
708          * Parses index data from an index object found within the CFF byte data
709          * @param cffData A byte array containing the CFF data
710          * @throws IOException Throws an IO Exception if an error occurs
711          */
parseIndexHeader(CFFDataInput cffData)712         public void parseIndexHeader(CFFDataInput cffData) throws IOException {
713             setNumObjects(cffData.readCard16());
714             setOffSize(cffData.readOffSize());
715             int[] offsets = new int[getNumObjects() + 1];
716             byte[] bytes;
717             //Fills the offsets array
718             for (int i = 0; i <= getNumObjects(); i++) {
719                 switch (getOffSize()) {
720                 case 1:
721                     offsets[i] = cffData.readCard8();
722                     break;
723                 case 2:
724                     offsets[i] = cffData.readCard16();
725                     break;
726                 case 3:
727                     bytes = cffData.readBytes(3);
728                     offsets[i] = ((bytes[0] & 0xFF) << 16) + ((bytes[1] & 0xFF) << 8) + (bytes[2] & 0xFF);
729                     break;
730                 case 4:
731                     bytes = cffData.readBytes(4);
732                     offsets[i] = ((bytes[0] & 0xFF) << 24) + ((bytes[1] & 0xFF) << 16)
733                             + ((bytes[2] & 0xFF) << 8) + (bytes[3] & 0xFF);
734                     break;
735                 default: continue;
736                 }
737             }
738             setOffsets(offsets);
739             int position = cffData.getPosition();
740             int dataSize = offsets[offsets.length - 1] - offsets[0];
741 
742             cffData.setPosition(cffData.getPosition() + dataSize);
743             setData(position, dataSize);
744         }
745 
746         /**
747          * Retrieves data from the index data
748          * @param index The index position of the data to retrieve
749          * @return Returns the byte data for the given index
750          * @throws IOException Throws an IO Exception if an error occurs
751          */
getValue(int index)752         public byte[] getValue(int index) throws IOException {
753             int oldPos = cffData.getPosition();
754             try {
755                 cffData.setPosition(dataLocation.getDataPosition() + (offsets[index] - 1));
756                 return cffData.readBytes(offsets[index + 1] - offsets[index]);
757             } finally {
758                 cffData.setPosition(oldPos);
759             }
760         }
761 
getValuePosition(int index)762         public int getValuePosition(int index) {
763             return dataLocation.getDataPosition() + (offsets[index] - 1);
764         }
765 
getValueLength(int index)766         public int getValueLength(int index) {
767             return offsets[index + 1] - offsets[index];
768         }
769     }
770 
771     public abstract class CustomEncoding {
772         private int format;
773         private int numEntries;
774 
setFormat(int format)775         public void setFormat(int format) {
776             this.format = format;
777         }
778 
getFormat()779         public int getFormat() {
780             return format;
781         }
782 
setNumEntries(int numEntries)783         public void setNumEntries(int numEntries) {
784             this.numEntries = numEntries;
785         }
786 
getNumEntries()787         public int getNumEntries() {
788             return numEntries;
789         }
790     }
791 
792     public class Format0Encoding extends CustomEncoding {
793         private int[] codes = new int[0];
794 
setCodes(int[] codes)795         public void setCodes(int[] codes) {
796             this.codes = codes.clone();
797         }
798 
getCodes()799         public int[] getCodes() {
800             return codes.clone();
801         }
802     }
803 
804     public class Format1Encoding extends CustomEncoding {
805         private Map<Integer, Integer> ranges;
806 
setRanges(Map<Integer, Integer> ranges)807         public void setRanges(Map<Integer, Integer> ranges) {
808             this.ranges = ranges;
809         }
810 
getRanges()811         public Map<Integer, Integer> getRanges() {
812             return ranges;
813         }
814     }
815 
816     public abstract class FDSelect {
817         private int format;
818 
setFormat(int format)819         public void setFormat(int format) {
820             this.format = format;
821         }
822 
getFormat()823         public int getFormat() {
824             return format;
825         }
826     }
827 
828     public class Format0FDSelect extends FDSelect {
829         private int[] fds = new int[0];
830 
setFDIndexes(int[] fds)831         public void setFDIndexes(int[] fds) {
832             this.fds = fds.clone();
833         }
834 
getFDIndexes()835         public int[] getFDIndexes() {
836             return fds.clone();
837         }
838     }
839 
840     public class Format3FDSelect extends FDSelect {
841         private int rangeCount;
842         private Map<Integer, Integer> ranges;
843         private int sentinelGID;
844 
setRangeCount(int rangeCount)845         public void setRangeCount(int rangeCount) {
846             this.rangeCount = rangeCount;
847         }
848 
getRangeCount()849         public int getRangeCount() {
850             return rangeCount;
851         }
852 
setRanges(Map<Integer, Integer> ranges)853         public void setRanges(Map<Integer, Integer> ranges) {
854             this.ranges = ranges;
855         }
856 
getRanges()857         public Map<Integer, Integer> getRanges() {
858             return ranges;
859         }
860 
setSentinelGID(int sentinelGID)861         public void setSentinelGID(int sentinelGID) {
862             this.sentinelGID = sentinelGID;
863         }
864 
getSentinelGID()865         public int getSentinelGID() {
866             return sentinelGID;
867         }
868     }
869 
870     public class FontDict extends CFFSubTable {
871         private String fontName;
872         private DataLocation dataLocation = new DataLocation();
873         private CFFIndexData localSubrData;
874 
setFontName(String groupName)875         public void setFontName(String groupName) {
876             this.fontName = groupName;
877         }
878 
getFontName()879         public String getFontName() {
880             return fontName;
881         }
882 
setPrivateDictData(int position, int length)883         public void setPrivateDictData(int position, int length) {
884             dataLocation = new DataLocation(position, length);
885         }
886 
getPrivateDictData()887         public byte[] getPrivateDictData() throws IOException {
888             int origPos = cffData.getPosition();
889             try {
890                 cffData.setPosition(dataLocation.getDataPosition());
891                 return cffData.readBytes(dataLocation.getDataLength());
892             } finally {
893                 cffData.setPosition(origPos);
894             }
895         }
896 
setLocalSubrData(CFFIndexData localSubrData)897         public void setLocalSubrData(CFFIndexData localSubrData) {
898             this.localSubrData = localSubrData;
899         }
900 
getLocalSubrData()901         public CFFIndexData getLocalSubrData() {
902             return localSubrData;
903         }
904     }
905 
906     private static class DataLocation {
907         private int dataPosition;
908         private int dataLength;
909 
DataLocation()910         public DataLocation() {
911             dataPosition = 0;
912             dataLength = 0;
913         }
914 
DataLocation(int position, int length)915         public DataLocation(int position, int length) {
916             this.dataPosition = position;
917             this.dataLength = length;
918         }
919 
getDataPosition()920         public int getDataPosition() {
921             return dataPosition;
922         }
923 
getDataLength()924         public int getDataLength() {
925             return dataLength;
926         }
927     }
928 }
929