From: Luis Bernardo Date: Wed, 14 Aug 2013 22:42:28 +0000 (+0000) Subject: FOP-2252: OpenType CFF support for FOP; patch submitted by Robert Meyer (eclipse... X-Git-Tag: fop-2_0~189 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=99e4e25414066866d0f8b3632623576246ae080b;p=xmlgraphics-fop.git FOP-2252: OpenType CFF support for FOP; patch submitted by Robert Meyer (eclipse misbehaved and missed a file) git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1514079 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/java/org/apache/fop/fonts/cff/CFFDataReader.java b/src/java/org/apache/fop/fonts/cff/CFFDataReader.java new file mode 100644 index 000000000..66126fb84 --- /dev/null +++ b/src/java/org/apache/fop/fonts/cff/CFFDataReader.java @@ -0,0 +1,927 @@ +/* + * 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 fdFonts; + + private static final int DOUBLE_BYTE_OPERATOR = 12; + private static final int NUM_STANDARD_STRINGS = 391; + + /** Commonly used parsed dictionaries */ + private LinkedHashMap 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 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 parseDictData(byte[] dictData) throws IOException { + LinkedHashMap dictEntries = new LinkedHashMap(); + List operands = new ArrayList(); + List operandLengths = new ArrayList(); + 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(operands)); + newEntry.setOperatorName(operatorName); + newEntry.setOffset(i - lastOperandLength); + newEntry.setOperandLength(lastOperandLength); + newEntry.setOperandLengths(new ArrayList(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 operands; + private List 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 operands) { + this.operands = operands; + } + + public List 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 operandLengths) { + this.operandLengths = operandLengths; + } + + public List 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 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 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 ranges = new LinkedHashMap(); + 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 ranges = new LinkedHashMap(); + 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 parseCIDData() throws IOException { + ArrayList fdFonts = new ArrayList(); + 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 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 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 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 ranges; + + public void setRanges(LinkedHashMap ranges) { + this.ranges = ranges; + } + + public LinkedHashMap 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 ranges; + private int sentinelGID; + + public void setRangeCount(int rangeCount) { + this.rangeCount = rangeCount; + } + + public int getRangeCount() { + return rangeCount; + } + + public void setRanges(LinkedHashMap ranges) { + this.ranges = ranges; + } + + public LinkedHashMap 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; + } + } +} diff --git a/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java b/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java index 8fd8d3444..2fe9f29e0 100644 --- a/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java +++ b/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java @@ -449,13 +449,14 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { private class Rev5Engine extends InitializationEngine { - protected byte[] userValidationSalt = new byte[8]; - protected byte[] userKeySalt = new byte[8]; - protected byte[] ownerValidationSalt = new byte[8]; - protected byte[] ownerKeySalt = new byte[8]; - protected byte[] ueValue; - protected byte[] oeValue; - protected final boolean encryptMetadata; + // private SecureRandom random = new SecureRandom(); + private byte[] userValidationSalt = new byte[8]; + private byte[] userKeySalt = new byte[8]; + private byte[] ownerValidationSalt = new byte[8]; + private byte[] ownerKeySalt = new byte[8]; + private byte[] ueValue; + private byte[] oeValue; + private final boolean encryptMetadata; Rev5Engine(EncryptionSettings encryptionSettings) { super(encryptionSettings); @@ -562,7 +563,7 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { /** * Algorithm 3.8-2 (page 20, Adobe Supplement to the ISO 32000, BaseVersion: 1.7, ExtensionLevel: 3) */ - protected void computeUEValue() { + private void computeUEValue() { digest.reset(); byte[] prepared = preparedUserPassword; byte[] concatenated = new byte[prepared.length + 8]; @@ -576,7 +577,7 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { /** * Algorithm 3.9-2 (page 20, Adobe Supplement to the ISO 32000, BaseVersion: 1.7, ExtensionLevel: 3) */ - protected void computeOEValue() { + private void computeOEValue() { digest.reset(); byte[] prepared = preparedOwnerPassword; byte[] concatenated = new byte[prepared.length + 56]; @@ -614,86 +615,6 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { } } - private class Rev6Engine extends Rev5Engine { - - private MessageDigest digest384; - private MessageDigest digest512; - - Rev6Engine(EncryptionSettings encryptionSettings) { - super(encryptionSettings); - try { - digest384 = MessageDigest.getInstance("SHA-384"); - digest512 = MessageDigest.getInstance("SHA-512"); - } catch (NoSuchAlgorithmException e) { - throw new UnsupportedOperationException(e.getMessage()); - } - } - - @Override - protected void computeUValue() { - byte[] userBytes = new byte[16]; - random.nextBytes(userBytes); - System.arraycopy(userBytes, 0, userValidationSalt, 0, 8); - System.arraycopy(userBytes, 8, userKeySalt, 0, 8); - digest.reset(); - byte[] prepared = preparedUserPassword; - byte[] concatenated = new byte[prepared.length + 8]; - System.arraycopy(prepared, 0, concatenated, 0, prepared.length); - System.arraycopy(userValidationSalt, 0, concatenated, prepared.length, 8); - digest.update(concatenated); - byte[] block = digest.digest(); - int blockSize = 32; - byte[] key = new byte[16]; - byte[] iv = new byte[16]; - int length = prepared.length + blockSize; - byte[] data = new byte[length * 64]; - for (int i = 0; i < 64 || i < data[length * 64 - 1] + 32; i++) { - System.arraycopy(block, 0, key, 0, 16); - System.arraycopy(block, 16, iv, 0, 16); - for (int j = 0; j < 64; j++) { - System.arraycopy(prepared, 0, data, j * length, prepared.length); - System.arraycopy(block, 0, data, j * length + prepared.length, blockSize); - } - try { - final Cipher cipher = PDFEncryptionJCE.initCipher(key, false, iv); - data = cipher.doFinal(data); - } catch (IllegalBlockSizeException e) { - throw new IllegalStateException(e.getMessage()); - } catch (BadPaddingException e) { - throw new IllegalStateException(e.getMessage()); - } - int sum = 0; - for (int k = 0; k < 16; k++) { - sum += data[k]; - } - blockSize = 32 + (sum % 3) * 16; - switch (blockSize) { - case 32: - digest.reset(); - digest.update(data); - block = digest.digest(); - break; - case 48: - digest384.reset(); - digest384.update(data); - block = digest384.digest(); - break; - case 64: - digest512.reset(); - digest512.update(data); - block = digest512.digest(); - break; - default: - // not possible - break; - } - length = prepared.length + blockSize; - data = new byte[length * 64]; - } - } - - } - private class EncryptionFilter extends PDFFilter { private int streamNumber;