--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.fonts.cff;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fontbox.cff.CFFDataInput;
+import org.apache.fontbox.cff.CFFOperator;
+
+import org.apache.fop.fonts.truetype.FontFileReader;
+import org.apache.fop.fonts.truetype.OTFFile;
+
+/**
+ * A class to read the CFF data from an OTF CFF font file.
+ */
+public class CFFDataReader {
+ private CFFDataInput cffData;
+
+ private byte[] header;
+ private CFFIndexData nameIndex;
+ private CFFIndexData topDICTIndex;
+ private CFFIndexData stringIndex;
+ private CFFIndexData charStringIndex;
+ private CFFIndexData globalIndexSubr;
+ private CFFIndexData localIndexSubr;
+ private CustomEncoding encoding;
+ private FDSelect fdSelect;
+ private List<FontDict> fdFonts;
+
+ private static final int DOUBLE_BYTE_OPERATOR = 12;
+ private static final int NUM_STANDARD_STRINGS = 391;
+
+ /** Commonly used parsed dictionaries */
+ private LinkedHashMap<String, DICTEntry> topDict;
+
+ public CFFDataReader() {
+
+ }
+
+ /**
+ * Constructor for the CFF data reader which accepts the CFF byte data
+ * as an argument.
+ * @param cffDataArray A byte array which holds the CFF data
+ */
+ public CFFDataReader(byte[] cffDataArray) throws IOException {
+ cffData = new CFFDataInput(cffDataArray);
+ readCFFData();
+ }
+
+ /**
+ * Constructor for the CFF data reader which accepts a FontFileReader object
+ * which points to the original font file as an argument.
+ * @param fontFile The font file as represented by a FontFileReader object
+ */
+ public CFFDataReader(FontFileReader fontFile) throws IOException {
+ cffData = new CFFDataInput(OTFFile.getCFFData(fontFile));
+ readCFFData();
+ }
+
+ private void readCFFData() throws IOException {
+ header = readHeader();
+ nameIndex = readIndex();
+ topDICTIndex = readIndex();
+ topDict = parseDictData(topDICTIndex.getData());
+ stringIndex = readIndex();
+ globalIndexSubr = readIndex();
+ charStringIndex = readCharStringIndex();
+ encoding = readEncoding();
+ fdSelect = readFDSelect();
+ localIndexSubr = readLocalIndexSubrs();
+ fdFonts = parseCIDData();
+ }
+
+ public Map<String, DICTEntry> getPrivateDict(DICTEntry privateEntry) throws IOException {
+ return parseDictData(getPrivateDictBytes(privateEntry));
+ }
+
+ public byte[] getPrivateDictBytes(DICTEntry privateEntry) throws IOException {
+ int privateLength = privateEntry.getOperands().get(0).intValue();
+ int privateOffset = privateEntry.getOperands().get(1).intValue();
+ return getCFFOffsetBytes(privateOffset, privateLength);
+ }
+
+ /**
+ * Retrieves a number of bytes from the CFF data stream
+ * @param offset The offset of the bytes to retrieve
+ * @param length The number of bytes to retrieve
+ * @return Returns a byte array of requested bytes
+ * @throws IOException Throws an IO Exception if an error occurs
+ */
+ private byte[] getCFFOffsetBytes(int offset, int length) throws IOException {
+ cffData.setPosition(offset);
+ return cffData.readBytes(length);
+ }
+
+ /**
+ * Parses the dictionary data and returns a map of objects for each entry
+ * @param dictData The data for the dictionary data
+ * @return Returns a map of type DICTEntry identified by the operand name
+ * @throws IOException Throws an IO Exception if an error occurs
+ */
+ public LinkedHashMap<String, DICTEntry> parseDictData(byte[] dictData) throws IOException {
+ LinkedHashMap<String, DICTEntry> dictEntries = new LinkedHashMap<String, DICTEntry>();
+ List<Number> operands = new ArrayList<Number>();
+ List<Integer> operandLengths = new ArrayList<Integer>();
+ int lastOperandLength = 0;
+ for (int i = 0; i < dictData.length; i++) {
+ int readByte = dictData[i] & 0xFF;
+ if (readByte < 28) {
+ int[] operator = new int[(readByte == DOUBLE_BYTE_OPERATOR) ? 2 : 1];
+ if (readByte == DOUBLE_BYTE_OPERATOR) {
+ operator[0] = dictData[i];
+ operator[1] = dictData[i + 1];
+ i++;
+ } else {
+ operator[0] = dictData[i];
+ }
+ String operatorName = "";
+ CFFOperator tempOp = null;
+ if (operator.length > 1) {
+ tempOp = CFFOperator.getOperator(new CFFOperator.Key(operator[0], operator[1]));
+ } else {
+ tempOp = CFFOperator.getOperator(new CFFOperator.Key(operator[0]));
+ }
+ if (tempOp != null) {
+ operatorName = tempOp.getName();
+ }
+ DICTEntry newEntry = new DICTEntry();
+ newEntry.setOperator(operator);
+ newEntry.setOperands(new ArrayList<Number>(operands));
+ newEntry.setOperatorName(operatorName);
+ newEntry.setOffset(i - lastOperandLength);
+ newEntry.setOperandLength(lastOperandLength);
+ newEntry.setOperandLengths(new ArrayList<Integer>(operandLengths));
+ byte[] byteData = new byte[lastOperandLength + operator.length];
+ System.arraycopy(dictData, i - operator.length - (lastOperandLength - 1),
+ byteData, 0, operator.length + lastOperandLength);
+ newEntry.setByteData(byteData);
+ dictEntries.put(operatorName, newEntry);
+ operands.clear();
+ operandLengths.clear();
+ lastOperandLength = 0;
+ } else {
+ if (readByte >= 32 && readByte <= 246) {
+ operands.add(readByte - 139);
+ lastOperandLength += 1;
+ operandLengths.add(1);
+ } else if (readByte >= 247 && readByte <= 250) {
+ operands.add((readByte - 247) * 256 + (dictData[i + 1] & 0xFF) + 108);
+ lastOperandLength += 2;
+ operandLengths.add(2);
+ i++;
+ } else if (readByte >= 251 && readByte <= 254) {
+ operands.add(-(readByte - 251) * 256 - (dictData[i + 1] & 0xFF) - 108);
+ lastOperandLength += 2;
+ operandLengths.add(2);
+ i++;
+ } else if (readByte == 28) {
+ operands.add((dictData[i + 1] & 0xFF) << 8 | (dictData[i + 2] & 0xFF));
+ lastOperandLength += 3;
+ operandLengths.add(3);
+ i += 2;
+ } else if (readByte == 29) {
+ operands.add((dictData[i + 1] & 0xFF) << 24 | (dictData[i + 2] & 0xFF) << 16
+ | (dictData[i + 3] & 0xFF) << 8 | (dictData[i + 4] & 0xFF));
+ lastOperandLength += 5;
+ operandLengths.add(5);
+ i += 4;
+ } else if (readByte == 30) {
+ boolean terminatorFound = false;
+ StringBuilder realNumber = new StringBuilder();
+ int byteCount = 1;
+ do {
+ byte nibblesByte = dictData[++i];
+ byteCount++;
+ terminatorFound = readNibble(realNumber, (nibblesByte >> 4) & 0x0F);
+ if (!terminatorFound) {
+ terminatorFound = readNibble(realNumber, nibblesByte & 0x0F);
+ }
+ } while (!terminatorFound);
+ operands.add(Double.valueOf(realNumber.toString()));
+ lastOperandLength += byteCount;
+ operandLengths.add(byteCount);
+ }
+ }
+ }
+ return dictEntries;
+ }
+
+ private boolean readNibble(StringBuilder realNumber, int nibble) {
+ if (nibble <= 0x9) {
+ realNumber.append(nibble);
+ } else {
+ switch (nibble) {
+ case 0xa: realNumber.append("."); break;
+ case 0xb: realNumber.append("E"); break;
+ case 0xc: realNumber.append("E-"); break;
+ case 0xd: break;
+ case 0xe: realNumber.append("-"); break;
+ case 0xf: return true;
+ default: throw new AssertionError("Unexpected nibble value");
+ }
+ }
+ return false;
+ }
+
+ /**
+ * A class containing data for a dictionary entry
+ */
+ public static class DICTEntry {
+ private int[] operator;
+ private List<Number> operands;
+ private List<Integer> operandLengths;
+ private String operatorName;
+ private int offset;
+ private int operandLength;
+ private byte[] data = new byte[0];
+
+ public void setOperator(int[] operator) {
+ this.operator = operator;
+ }
+
+ public int[] getOperator() {
+ return this.operator;
+ }
+
+ public void setOperands(List<Number> operands) {
+ this.operands = operands;
+ }
+
+ public List<Number> getOperands() {
+ return this.operands;
+ }
+
+ public void setOperatorName(String operatorName) {
+ this.operatorName = operatorName;
+ }
+
+ public String getOperatorName() {
+ return this.operatorName;
+ }
+
+ public void setOffset(int offset) {
+ this.offset = offset;
+ }
+
+ public int getOffset() {
+ return this.offset;
+ }
+
+ public void setOperandLength(int operandLength) {
+ this.operandLength = operandLength;
+ }
+
+ public int getOperandLength() {
+ return this.operandLength;
+ }
+
+ public void setByteData(byte[] data) {
+ this.data = data.clone();
+ }
+
+ public byte[] getByteData() {
+ return data.clone();
+ }
+
+ public void setOperandLengths(List<Integer> operandLengths) {
+ this.operandLengths = operandLengths;
+ }
+
+ public List<Integer> getOperandLengths() {
+ return operandLengths;
+ }
+ }
+
+ private byte[] readHeader() throws IOException {
+ //Read known header
+ byte[] fixedHeader = cffData.readBytes(4);
+ int hdrSize = (fixedHeader[2] & 0xFF);
+ byte[] extra = cffData.readBytes(hdrSize - 4);
+ byte[] header = new byte[hdrSize];
+ for (int i = 0; i < fixedHeader.length; i++) {
+ header[i] = fixedHeader[i];
+ }
+ for (int i = 4; i < extra.length; i++) {
+ header[i] = extra[i - 4];
+ }
+ return header;
+ }
+
+ /**
+ * Reads a CFF index object are the specified offset position
+ * @param offset The position of the index object to read
+ * @return Returns an object representing the index
+ * @throws IOException Throws an IO Exception if an error occurs
+ */
+ public CFFIndexData readIndex(int offset) throws IOException {
+ cffData.setPosition(offset);
+ return readIndex();
+ }
+
+ private CFFIndexData readIndex() throws IOException {
+ return readIndex(cffData);
+ }
+
+ /**
+ * Reads an index from the current position of the CFFDataInput object
+ * @param input The object holding the CFF byte data
+ * @return Returns an object representing the index
+ * @throws IOException Throws an IO Exception if an error occurs
+ */
+ public CFFIndexData readIndex(CFFDataInput input) throws IOException {
+ CFFIndexData nameIndex = new CFFIndexData();
+ if (input != null) {
+ int origPos = input.getPosition();
+ nameIndex.parseIndexHeader(input);
+ int tableSize = input.getPosition() - origPos;
+ nameIndex.setByteData(input.getPosition() - tableSize, tableSize);
+ }
+ return nameIndex;
+ }
+
+ /**
+ * Retrieves the SID for the given GID object
+ * @param charsetOffset The offset of the charset data
+ * @param GID The GID for which to retrieve the SID
+ * @return Returns the SID as an integer
+ */
+ public int getSIDFromGID(int charsetOffset, int gid) throws IOException {
+ if (gid == 0) {
+ return 0;
+ }
+ cffData.setPosition(charsetOffset);
+ int charsetFormat = cffData.readCard8();
+ switch (charsetFormat) {
+ case 0: //Adjust for .notdef character
+ cffData.setPosition(cffData.getPosition() + (--gid * 2));
+ return cffData.readSID();
+ case 1: return getSIDFromGIDFormat(gid, 1);
+ case 2: return getSIDFromGIDFormat(gid, 2);
+ default: return 0;
+ }
+ }
+
+ private int getSIDFromGIDFormat(int gid, int format) throws IOException {
+ int glyphCount = 0;
+ while (true) {
+ int oldGlyphCount = glyphCount;
+ int start = cffData.readSID();
+ glyphCount += ((format == 1) ? cffData.readCard8() : cffData.readCard16()) + 1;
+ if (gid <= glyphCount) {
+ return start + (gid - oldGlyphCount) - 1;
+ }
+ }
+ }
+
+ public byte[] getHeader() {
+ return header.clone();
+ }
+
+ public CFFIndexData getNameIndex() {
+ return nameIndex;
+ }
+
+ public CFFIndexData getTopDictIndex() {
+ return topDICTIndex;
+ }
+
+ public LinkedHashMap<String, DICTEntry> getTopDictEntries() {
+ return topDict;
+ }
+
+ public CFFIndexData getStringIndex() {
+ return stringIndex;
+ }
+
+ public CFFIndexData getGlobalIndexSubr() {
+ return globalIndexSubr;
+ }
+
+ public CFFIndexData getLocalIndexSubr() {
+ return localIndexSubr;
+ }
+
+ public CFFIndexData getCharStringIndex() {
+ return charStringIndex;
+ }
+
+ public CFFDataInput getCFFData() {
+ return cffData;
+ }
+
+ public CustomEncoding getEncoding() {
+ return encoding;
+ }
+
+ public FDSelect getFDSelect() {
+ return fdSelect;
+ }
+
+ public List<FontDict> getFDFonts() {
+ return fdFonts;
+ }
+
+ public CFFDataInput getLocalSubrsForGlyph(int glyph) throws IOException {
+ //Subsets are currently written using a Format0 FDSelect
+ FDSelect fontDictionary = getFDSelect();
+ if (fontDictionary instanceof Format0FDSelect) {
+ Format0FDSelect fdSelect = (Format0FDSelect)fontDictionary;
+ int found = fdSelect.getFDIndexes()[glyph];
+ FontDict font = getFDFonts().get(found);
+ byte[] localSubrData = font.getLocalSubrData().getByteData();
+ if (localSubrData != null) {
+ return new CFFDataInput(localSubrData);
+ } else {
+ return null;
+ }
+ } else if (fontDictionary instanceof Format3FDSelect) {
+ Format3FDSelect fdSelect = (Format3FDSelect)fontDictionary;
+ int index = 0;
+ for (int first : fdSelect.getRanges().keySet()) {
+ if (first > glyph) {
+ break;
+ }
+ index++;
+ }
+ FontDict font = getFDFonts().get(index);
+ byte[] localSubrsData = font.getLocalSubrData().getByteData();
+ if (localSubrsData != null) {
+ return new CFFDataInput(localSubrsData);
+ } else {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Parses the char string index from the CFF byte data
+ * @param offset The offset to the char string index
+ * @return Returns the char string index object
+ * @throws IOException Throws an IO Exception if an error occurs
+ */
+ public CFFIndexData readCharStringIndex() throws IOException {
+ int offset = topDict.get("CharStrings").getOperands().get(0).intValue();
+ cffData.setPosition(offset);
+ return readIndex();
+ }
+
+ private CustomEncoding readEncoding() throws IOException {
+ CustomEncoding foundEncoding = null;
+ if (topDict.get("Encoding") != null) {
+ int offset = topDict.get("Encoding").getOperands().get(0).intValue();
+ if (offset != 0 && offset != 1) {
+ //No need to set the offset as we are reading the data sequentially.
+ int format = cffData.readCard8();
+ int numEntries = cffData.readCard8();
+ switch (format) {
+ case 0:
+ foundEncoding = readFormat0Encoding(format, numEntries);
+ break;
+ case 1:
+ foundEncoding = readFormat1Encoding(format, numEntries);
+ break;
+ default: break;
+ }
+ }
+ }
+ return foundEncoding;
+ }
+
+ private Format0Encoding readFormat0Encoding(int format, int numEntries)
+ throws IOException {
+ Format0Encoding newEncoding = new Format0Encoding();
+ newEncoding.setFormat(format);
+ newEncoding.setNumEntries(numEntries);
+ int[] codes = new int[numEntries];
+ for (int i = 0; i < numEntries; i++) {
+ codes[i] = cffData.readCard8();
+ }
+ newEncoding.setCodes(codes);
+ return newEncoding;
+ }
+
+ private Format1Encoding readFormat1Encoding(int format, int numEntries)
+ throws IOException {
+ Format1Encoding newEncoding = new Format1Encoding();
+ newEncoding.setFormat(format);
+ newEncoding.setNumEntries(numEntries);
+ LinkedHashMap<Integer, Integer> ranges = new LinkedHashMap<Integer, Integer>();
+ for (int i = 0; i < numEntries; i++) {
+ int first = cffData.readCard8();
+ int left = cffData.readCard8();
+ ranges.put(first, left);
+ }
+ newEncoding.setRanges(ranges);
+ return newEncoding;
+ }
+
+ private FDSelect readFDSelect() throws IOException {
+ FDSelect fdSelect = null;
+ DICTEntry fdSelectEntry = topDict.get("FDSelect");
+ if (fdSelectEntry != null) {
+ int fdOffset = fdSelectEntry.getOperands().get(0).intValue();
+ cffData.setPosition(fdOffset);
+ int format = cffData.readCard8();
+ switch (format) {
+ case 0:
+ fdSelect = readFormat0FDSelect();
+ break;
+ case 3:
+ fdSelect = readFormat3FDSelect();
+ break;
+ default:
+ }
+ }
+ return fdSelect;
+ }
+
+ private Format0FDSelect readFormat0FDSelect() throws IOException {
+ Format0FDSelect newFDs = new Format0FDSelect();
+ newFDs.setFormat(0);
+ int glyphCount = charStringIndex.getNumObjects();
+ int[] fds = new int[glyphCount];
+ for (int i = 0; i < glyphCount; i++) {
+ fds[i] = cffData.readCard8();
+ }
+ newFDs.setFDIndexes(fds);
+ return newFDs;
+ }
+
+ private Format3FDSelect readFormat3FDSelect() throws IOException {
+ Format3FDSelect newFDs = new Format3FDSelect();
+ newFDs.setFormat(3);
+ int rangeCount = cffData.readCard16();
+ newFDs.setRangeCount(rangeCount);
+ LinkedHashMap<Integer, Integer> ranges = new LinkedHashMap<Integer, Integer>();
+ for (int i = 0; i < rangeCount; i++) {
+ int first = cffData.readCard16();
+ int fd = cffData.readCard8();
+ ranges.put(first, fd);
+ }
+ newFDs.setRanges(ranges);
+ newFDs.setSentinelGID(cffData.readCard16());
+ return newFDs;
+ }
+
+ private List<FontDict> parseCIDData() throws IOException {
+ ArrayList<FontDict> fdFonts = new ArrayList<FontDict>();
+ if (topDict.get("ROS") != null) {
+ DICTEntry fdArray = topDict.get("FDArray");
+ if (fdArray != null) {
+ int fdIndex = fdArray.getOperands().get(0).intValue();
+ CFFIndexData fontDicts = readIndex(fdIndex);
+ for (int i = 0; i < fontDicts.getNumObjects(); i++) {
+ FontDict newFontDict = new FontDict();
+
+ byte[] fdData = fontDicts.getValue(i);
+ LinkedHashMap<String, DICTEntry> fdEntries = parseDictData(fdData);
+ newFontDict.setByteData(fontDicts.getValuePosition(i), fontDicts.getValueLength(i));
+ DICTEntry fontFDEntry = fdEntries.get("FontName");
+ newFontDict.setFontName(getString(fontFDEntry.getOperands().get(0).intValue()));
+ DICTEntry privateFDEntry = fdEntries.get("Private");
+ if (privateFDEntry != null) {
+ newFontDict = setFDData(privateFDEntry, newFontDict);
+ }
+
+ fdFonts.add(newFontDict);
+ }
+ }
+ }
+ return fdFonts;
+ }
+
+ private FontDict setFDData(DICTEntry privateFDEntry, FontDict newFontDict) throws IOException {
+ int privateFDLength = privateFDEntry.getOperands().get(0).intValue();
+ int privateFDOffset = privateFDEntry.getOperands().get(1).intValue();
+ cffData.setPosition(privateFDOffset);
+ byte[] privateDict = cffData.readBytes(privateFDLength);
+ newFontDict.setPrivateDictData(privateFDOffset, privateFDLength);
+ LinkedHashMap<String, DICTEntry> privateEntries = parseDictData(privateDict);
+ DICTEntry subroutines = privateEntries.get("Subrs");
+ if (subroutines != null) {
+ CFFIndexData localSubrs = readIndex(privateFDOffset
+ + subroutines.getOperands().get(0).intValue());
+ newFontDict.setLocalSubrData(localSubrs);
+ } else {
+ newFontDict.setLocalSubrData(new CFFIndexData());
+ }
+ return newFontDict;
+ }
+
+ private String getString(int sid) throws IOException {
+ return new String(stringIndex.getValue(sid - NUM_STANDARD_STRINGS));
+ }
+
+ private CFFIndexData readLocalIndexSubrs() throws IOException {
+ CFFIndexData localSubrs = null;
+ DICTEntry privateEntry = topDict.get("Private");
+ if (privateEntry != null) {
+ int length = privateEntry.getOperands().get(0).intValue();
+ int offset = privateEntry.getOperands().get(1).intValue();
+ cffData.setPosition(offset);
+ byte[] privateData = cffData.readBytes(length);
+ LinkedHashMap<String, DICTEntry> privateDict = parseDictData(privateData);
+ DICTEntry localSubrsEntry = privateDict.get("Subrs");
+ if (localSubrsEntry != null) {
+ int localOffset = offset + localSubrsEntry.getOperands().get(0).intValue();
+ cffData.setPosition(localOffset);
+ localSubrs = readIndex();
+ }
+ }
+ return localSubrs;
+ }
+
+ /**
+ * Parent class which provides the ability to retrieve byte data from
+ * a sub-table.
+ */
+ public class CFFSubTable {
+ private DataLocation dataLocation = new DataLocation();
+
+ public void setByteData(int position, int length) {
+ dataLocation = new DataLocation(position, length);
+ }
+
+ public byte[] getByteData() throws IOException {
+ int oldPos = cffData.getPosition();
+ try {
+ cffData.setPosition(dataLocation.getDataPosition());
+ return cffData.readBytes(dataLocation.getDataLength());
+ } finally {
+ cffData.setPosition(oldPos);
+ }
+ }
+ }
+
+ /**
+ * An object used to hold index data from the CFF data
+ */
+ public class CFFIndexData extends CFFSubTable {
+ private int numObjects;
+ private int offSize;
+ private int[] offsets = new int[0];
+ private DataLocation dataLocation = new DataLocation();
+
+ public void setNumObjects(int numObjects) {
+ this.numObjects = numObjects;
+ }
+
+ public int getNumObjects() {
+ return this.numObjects;
+ }
+
+ public void setOffSize(int offSize) {
+ this.offSize = offSize;
+ }
+
+ public int getOffSize() {
+ return this.offSize;
+ }
+
+ public void setOffsets(int[] offsets) {
+ this.offsets = offsets.clone();
+ }
+
+ public int[] getOffsets() {
+ return offsets.clone();
+ }
+
+ public void setData(int position, int length) {
+ dataLocation = new DataLocation(position, length);
+ }
+
+ public byte[] getData() throws IOException {
+ int origPos = cffData.getPosition();
+ try {
+ cffData.setPosition(dataLocation.getDataPosition());
+ return cffData.readBytes(dataLocation.getDataLength());
+ } finally {
+ cffData.setPosition(origPos);
+ }
+ }
+
+ /**
+ * Parses index data from an index object found within the CFF byte data
+ * @param cffData A byte array containing the CFF data
+ * @throws IOException Throws an IO Exception if an error occurs
+ */
+ public void parseIndexHeader(CFFDataInput cffData) throws IOException {
+ setNumObjects(cffData.readCard16());
+ setOffSize(cffData.readOffSize());
+ int[] offsets = new int[getNumObjects() + 1];
+ byte[] bytes;
+ //Fills the offsets array
+ for (int i = 0; i <= getNumObjects(); i++) {
+ switch (getOffSize()) {
+ case 1:
+ offsets[i] = cffData.readCard8();
+ break;
+ case 2:
+ offsets[i] = cffData.readCard16();
+ break;
+ case 3:
+ bytes = cffData.readBytes(3);
+ offsets[i] = ((bytes[0] & 0xFF) << 16) + ((bytes[1] & 0xFF) << 8) + (bytes[2] & 0xFF);
+ break;
+ case 4:
+ bytes = cffData.readBytes(4);
+ offsets[i] = ((bytes[0] & 0xFF) << 24) + ((bytes[1] & 0xFF) << 16)
+ + ((bytes[2] & 0xFF) << 8) + (bytes[3] & 0xFF);
+ break;
+ default: continue;
+ }
+ }
+ setOffsets(offsets);
+ int position = cffData.getPosition();
+ int dataSize = offsets[offsets.length - 1] - offsets[0];
+
+ cffData.setPosition(cffData.getPosition() + dataSize);
+ setData(position, dataSize);
+ }
+
+ /**
+ * Retrieves data from the index data
+ * @param index The index position of the data to retrieve
+ * @return Returns the byte data for the given index
+ * @throws IOException Throws an IO Exception if an error occurs
+ */
+ public byte[] getValue(int index) throws IOException {
+ int oldPos = cffData.getPosition();
+ try {
+ cffData.setPosition(dataLocation.getDataPosition() + (offsets[index] - 1));
+ return cffData.readBytes(offsets[index + 1] - offsets[index]);
+ } finally {
+ cffData.setPosition(oldPos);
+ }
+ }
+
+ public int getValuePosition(int index) {
+ return dataLocation.getDataPosition() + (offsets[index] - 1);
+ }
+
+ public int getValueLength(int index) {
+ return offsets[index + 1] - offsets[index];
+ }
+ }
+
+ public abstract class CustomEncoding {
+ private int format;
+ private int numEntries;
+
+ public void setFormat(int format) {
+ this.format = format;
+ }
+
+ public int getFormat() {
+ return format;
+ }
+
+ public void setNumEntries(int numEntries) {
+ this.numEntries = numEntries;
+ }
+
+ public int getNumEntries() {
+ return numEntries;
+ }
+ }
+
+ public class Format0Encoding extends CustomEncoding {
+ private int[] codes = new int[0];
+
+ public void setCodes(int[] codes) {
+ this.codes = codes.clone();
+ }
+
+ public int[] getCodes() {
+ return codes.clone();
+ }
+ }
+
+ public class Format1Encoding extends CustomEncoding {
+ private LinkedHashMap<Integer, Integer> ranges;
+
+ public void setRanges(LinkedHashMap<Integer, Integer> ranges) {
+ this.ranges = ranges;
+ }
+
+ public LinkedHashMap<Integer, Integer> getRanges() {
+ return ranges;
+ }
+ }
+
+ public class FDSelect {
+ private int format;
+
+ public void setFormat(int format) {
+ this.format = format;
+ }
+
+ public int getFormat() {
+ return format;
+ }
+ }
+
+ public class Format0FDSelect extends FDSelect {
+ private int[] fds = new int[0];
+
+ public void setFDIndexes(int[] fds) {
+ this.fds = fds.clone();
+ }
+
+ public int[] getFDIndexes() {
+ return fds.clone();
+ }
+ }
+
+ public class Format3FDSelect extends FDSelect {
+ private int rangeCount;
+ private LinkedHashMap<Integer, Integer> ranges;
+ private int sentinelGID;
+
+ public void setRangeCount(int rangeCount) {
+ this.rangeCount = rangeCount;
+ }
+
+ public int getRangeCount() {
+ return rangeCount;
+ }
+
+ public void setRanges(LinkedHashMap<Integer, Integer> ranges) {
+ this.ranges = ranges;
+ }
+
+ public LinkedHashMap<Integer, Integer> getRanges() {
+ return ranges;
+ }
+
+ public void setSentinelGID(int sentinelGID) {
+ this.sentinelGID = sentinelGID;
+ }
+
+ public int getSentinelGID() {
+ return sentinelGID;
+ }
+ }
+
+ public class FontDict extends CFFSubTable {
+ private String fontName;
+ private DataLocation dataLocation = new DataLocation();
+ private CFFIndexData localSubrData;
+
+ public void setFontName(String groupName) {
+ this.fontName = groupName;
+ }
+
+ public String getFontName() {
+ return fontName;
+ }
+
+ public void setPrivateDictData(int position, int length) {
+ dataLocation = new DataLocation(position, length);
+ }
+
+ public byte[] getPrivateDictData() throws IOException {
+ int origPos = cffData.getPosition();
+ try {
+ cffData.setPosition(dataLocation.getDataPosition());
+ return cffData.readBytes(dataLocation.getDataLength());
+ } finally {
+ cffData.setPosition(origPos);
+ }
+ }
+
+ public void setLocalSubrData(CFFIndexData localSubrData) {
+ this.localSubrData = localSubrData;
+ }
+
+ public CFFIndexData getLocalSubrData() {
+ return localSubrData;
+ }
+ }
+
+ private static class DataLocation {
+ private int dataPosition;
+ private int dataLength;
+
+ public DataLocation() {
+ dataPosition = 0;
+ dataLength = 0;
+ }
+
+ public DataLocation(int position, int length) {
+ this.dataPosition = position;
+ this.dataLength = length;
+ }
+
+ public int getDataPosition() {
+ return dataPosition;
+ }
+
+ public int getDataLength() {
+ return dataLength;
+ }
+ }
+}