FOP-2557: Branch OTFSubSetFile to avoid incompatibility with new fop version

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop-pdf-images/branches/Temp_PDFBox2@1738813 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/java/org/apache/fop/render/pdf/pdfbox/MergeCFFFonts.java b/src/java/org/apache/fop/render/pdf/pdfbox/MergeCFFFonts.java
index 34a7157..ae86528 100644
--- a/src/java/org/apache/fop/render/pdf/pdfbox/MergeCFFFonts.java
+++ b/src/java/org/apache/fop/render/pdf/pdfbox/MergeCFFFonts.java
@@ -42,7 +42,6 @@
 
 import org.apache.fop.fonts.cff.CFFDataReader;
 import org.apache.fop.fonts.truetype.FontFileReader;
-import org.apache.fop.fonts.truetype.OTFSubSetFile;
 
 public class MergeCFFFonts extends OTFSubSetFile implements MergeFonts {
     protected List<LinkedHashMap<Integer, Integer>> subsetGlyphsList = new ArrayList<LinkedHashMap<Integer, Integer>>();
@@ -219,12 +218,10 @@
             writeBytes(fontFile.getAllBytes());
             return super.getFontSubset();
         }
-        subsetGlyphs = subsetGlyphsList.get(0);
         createCFF();
         return super.getFontSubset();
     }
 
-    @Override
     protected void createCFF() throws IOException {
         //Header
         writeBytes(cffReader.getHeader());
@@ -382,32 +379,13 @@
     }
 
     protected void createCharStringData() throws IOException {
-        //Create the new char string index
-        for (int i = 0; i < subsetGlyphsList.size(); i++) {
-            Map<String, CFFDataReader.DICTEntry> topDICT = cffReader.getTopDictEntries();
-            final CFFDataReader.DICTEntry privateEntry = topDICT.get("Private");
-            if (privateEntry != null) {
-                int privateOffset = privateEntry.getOperands().get(1).intValue();
-                Map<String, CFFDataReader.DICTEntry> privateDICT = cffReader.getPrivateDict(privateEntry);
-
-                if (privateDICT.containsKey("Subrs")) {
-                    int localSubrOffset = privateOffset + privateDICT.get("Subrs").getOperands().get(0).intValue();
-                    localIndexSubr = cffReader.readIndex(localSubrOffset);
-                }
-            }
-
-            globalIndexSubr = cffReader.getGlobalIndexSubr();
-        }
         //Create the two lists which are to store the local and global subroutines
         subsetLocalIndexSubr = new ArrayList<byte[]>();
-        subsetGlobalIndexSubr = new ArrayList<byte[]>();
 
         localUniques = new ArrayList<Integer>();
         globalUniques = new ArrayList<Integer>();
 
         //Store the size of each subset index and clear the unique arrays
-        subsetLocalSubrCount = localUniques.size();
-        subsetGlobalSubrCount = globalUniques.size();
         localUniques.clear();
         globalUniques.clear();
     }
