| /* |
| * 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"); |
| if (fontFDEntry != null) { |
| 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; |
| } |
| } |
| } |