diff --git a/src/java/org/apache/fop/render/pdf/pdfbox/OTFSubSetFile.java b/src/java/org/apache/fop/render/pdf/pdfbox/OTFSubSetFile.java
new file mode 100644
index 0000000..d8ca034
--- /dev/null
+++ b/src/java/org/apache/fop/render/pdf/pdfbox/OTFSubSetFile.java
@@ -0,0 +1,419 @@
+/*
+ * 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.render.pdf.pdfbox;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fop.fonts.cff.CFFDataReader;
+import org.apache.fop.fonts.cff.CFFDataReader.DICTEntry;
+import org.apache.fop.fonts.truetype.OTFFile;
+
+/**
+ * Reads an OpenType CFF file and generates a subset
+ * The OpenType specification can be found at the Microsoft
+ * Typography site: http://www.microsoft.com/typography/otspec/
+ */
+public abstract class OTFSubSetFile extends OTFFile {
+
+    protected byte[] output;
+    protected int currentPos;
+    private int realSize;
+
+    /** A map of the new GID to SID used to construct the charset table **/
+    protected LinkedHashMap<Integer, Integer> gidToSID;
+
+    /** List of subroutines to write to the local / global indexes in the subset font **/
+    protected List<byte[]> subsetLocalIndexSubr;
+
+
+    /** A list of unique subroutines from the global / local subroutine indexes */
+    protected List<Integer> localUniques;
+    protected List<Integer> globalUniques;
+
+    /** A list of char string data for each glyph to be stored in the subset font **/
+    protected List<byte[]> subsetCharStringsIndex;
+
+    /** The embedded name to change in the name table **/
+    protected String embeddedName;
+
+    /** An array used to hold the string index data for the subset font **/
+    protected List<byte[]> stringIndexData = new ArrayList<byte[]>();
+
+    /** The CFF reader object used to read data and offsets from the original font file */
+    protected CFFDataReader cffReader;
+
+    /** The number of standard strings in CFF **/
+    public static final int NUM_STANDARD_STRINGS = 391;
+    /** The operator used to identify a local subroutine reference */
+    private static final int LOCAL_SUBROUTINE = 10;
+    /** The operator used to identify a global subroutine reference */
+    private static final int GLOBAL_SUBROUTINE = 29;
+
+    public OTFSubSetFile() throws IOException {
+        super();
+    }
+
+    protected void writeBytes(byte[] out) {
+        for (byte anOut : out) {
+            writeByte(anOut);
+        }
+    }
+
+    protected void writeBytes(byte[] out, int offset, int length) {
+        for (int i = offset; i < offset + length; i++) {
+            output[currentPos++] = out[i];
+            realSize++;
+        }
+    }
+
+    protected void writeTopDICT() throws IOException {
+        LinkedHashMap<String, DICTEntry> topDICT = cffReader.getTopDictEntries();
+        List<String> topDictStringEntries = Arrays.asList("version", "Notice", "Copyright",
+                "FullName", "FamilyName", "Weight", "PostScript");
+        for (Map.Entry<String, DICTEntry> dictEntry : topDICT.entrySet()) {
+            String dictKey = dictEntry.getKey();
+            DICTEntry entry = dictEntry.getValue();
+            //If the value is an SID, update the reference but keep the size the same
+            if (dictKey.equals("ROS")) {
+                writeROSEntry(entry);
+            } else if (dictKey.equals("CIDCount")) {
+                writeCIDCount(entry);
+            } else if (topDictStringEntries.contains(dictKey)) {
+                writeTopDictStringEntry(entry);
+            } else {
+                writeBytes(entry.getByteData());
+            }
+        }
+    }
+
+    private void writeROSEntry(DICTEntry dictEntry) throws IOException {
+        int sidA = dictEntry.getOperands().get(0).intValue();
+        if (sidA > 390) {
+            stringIndexData.add(cffReader.getStringIndex().getValue(sidA - NUM_STANDARD_STRINGS));
+        }
+        int sidAStringIndex = stringIndexData.size() + 390;
+        int sidB = dictEntry.getOperands().get(1).intValue();
+        if (sidB > 390) {
+            stringIndexData.add("Identity".getBytes("UTF-8"));
+        }
+        int sidBStringIndex = stringIndexData.size() + 390;
+        byte[] cidEntryByteData = dictEntry.getByteData();
+        cidEntryByteData = updateOffset(cidEntryByteData, 0, dictEntry.getOperandLengths().get(0),
+                sidAStringIndex);
+        cidEntryByteData = updateOffset(cidEntryByteData, dictEntry.getOperandLengths().get(0),
+                dictEntry.getOperandLengths().get(1), sidBStringIndex);
+        cidEntryByteData = updateOffset(cidEntryByteData, dictEntry.getOperandLengths().get(0)
+                + dictEntry.getOperandLengths().get(1), dictEntry.getOperandLengths().get(2), 139);
+        writeBytes(cidEntryByteData);
+    }
+
+    protected abstract void writeCIDCount(DICTEntry dictEntry) throws IOException;
+
+    private void writeTopDictStringEntry(DICTEntry dictEntry) throws IOException {
+        int sid = dictEntry.getOperands().get(0).intValue();
+        if (sid > 391) {
+            stringIndexData.add(cffReader.getStringIndex().getValue(sid - 391));
+        }
+
+        byte[] newDictEntry = createNewRef(stringIndexData.size() + 390, dictEntry.getOperator(),
+                dictEntry.getOperandLength());
+        writeBytes(newDictEntry);
+    }
+
+    public static byte[] createNewRef(int newRef, int[] operatorCode, int forceLength) {
+        byte[] newRefBytes;
+        int sizeOfOperator = operatorCode.length;
+        if ((forceLength == -1 && newRef <= 107) || forceLength == 1) {
+            newRefBytes = new byte[1 + sizeOfOperator];
+            //The index values are 0 indexed
+            newRefBytes[0] = (byte)(newRef + 139);
+            for (int i = 0; i < operatorCode.length; i++) {
+                newRefBytes[1 + i] = (byte)operatorCode[i];
+            }
+        } else if ((forceLength == -1 && newRef <= 1131) || forceLength == 2) {
+            newRefBytes = new byte[2 + sizeOfOperator];
+            if (newRef <= 363) {
+                newRefBytes[0] = (byte)247;
+            } else if (newRef <= 619) {
+                newRefBytes[0] = (byte)248;
+            } else if (newRef <= 875) {
+                newRefBytes[0] = (byte)249;
+            } else {
+                newRefBytes[0] = (byte)250;
+            }
+            newRefBytes[1] = (byte)(newRef - 108);
+            for (int i = 0; i < operatorCode.length; i++) {
+                newRefBytes[2 + i] = (byte)operatorCode[i];
+            }
+        } else if ((forceLength == -1 && newRef <= 32767) || forceLength == 3) {
+            newRefBytes = new byte[3 + sizeOfOperator];
+            newRefBytes[0] = 28;
+            newRefBytes[1] = (byte)(newRef >> 8);
+            newRefBytes[2] = (byte)newRef;
+            for (int i = 0; i < operatorCode.length; i++) {
+                newRefBytes[3 + i] = (byte)operatorCode[i];
+            }
+        } else {
+            newRefBytes = new byte[5 + sizeOfOperator];
+            newRefBytes[0] = 29;
+            newRefBytes[1] = (byte)(newRef >> 24);
+            newRefBytes[2] = (byte)(newRef >> 16);
+            newRefBytes[3] = (byte)(newRef >> 8);
+            newRefBytes[4] = (byte)newRef;
+            for (int i = 0; i < operatorCode.length; i++) {
+                newRefBytes[5 + i] = (byte)operatorCode[i];
+            }
+        }
+        return newRefBytes;
+    }
+
+    protected int writeIndex(List<byte[]> dataArray) {
+        int hdrTotal = 3;
+        //2 byte number of items
+        this.writeCard16(dataArray.size());
+        //Offset Size: 1 byte = 256, 2 bytes = 65536 etc.
+        int totLength = 0;
+        for (byte[] aDataArray1 : dataArray) {
+            totLength += aDataArray1.length;
+        }
+        int offSize = 1;
+        if (totLength <= (1 << 8)) {
+            offSize = 1;
+        } else if (totLength <= (1 << 16)) {
+            offSize = 2;
+        } else if (totLength <= (1 << 24)) {
+            offSize = 3;
+        } else {
+            offSize = 4;
+        }
+        this.writeByte(offSize);
+        //Count the first offset 1
+        hdrTotal += offSize;
+        int total = 0;
+        for (int i = 0; i < dataArray.size(); i++) {
+            hdrTotal += offSize;
+            int length = dataArray.get(i).length;
+            switch (offSize) {
+                case 1:
+                    if (i == 0) {
+                        writeByte(1);
+                    }
+                    total += length;
+                    writeByte(total + 1);
+                    break;
+                case 2:
+                    if (i == 0) {
+                        writeCard16(1);
+                    }
+                    total += length;
+                    writeCard16(total + 1);
+                    break;
+                case 3:
+                    if (i == 0) {
+                        writeThreeByteNumber(1);
+                    }
+                    total += length;
+                    writeThreeByteNumber(total + 1);
+                    break;
+                case 4:
+                    if (i == 0) {
+                        writeULong(1);
+                    }
+                    total += length;
+                    writeULong(total + 1);
+                    break;
+                default:
+                    throw new AssertionError("Offset Size was not an expected value.");
+            }
+        }
+        for (byte[] aDataArray : dataArray) {
+            writeBytes(aDataArray);
+        }
+        return hdrTotal + total;
+    }
+
+    protected void writePrivateDict() throws IOException {
+        Map<String, DICTEntry> topDICT = cffReader.getTopDictEntries();
+
+        DICTEntry privateEntry = topDICT.get("Private");
+        if (privateEntry != null) {
+            writeBytes(cffReader.getPrivateDictBytes(privateEntry));
+        }
+    }
+
+    protected void updateOffsets(int topDictOffset, int charsetOffset, int charStringOffset,
+                                 int privateDictOffset, int localIndexOffset, int encodingOffset) throws IOException {
+        Map<String, DICTEntry> topDICT = cffReader.getTopDictEntries();
+        Map<String, DICTEntry> privateDICT = null;
+
+        DICTEntry privateEntry = topDICT.get("Private");
+        if (privateEntry != null) {
+            privateDICT = cffReader.getPrivateDict(privateEntry);
+        }
+
+        int dataPos = 3 + (cffReader.getTopDictIndex().getOffSize()
+                * cffReader.getTopDictIndex().getOffsets().length);
+        int dataTopDictOffset = topDictOffset + dataPos;
+
+        updateFixedOffsets(topDICT, dataTopDictOffset, charsetOffset, charStringOffset, encodingOffset);
+
+        if (privateDICT != null) {
+            //Private index offset in the top dict
+            int oldPrivateOffset = dataTopDictOffset + privateEntry.getOffset();
+            output = updateOffset(output, oldPrivateOffset + privateEntry.getOperandLengths().get(0),
+                    privateEntry.getOperandLengths().get(1), privateDictOffset);
+
+            //Update the local subroutine index offset in the private dict
+            DICTEntry subroutines = privateDICT.get("Subrs");
+            if (subroutines != null) {
+                int oldLocalSubrOffset = privateDictOffset + subroutines.getOffset();
+                //Value needs to be converted to -139 etc.
+                int encodeValue = 0;
+                if (subroutines.getOperandLength() == 1) {
+                    encodeValue = 139;
+                }
+                output = updateOffset(output, oldLocalSubrOffset, subroutines.getOperandLength(),
+                        (localIndexOffset - privateDictOffset) + encodeValue);
+            }
+        }
+    }
+
+    protected abstract void updateFixedOffsets(Map<String, DICTEntry> topDICT, int dataTopDictOffset,
+                                               int charsetOffset, int charStringOffset, int encodingOffset);
+
+    protected void updateCIDOffsets(int topDictDataOffset, int fdArrayOffset, int fdSelectOffset,
+                                    int charsetOffset, int charStringOffset, int encodingOffset) {
+        LinkedHashMap<String, DICTEntry> topDict = cffReader.getTopDictEntries();
+
+        DICTEntry fdArrayEntry = topDict.get("FDArray");
+        if (fdArrayEntry != null) {
+            output = updateOffset(output, topDictDataOffset + fdArrayEntry.getOffset() - 1,
+                    fdArrayEntry.getOperandLength(), fdArrayOffset);
+        }
+
+        DICTEntry fdSelect = topDict.get("FDSelect");
+        if (fdSelect != null) {
+            output = updateOffset(output, topDictDataOffset + fdSelect.getOffset() - 1,
+                    fdSelect.getOperandLength(), fdSelectOffset);
+        }
+
+        updateFixedOffsets(topDict, topDictDataOffset, charsetOffset, charStringOffset, encodingOffset);
+    }
+
+    protected byte[] updateOffset(byte[] out, int position, int length, int replacement) {
+        switch (length) {
+            case 1:
+                out[position] = (byte)(replacement & 0xFF);
+                break;
+            case 2:
+                if (replacement <= 363) {
+                    out[position] = (byte)247;
+                } else if (replacement <= 619) {
+                    out[position] = (byte)248;
+                } else if (replacement <= 875) {
+                    out[position] = (byte)249;
+                } else {
+                    out[position] = (byte)250;
+                }
+                out[position + 1] = (byte)(replacement - 108);
+                break;
+            case 3:
+                out[position] = (byte)28;
+                out[position + 1] = (byte)((replacement >> 8) & 0xFF);
+                out[position + 2] = (byte)(replacement & 0xFF);
+                break;
+            case 5:
+                out[position] = (byte)29;
+                out[position + 1] = (byte)((replacement >> 24) & 0xFF);
+                out[position + 2] = (byte)((replacement >> 16) & 0xFF);
+                out[position + 3] = (byte)((replacement >> 8) & 0xFF);
+                out[position + 4] = (byte)(replacement & 0xFF);
+                break;
+            default:
+        }
+        return out;
+    }
+
+    /**
+     * Appends a byte to the output array,
+     * updates currentPost but not realSize
+     *
+     * @param b b
+     */
+    protected void writeByte(int b) {
+        output[currentPos++] = (byte)b;
+        realSize++;
+    }
+
+    /**
+     * Appends a USHORT to the output array,
+     * updates currentPost but not realSize
+     *
+     * @param s s
+     */
+    protected void writeCard16(int s) {
+        byte b1 = (byte)((s >> 8) & 0xff);
+        byte b2 = (byte)(s & 0xff);
+        writeByte(b1);
+        writeByte(b2);
+    }
+
+    private void writeThreeByteNumber(int s) {
+        byte b1 = (byte)((s >> 16) & 0xFF);
+        byte b2 = (byte)((s >> 8) & 0xFF);
+        byte b3 = (byte)(s & 0xFF);
+        writeByte(b1);
+        writeByte(b2);
+        writeByte(b3);
+    }
+
+    /**
+     * Appends a ULONG to the output array,
+     * at the given position
+     *
+     * @param s s
+     */
+    private void writeULong(int s) {
+        byte b1 = (byte)((s >> 24) & 0xff);
+        byte b2 = (byte)((s >> 16) & 0xff);
+        byte b3 = (byte)((s >> 8) & 0xff);
+        byte b4 = (byte)(s & 0xff);
+        writeByte(b1);
+        writeByte(b2);
+        writeByte(b3);
+        writeByte(b4);
+    }
+
+    /**
+     * Returns a subset of the fonts (readFont() MUST be called first in order to create the
+     * subset).
+     * @return byte array
+     */
+    public byte[] getFontSubset() {
+        byte[] ret = new byte[realSize];
+        System.arraycopy(output, 0, ret, 0, realSize);
+        return ret;
+    }
+}