You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ja...@apache.org on 2014/12/16 00:54:56 UTC

svn commit: r1645796 [1/2] - in /pdfbox/trunk: examples/src/main/java/org/apache/pdfbox/examples/pdmodel/ fontbox/src/main/java/org/apache/fontbox/ttf/ pdfbox/src/main/java/org/apache/pdfbox/pdmodel/edit/ pdfbox/src/main/java/org/apache/pdfbox/pdmodel/...

Author: jahewson
Date: Mon Dec 15 23:54:55 2014
New Revision: 1645796

URL: http://svn.apache.org/r1645796
Log:
PDFBOX-2565: Subsetting of embedded Type0 TrueType fonts

Added:
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/Subsetter.java   (with props)
Modified:
    pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/HelloWorldTTF.java
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/CmapTable.java
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/PostScriptTable.java
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFParser.java
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubsetter.java
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TrueTypeFont.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/edit/PDPageContentStream.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2Embedder.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFont.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDSimpleFont.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDTrueTypeFont.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDTrueTypeFontEmbedder.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType0Font.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/TrueTypeEmbedder.java
    pdfbox/trunk/tools/src/main/java/org/apache/pdfbox/tools/TextToPDF.java

Modified: pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/HelloWorldTTF.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/HelloWorldTTF.java?rev=1645796&r1=1645795&r2=1645796&view=diff
==============================================================================
--- pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/HelloWorldTTF.java (original)
+++ pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/HelloWorldTTF.java Mon Dec 15 23:54:55 2014
@@ -25,6 +25,7 @@ import org.apache.pdfbox.pdmodel.PDPage;
 import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
 import org.apache.pdfbox.pdmodel.font.PDFont;
 import org.apache.pdfbox.pdmodel.font.PDTrueTypeFont;
+import org.apache.pdfbox.pdmodel.font.PDType0Font;
 
 /**
  * This is an example that creates a simple document
@@ -56,7 +57,7 @@ public class HelloWorldTTF
 
             PDPage page = new PDPage();
             doc.addPage(page);
-            PDFont font = PDTrueTypeFont.loadTTF(doc, new File(fontfile));
+            PDFont font = PDType0Font.load(doc, new File(fontfile));
 
             PDPageContentStream contentStream = new PDPageContentStream(doc,
                     page);

Modified: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/CmapTable.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/CmapTable.java?rev=1645796&r1=1645795&r2=1645796&view=diff
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/CmapTable.java (original)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/CmapTable.java Mon Dec 15 23:54:55 2014
@@ -40,12 +40,13 @@ public class CmapTable extends TTFTable
 
     // Windows encodings
     public static final int ENCODING_WIN_SYMBOL = 0; // Unicode, non-standard character set
-    public static final int ENCODING_WIN_UNICODE = 1;
+    public static final int ENCODING_WIN_UNICODE_BMP = 1; // Unicode BMP (UCS-2)
     public static final int ENCODING_WIN_SHIFT_JIS = 2;
     public static final int ENCODING_WIN_BIG5 = 3;
     public static final int ENCODING_WIN_PRC = 4;
     public static final int ENCODING_WIN_WANSUNG = 5;
     public static final int ENCODING_WIN_JOHAB = 6;
+    public static final int ENCODING_WIN_UNICODE_FULL = 10; // Unicode Full (UCS-4)
 
     // Unicode encodings
     public static final int ENCODING_UNICODE_1_0 = 0;

Modified: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/PostScriptTable.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/PostScriptTable.java?rev=1645796&r1=1645795&r2=1645796&view=diff
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/PostScriptTable.java (original)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/PostScriptTable.java Mon Dec 15 23:54:55 2014
@@ -225,7 +225,7 @@ public class PostScriptTable extends TTF
     /**
      * @return Returns the mimMemType1.
      */
-    public long getMimMemType1()
+    public long getMinMemType1()
     {
         return mimMemType1;
     }
@@ -307,7 +307,7 @@ public class PostScriptTable extends TTF
      */
     public String getName(int gid)
     {
-        if (gid < 0 || gid > glyphNames.length)
+        if (gid < 0 || glyphNames == null || gid > glyphNames.length)
         {
             return null;
         }

Modified: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFParser.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFParser.java?rev=1645796&r1=1645795&r2=1645796&view=diff
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFParser.java (original)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFParser.java Mon Dec 15 23:54:55 2014
@@ -99,6 +99,19 @@ public class TTFParser
     /**
      * Parse a file and get a true type font.
      *
+     * @param ttfData The TTF data to parse.
+     * @return A true type font.
+     * @throws IOException If there is an error parsing the true type font.
+     */
+    public TrueTypeFont parseEmbedded(InputStream ttfData) throws IOException
+    {
+        this.isEmbedded = true;
+        return parse(new MemoryTTFDataStream(ttfData));
+    }
+
+    /**
+     * Parse a file and get a true type font.
+     *
      * @param raf The TTF file.
      * @return A true type font.
      * @throws IOException If there is an error parsing the true type font.

Modified: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubsetter.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubsetter.java?rev=1645796&r1=1645795&r2=1645796&view=diff
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubsetter.java (original)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubsetter.java Mon Dec 15 23:54:55 2014
@@ -22,7 +22,7 @@ import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.ArrayList;
+import java.nio.charset.Charset;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
 import java.util.HashMap;
@@ -36,1008 +36,904 @@ import java.util.SortedSet;
 import java.util.TreeMap;
 import java.util.TreeSet;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
 /**
- * A font, which is comprised of a subset of characters of a TrueType font.
- * Based on code developed by Wolfgang Glas
- * http://svn.clazzes.org/svn/sketch/trunk/pdf/pdf-entities/src/main/java/org/clazzes/sketch/pdf/entities/impl/TTFSubFont.java
+ * Subsetter for TrueType (TTF) fonts.
+ *
+ * <p>Originally developed by Wolfgang Glas for
+ * <a href="https://clazzes.org/display/SKETCH/Clazzes.org+Sketch+Home">Sketch</a>.
+ *
+ * @author Wolfgang Glas
  */
-public class TTFSubsetter
+public final class TTFSubsetter
 {
-    private static final Log LOG = LogFactory.getLog(TTFSubsetter.class);
-    private static final byte[] PAD_BUF = new byte[] {0,0,0};
-    
-    private final TrueTypeFont baseTTF;
-    private final String nameSuffix;
-    private final CmapSubtable baseCmap;
-    
-    // A map of unicode char codes to glyph IDs of the original font.
-    private final SortedMap<Integer,Integer> characters;
-    // A sorted version of this set will comprise the generated glyph IDs
-    // for the written truetype font.
-    private final SortedSet<Integer> glyphIds;
-    
+    private static final byte[] PAD_BUF = new byte[] { 0, 0, 0 };
+
+    private final TrueTypeFont ttf;
+    private final CmapSubtable unicodeCmap;
+    private final SortedMap<Integer, Integer> uniToGID;
+
+    private final List<String> keepTables;
+    private final SortedSet<Integer> glyphIds; // new glyph ids
+    private String prefix;
+    private boolean hasAddedCompoundReferences;
+
     /**
-     * Constructs a subfont based on the given font using the given suffix.
-     * 
-     * @param baseFont the base font of the subfont
-     * @param suffix suffix used for the naming
-     * 
+     * Creates a subsetter for the given font.
+     *
+     * @param ttf the font to be subset
      */
-    public TTFSubsetter(TrueTypeFont baseFont, String suffix) throws IOException
+    public TTFSubsetter(TrueTypeFont ttf) throws IOException
     {
-        baseTTF = baseFont;
-        nameSuffix = suffix;
-        characters = new TreeMap<Integer, Integer>();
-        glyphIds = new TreeSet<Integer>();
-        
-        CmapSubtable[] cmaps = this.baseTTF.getCmap().getCmaps();
-        CmapSubtable unicodeCmap = null;
-        
-        for (CmapSubtable cmap : cmaps)
-        {
-            // take first unicode map.
-            if (cmap.getPlatformId() == 0 || (cmap.getPlatformId() == 3 && cmap.getPlatformEncodingId() == 1)) 
-            {
-                unicodeCmap = cmap;
-                break;
-            }
-        }
-        baseCmap = unicodeCmap;
-        // add notdef character.
-        addCharCode(0);
+        this(ttf, null);
     }
-    
+
     /**
-     * Add the given charcode to the subfpont.
-     * 
-     * @param charCode the charCode to be added
+     * Creates a subsetter for the given font.
      * 
+     * @param ttf the font to be subset
+     * @param tables optional tables to keep if present
      */
-    public void addCharCode(int charCode) 
+    public TTFSubsetter(TrueTypeFont ttf, List<String> tables) throws IOException
     {
-        Integer gid = baseCmap.getGlyphId(charCode);
-        if (charCode == 0 || gid != 0) 
-        {
-            characters.put(charCode,gid);
-            glyphIds.add(gid);
-        }
+        this.ttf = ttf;
+        this.keepTables = tables;
+
+        uniToGID = new TreeMap<Integer, Integer>();
+        glyphIds = new TreeSet<Integer>();
+
+        // find the best Unicode cmap
+        this.unicodeCmap = getUnicodeCmap(ttf.getCmap());
+
+        // always copy GID 0
+        glyphIds.add(0);
     }
 
-    private static int log2i(int i) 
+    /**
+     * Returns the best Unicode from the font (the most general).
+     */
+    private CmapSubtable getUnicodeCmap(CmapTable cmapTable) throws IOException
     {
-        int ret = -1;
-        if ((i & 0xffff0000) != 0) 
+        CmapSubtable cmap = cmapTable.getSubtable(CmapTable.PLATFORM_UNICODE,
+                CmapTable.ENCODING_UNICODE_2_0_FULL);
+        if (cmap == null)
         {
-            i >>>= 16;
-            ret += 16;
+            cmap = cmapTable.getSubtable(CmapTable.PLATFORM_UNICODE,
+                    CmapTable.ENCODING_UNICODE_2_0_BMP);
         }
-        if ((i & 0xff00) != 0) 
+        if (cmap == null)
         {
-            i >>>= 8;
-            ret += 8;
+            cmap = cmapTable.getSubtable(CmapTable.PLATFORM_WINDOWS,
+                    CmapTable.ENCODING_WIN_UNICODE_BMP);
         }
-        if ((i & 0xf0) != 0) 
+        if (cmap == null)
         {
-            i >>>= 4;
-            ret += 4;
+            // Microsoft's "Recommendations for OpenType Fonts" says that "Symbol" encoding
+            // actually means "Unicode, non-standard character set"
+            cmap = cmapTable.getSubtable(CmapTable.PLATFORM_WINDOWS,
+                    CmapTable.ENCODING_WIN_SYMBOL);
         }
-        if ((i & 0xc) != 0) 
+        if (cmap == null)
         {
-            i >>>= 2;
-            ret += 2;
+            throw new IOException("The TrueType font does not contain a Unicode cmap");
         }
-        if ((i & 0x2) != 0) 
-        {
-            i >>>= 1;
-            ++ret;
-        }
-        if (i != 0) 
+        return cmap;
+    }
+
+    /**
+     * Sets the prefix to add to the font's PostScript name.
+     */
+    public void setPrefix(String prefix)
+    {
+        this.prefix = prefix;
+    }
+    
+    /**
+     * Add the given character code to the subset.
+     * 
+     * @param unicode character code
+     */
+    public void add(int unicode)
+    {
+        int gid = unicodeCmap.getGlyphId(unicode);
+        if (gid != 0)
         {
-            ++ret;
+            uniToGID.put(unicode, gid);
+            glyphIds.add(gid);
         }
-        return ret;
     }
-    
-    private static long buildUint32(int high, int low) 
+
+    /**
+     * Add the given character codes to the subset.
+     *
+     * @param unicodeSet character code set
+     */
+    public void addAll(Set<Integer> unicodeSet)
     {
-        return ((((long)high)&0xffffL) << 16) | (((long)low)&0xffffL);
+        for (int unicode : unicodeSet)
+        {
+            add(unicode);
+        }
     }
-    
-    private static long buildUint32(byte[] bytes) 
+
+    /**
+     * Returns the map of new -> old GIDs.
+     */
+    public Map<Integer, Integer> getGIDMap() throws IOException
     {
-        return ((((long)bytes[0])&0xffL) << 24) |
-                ((((long)bytes[1])&0xffL) << 16) |
-                ((((long)bytes[2])&0xffL) << 8) |
-                (((long)bytes[3])&0xffL);
+        addCompoundReferences();
+
+        Map<Integer, Integer> newToOld = new HashMap<Integer, Integer>();
+        int newGID = 0;
+        for (int oldGID : glyphIds)
+        {
+            newToOld.put(newGID, oldGID);
+            newGID++;
+        }
+        return newToOld;
     }
 
     /**
-     * @param dos The data output stream.
+     * @param out The data output stream.
      * @param nTables The number of table.
      * @return The file offset of the first TTF table to write.
      * @throws IOException Upon errors.
      */
-    private static long writeFileHeader(DataOutputStream dos, int nTables) throws IOException 
+    private long writeFileHeader(DataOutputStream out, int nTables) throws IOException
     {
-        dos.writeInt(0x00010000);
-        dos.writeShort(nTables);
+        out.writeInt(0x00010000);
+        out.writeShort(nTables);
         
         int mask = Integer.highestOneBit(nTables);
         int searchRange = mask * 16;
-        dos.writeShort(searchRange);
+        out.writeShort(searchRange);
         
-        int entrySelector=log2i(mask);
+        int entrySelector = log2(mask);
     
-        dos.writeShort(entrySelector);
+        out.writeShort(entrySelector);
         
         // numTables * 16 - searchRange
         int last = 16 * nTables - searchRange;
-        dos.writeShort(last);
+        out.writeShort(last);
         
-        return 0x00010000L + buildUint32(nTables,searchRange) + buildUint32(entrySelector,last);
+        return 0x00010000L + toUInt32(nTables, searchRange) + toUInt32(entrySelector, last);
     }
         
-    private static long writeTableHeader(DataOutputStream dos, String tag, long offset, byte[] bytes) 
+    private long writeTableHeader(DataOutputStream out, String tag, long offset, byte[] bytes)
             throws IOException 
     {
-        
-        int n = bytes.length;
-        int nup;
-        long checksum = 0L;
-        
-        for (nup=0;nup<n;++nup) 
+        long checksum = 0;
+        for (int nup = 0, n = bytes.length; nup < n; nup++)
         {
-            checksum += (((long)bytes[nup]) & 0xffL)<<(24-(nup%4)*8);
+            checksum += ((long)bytes[nup] & 0xffL) << 24- nup % 4 *8;
         }
-        
         checksum &= 0xffffffffL;
-        
-        LOG.debug(String.format("Writing table header [%s,%08x,%08x,%08x]",tag,checksum,offset,bytes.length));
-        
+
         byte[] tagbytes = tag.getBytes("US-ASCII");
-        
-        dos.write(tagbytes,0,4);
-        dos.writeInt((int)checksum);
-        dos.writeInt((int)offset);
-        dos.writeInt(bytes.length);
-        
-        // account for the checksum twice, one time for the header field, on time for the content itself.
-        return buildUint32(tagbytes) + checksum + checksum + offset + bytes.length;
+
+        out.write(tagbytes, 0, 4);
+        out.writeInt((int)checksum);
+        out.writeInt((int)offset);
+        out.writeInt(bytes.length);
+
+        // account for the checksum twice, once for the header field, once for the content itself
+        return toUInt32(tagbytes) + checksum + checksum + offset + bytes.length;
     }
-    
-    private static void writeTableBody(OutputStream os, byte[] bytes) throws IOException 
+
+    private void writeTableBody(OutputStream os, byte[] bytes) throws IOException
     {
         int n = bytes.length;
         os.write(bytes);
-        if ((n%4)!=0) 
+        if (n % 4 != 0)
         {
-            os.write(PAD_BUF,0,4-n%4);
+            os.write(PAD_BUF, 0, 4 - n % 4);
         }
     }
 
-    private static void writeFixed(DataOutputStream dos, double f) throws IOException 
-    {
-        double ip = Math.floor(f);
-        double fp = (f-ip) * 65536.0;
-        dos.writeShort((int)ip);
-        dos.writeShort((int)fp);
-    }
-    
-    private static void writeUint32(DataOutputStream dos, long l) throws IOException 
-    {
-        dos.writeInt((int)l);
-    }
-
-    private static void writeUint16(DataOutputStream dos, int i) throws IOException 
-    {
-        dos.writeShort(i);
-    }
-
-    private static void writeSint16(DataOutputStream dos, short i) throws IOException 
-    {
-        dos.writeShort(i);
-    }
-
-    private static void writeUint8(DataOutputStream dos, int i) throws IOException 
-    {
-        dos.writeByte(i);
-    }
-
-    private static void writeLongDateTime(DataOutputStream dos, Calendar calendar) throws IOException 
-    {
-        // inverse operation of TTFDataStream.readInternationalDate()
-        GregorianCalendar cal = new GregorianCalendar( 1904, 0, 1 );
-        long millisFor1904 = cal.getTimeInMillis();
-        long secondsSince1904 = (calendar.getTimeInMillis() - millisFor1904) / 1000L;
-        dos.writeLong(secondsSince1904);
-    }
-
-    private byte[] buildHeadTable() throws IOException 
+    private byte[] buildHeadTable() throws IOException
     {
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        DataOutputStream dos = new DataOutputStream(bos);
-        
-        LOG.debug("Building table [head]...");
-        
-        HeaderTable h = this.baseTTF.getHeader();
-        
-        writeFixed(dos,h.getVersion());
-        writeFixed(dos,h.getFontRevision());
-        writeUint32(dos,0 /* h.getCheckSumAdjustment() */);
-        writeUint32(dos,h.getMagicNumber());
-        writeUint16(dos,h.getFlags());
-        writeUint16(dos,h.getUnitsPerEm());
-        writeLongDateTime(dos,h.getCreated());
-        writeLongDateTime(dos,h.getModified());
-        writeSint16(dos,h.getXMin());
-        writeSint16(dos,h.getYMin());
-        writeSint16(dos,h.getXMax());
-        writeSint16(dos,h.getYMax());
-        writeUint16(dos,h.getMacStyle());
-        writeUint16(dos,h.getLowestRecPPEM());
-        writeSint16(dos,h.getFontDirectionHint());
-        // force long format of 'loca' table.
-        writeSint16(dos,(short)1 /* h.getIndexToLocFormat() */);
-        writeSint16(dos,h.getGlyphDataFormat());
-        dos.flush();
-        
-        LOG.debug("Finished table [head].");
+        DataOutputStream out = new DataOutputStream(bos);
+
+        HeaderTable h = ttf.getHeader();
+        writeFixed(out, h.getVersion());
+        writeFixed(out, h.getFontRevision());
+        writeUint32(out, 0); // h.getCheckSumAdjustment()
+        writeUint32(out, h.getMagicNumber());
+        writeUint16(out, h.getFlags());
+        writeUint16(out, h.getUnitsPerEm());
+        writeLongDateTime(out, h.getCreated());
+        writeLongDateTime(out, h.getModified());
+        writeSInt16(out, h.getXMin());
+        writeSInt16(out, h.getYMin());
+        writeSInt16(out, h.getXMax());
+        writeSInt16(out, h.getYMax());
+        writeUint16(out, h.getMacStyle());
+        writeUint16(out, h.getLowestRecPPEM());
+        writeSInt16(out, h.getFontDirectionHint());
+        // force long format of 'loca' table
+        writeSInt16(out, (short)1); // h.getIndexToLocFormat()
+        writeSInt16(out, h.getGlyphDataFormat());
+        out.flush();
 
         return bos.toByteArray();
     }
-    
-    private byte[] buildHheaTable() throws IOException 
+
+    private byte[] buildHheaTable() throws IOException
     {
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        DataOutputStream dos = new DataOutputStream(bos);
+        DataOutputStream out = new DataOutputStream(bos);
 
-        LOG.debug("Building table [hhea]...");
+        HorizontalHeaderTable h = ttf.getHorizontalHeader();
+        writeFixed(out, h.getVersion());
+        writeSInt16(out, h.getAscender());
+        writeSInt16(out, h.getDescender());
+        writeSInt16(out, h.getLineGap());
+        writeUint16(out, h.getAdvanceWidthMax());
+        writeSInt16(out, h.getMinLeftSideBearing());
+        writeSInt16(out, h.getMinRightSideBearing());
+        writeSInt16(out, h.getXMaxExtent());
+        writeSInt16(out, h.getCaretSlopeRise());
+        writeSInt16(out, h.getCaretSlopeRun());
+        writeSInt16(out, h.getReserved1()); // caretOffset
+        writeSInt16(out, h.getReserved2());
+        writeSInt16(out, h.getReserved3());
+        writeSInt16(out, h.getReserved4());
+        writeSInt16(out, h.getReserved5());
+        writeSInt16(out, h.getMetricDataFormat());
+        writeUint16(out, glyphIds.subSet(0, h.getNumberOfHMetrics()).size());
 
-        HorizontalHeaderTable h = this.baseTTF.getHorizontalHeader();
-        
-        writeFixed(dos,h.getVersion());
-        writeSint16(dos,h.getAscender());
-        writeSint16(dos,h.getDescender());
-        writeSint16(dos,h.getLineGap());
-        writeUint16(dos,h.getAdvanceWidthMax());
-        writeSint16(dos,h.getMinLeftSideBearing());
-        writeSint16(dos,h.getMinRightSideBearing());
-        writeSint16(dos,h.getXMaxExtent());
-        writeSint16(dos,h.getCaretSlopeRise());
-        writeSint16(dos,h.getCaretSlopeRun());
-        writeSint16(dos,h.getReserved1()); // caretOffset
-        writeSint16(dos,h.getReserved2());
-        writeSint16(dos,h.getReserved3());
-        writeSint16(dos,h.getReserved4());
-        writeSint16(dos,h.getReserved5());
-        writeSint16(dos,h.getMetricDataFormat());
-        writeUint16(dos,glyphIds.subSet(0,h.getNumberOfHMetrics()).size());
-                
-        dos.flush();
-        LOG.debug("Finished table [hhea].");
+        out.flush();
         return bos.toByteArray();
     }
 
-    private static boolean replicateNameRecord(NameRecord nr) 
+    private boolean shouldCopyNameRecord(NameRecord nr)
     {
-        return nr.getPlatformId() == NameRecord.PLATFORM_WINDOWS 
+        return nr.getPlatformId() == NameRecord.PLATFORM_WINDOWS
                 && nr.getPlatformEncodingId() == NameRecord.ENCODING_WINDOWS_UNICODE_BMP
-                && nr.getLanguageId() == 0 
+                && nr.getLanguageId() == NameRecord.LANGUGAE_WINDOWS_EN_US
                 && nr.getNameId() >= 0 && nr.getNameId() < 7;
     }
-    
-    private byte[] buildNameTable() throws IOException 
+
+    private byte[] buildNameTable() throws IOException
     {
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        DataOutputStream dos = new DataOutputStream(bos);
-        
-        LOG.debug("Building table [name]...");
+        DataOutputStream out = new DataOutputStream(bos);
+
+        NamingTable name = ttf.getNaming();
+        if (name == null || keepTables != null && !keepTables.contains("name"))
+        {
+            return null;
+        }
 
-        NamingTable n = this.baseTTF.getNaming();
-        List<NameRecord> nameRecords = null;
-        if ( n != null)
-        {
-            nameRecords = n.getNameRecords();
-        }
-        else
-        {
-            // sometimes there is no naming table in an embedded subfonts
-            // create some dummies
-            nameRecords = new ArrayList<NameRecord>();
-            NameRecord nr = new NameRecord();
-            nr.setPlatformId(NameRecord.PLATFORM_WINDOWS);
-            nr.setPlatformEncodingId(NameRecord.ENCODING_WINDOWS_UNICODE_BMP);
-            nr.setLanguageId(0);
-            nr.setNameId(NameRecord.NAME_FONT_FAMILY_NAME);
-            nr.setString("PDFBox-Dummy-Familyname");
-            nameRecords.add(nr);
-            nr = new NameRecord();
-            nr.setPlatformId(NameRecord.PLATFORM_WINDOWS);
-            nr.setPlatformEncodingId(NameRecord.ENCODING_WINDOWS_UNICODE_BMP);
-            nr.setLanguageId(0);
-            nr.setNameId(NameRecord.NAME_FULL_FONT_NAME);
-            nr.setString("PDFBox-Dummy-Fullname");
-            nameRecords.add(nr);
-        }
-        int numberOfRecords = nameRecords.size();
-        int nrep = 0;
-        for (int i=0;i<numberOfRecords;++i) 
+        List<NameRecord> nameRecords = name.getNameRecords();
+        int numRecords = 0;
+        for (NameRecord record : nameRecords)
         {
-            NameRecord nr = nameRecords.get(i);
-            if (replicateNameRecord(nr)) 
+            if (shouldCopyNameRecord(record))
             {
-                LOG.debug("Writing name record ["+nr.getNameId()+"], ["+nr.getString()+"],");
-                ++nrep;
+                numRecords++;
             }
         }
-        writeUint16(dos,0);
-        writeUint16(dos,nrep);
-        writeUint16(dos,2*3 + (2*6) * nrep);
-
-        byte[][] names = new byte[nrep][];
-        int j=0;
-        for (int i=0;i<numberOfRecords;++i) 
+        writeUint16(out, 0);
+        writeUint16(out, numRecords);
+        writeUint16(out, 2*3 + 2*6 * numRecords);
+
+        if (numRecords == 0)
         {
-            NameRecord nr = nameRecords.get(i);
-            if (replicateNameRecord(nr)) 
+            return null;
+        }
+
+        byte[][] names = new byte[numRecords][];
+        int j = 0;
+        for (NameRecord record : nameRecords)
+        {
+            if (shouldCopyNameRecord(record))
             {
-                int platform = nr.getPlatformId();
-                int encoding = nr.getPlatformEncodingId();
+                int platform = record.getPlatformId();
+                int encoding = record.getPlatformEncodingId();
                 String charset = "ISO-8859-1";
-                if( platform == 3 && encoding == 1 )
+
+                if (platform == CmapTable.PLATFORM_WINDOWS &&
+                    encoding == CmapTable.ENCODING_WIN_UNICODE_BMP)
                 {
                     charset = "UTF-16BE";
                 }
-                else if( platform == 2 )
+                else if (platform == 2) // ISO [deprecated]=
                 {
-                    if( encoding == 0 )
+                    if (encoding == 0) // 7-bit ASCII
                     {
                         charset = "US-ASCII";
                     }
-                    else if( encoding == 1 )
+                    else if (encoding == 1) // ISO 10646=
                     {
                         //not sure is this is correct??
                         charset = "UTF16-BE";
                     }
-                    else if( encoding == 2 )
+                    else if (encoding == 2) // ISO 8859-1
                     {
                         charset = "ISO-8859-1";
                     }
                 }
-                String value = nr.getString();
-                if (nr.getNameId() == 6 && this.nameSuffix != null) 
+                String value = record.getString();
+                if (record.getNameId() == 6 && prefix != null)
                 {
-                    value += this.nameSuffix;
+                    value = prefix + value;
                 }
                 names[j] = value.getBytes(charset);
-                ++j;
+                j++;
             }
         }
-        
+
         int offset = 0;
         j = 0;
-        for (int i=0;i<numberOfRecords;++i) 
+        for (NameRecord nr : nameRecords)
         {
-            NameRecord nr = nameRecords.get(i);
-            if (replicateNameRecord(nr)) 
+            if (shouldCopyNameRecord(nr))
             {
-                writeUint16(dos,nr.getPlatformId());
-                writeUint16(dos,nr.getPlatformEncodingId());
-                writeUint16(dos,nr.getLanguageId());
-                writeUint16(dos,nr.getNameId());
-                writeUint16(dos,names[j].length);
-                writeUint16(dos,offset);
+                writeUint16(out, nr.getPlatformId());
+                writeUint16(out, nr.getPlatformEncodingId());
+                writeUint16(out, nr.getLanguageId());
+                writeUint16(out, nr.getNameId());
+                writeUint16(out, names[j].length);
+                writeUint16(out, offset);
                 offset += names[j].length;
-                ++j;
+                j++;
             }
         }
-        
-        for (int i=0;i<nrep;++i) 
+
+        for (int i = 0; i < numRecords; i++)
         {
-            dos.write(names[i]);
+            out.write(names[i]);
         }
-        dos.flush();
-        LOG.debug("Finished table [name].");
+
+        out.flush();
         return bos.toByteArray();
     }
-    
-    private byte[] buildMaxpTable() throws IOException 
+
+    private byte[] buildMaxpTable() throws IOException
     {
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        DataOutputStream dos = new DataOutputStream(bos);
-
-        LOG.debug("Building table [maxp]...");
+        DataOutputStream out = new DataOutputStream(bos);
 
-        MaximumProfileTable p = this.baseTTF.getMaximumProfile();
+        MaximumProfileTable p = ttf.getMaximumProfile();
+        writeFixed(out, 1.0);
+        writeUint16(out, glyphIds.size());
+        writeUint16(out, p.getMaxPoints());
+        writeUint16(out, p.getMaxContours());
+        writeUint16(out, p.getMaxCompositePoints());
+        writeUint16(out, p.getMaxCompositeContours());
+        writeUint16(out, p.getMaxZones());
+        writeUint16(out, p.getMaxTwilightPoints());
+        writeUint16(out, p.getMaxStorage());
+        writeUint16(out, p.getMaxFunctionDefs());
+        writeUint16(out, p.getMaxInstructionDefs());
+        writeUint16(out, p.getMaxStackElements());
+        writeUint16(out, p.getMaxSizeOfInstructions());
+        writeUint16(out, p.getMaxComponentElements());
+        writeUint16(out, p.getMaxComponentDepth());
 
-        writeFixed(dos,1.0);
-        writeUint16(dos,glyphIds.size());
-        writeUint16(dos,p.getMaxPoints());
-        writeUint16(dos,p.getMaxContours());
-        writeUint16(dos,p.getMaxCompositePoints());
-        writeUint16(dos,p.getMaxCompositeContours());
-        writeUint16(dos,p.getMaxZones());
-        writeUint16(dos,p.getMaxTwilightPoints());
-        writeUint16(dos,p.getMaxStorage());
-        writeUint16(dos,p.getMaxFunctionDefs());
-        writeUint16(dos,0);
-        writeUint16(dos,p.getMaxStackElements());
-        writeUint16(dos,0);
-        writeUint16(dos,p.getMaxComponentElements());
-        writeUint16(dos,p.getMaxComponentDepth());
-
-        dos.flush();
-        LOG.debug("Finished table [maxp].");
+        out.flush();
         return bos.toByteArray();
     }
-    
-    private byte[] buildOS2Table() throws IOException 
+
+    private byte[] buildOS2Table() throws IOException
     {
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        DataOutputStream dos = new DataOutputStream(bos);
-        OS2WindowsMetricsTable os2 = this.baseTTF.getOS2Windows();
-        if (os2 == null)
-        {
-            // sometimes there is no OS2 table in an embedded subfonts
-            // create a dummy
-            os2 = new OS2WindowsMetricsTable();
+        OS2WindowsMetricsTable os2 = ttf.getOS2Windows();
+        if (os2 == null || keepTables != null && !keepTables.contains("OS/2"))
+        {
+            return null;
         }
-        
-        LOG.debug("Building table [OS/2]...");
 
-        writeUint16(dos,0);
-        writeSint16(dos,os2.getAverageCharWidth());
-        writeUint16(dos,os2.getWeightClass());
-        writeUint16(dos,os2.getWidthClass());
-            
-        writeSint16(dos,os2.getFsType());
-        
-        writeSint16(dos,os2.getSubscriptXSize());
-        writeSint16(dos,os2.getSubscriptYSize());
-        writeSint16(dos,os2.getSubscriptXOffset());
-        writeSint16(dos,os2.getSubscriptYOffset());
-        
-        writeSint16(dos,os2.getSuperscriptXSize());
-        writeSint16(dos,os2.getSuperscriptYSize());
-        writeSint16(dos,os2.getSuperscriptXOffset());
-        writeSint16(dos,os2.getSuperscriptYOffset());
-        
-        writeSint16(dos,os2.getStrikeoutSize());
-        writeSint16(dos,os2.getStrikeoutPosition());
-        writeUint8(dos,os2.getFamilyClass());
-        writeUint8(dos,os2.getFamilySubClass());
-        dos.write(os2.getPanose());
-        
-        writeUint32(dos,0);
-        writeUint32(dos,0);
-        writeUint32(dos,0);
-        writeUint32(dos,0);
-        
-        dos.write(os2.getAchVendId().getBytes("ISO-8859-1"));
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        DataOutputStream out = new DataOutputStream(bos);
 
-        Iterator<Entry<Integer, Integer>> it = characters.entrySet().iterator();
+        writeUint16(out, os2.getVersion());
+        writeSInt16(out, os2.getAverageCharWidth());
+        writeUint16(out, os2.getWeightClass());
+        writeUint16(out, os2.getWidthClass());
+
+        writeSInt16(out, os2.getFsType());
+
+        writeSInt16(out, os2.getSubscriptXSize());
+        writeSInt16(out, os2.getSubscriptYSize());
+        writeSInt16(out, os2.getSubscriptXOffset());
+        writeSInt16(out, os2.getSubscriptYOffset());
+
+        writeSInt16(out, os2.getSuperscriptXSize());
+        writeSInt16(out, os2.getSuperscriptYSize());
+        writeSInt16(out, os2.getSuperscriptXOffset());
+        writeSInt16(out, os2.getSuperscriptYOffset());
+
+        writeSInt16(out, os2.getStrikeoutSize());
+        writeSInt16(out, os2.getStrikeoutPosition());
+        writeUint8(out, os2.getFamilyClass());
+        writeUint8(out, os2.getFamilySubClass());
+        out.write(os2.getPanose());
+
+        writeUint32(out, 0);
+        writeUint32(out, 0);
+        writeUint32(out, 0);
+        writeUint32(out, 0);
+
+        out.write(os2.getAchVendId().getBytes("US-ASCII"));
+
+        Iterator<Entry<Integer, Integer>> it = uniToGID.entrySet().iterator();
         it.next();
         Entry<Integer, Integer> first = it.next();
-        
-        writeUint16(dos,os2.getFsSelection());
-        writeUint16(dos,first.getKey());
-        writeUint16(dos,characters.lastKey());
-        /*
-         * The mysterious Microsoft additions.
-         *
-         * SHORT    sTypoAscender    
-         * SHORT    sTypoDescender    
-         * SHORT    sTypoLineGap
-         * USHORT    usWinAscent
-         * USHORT    usWinDescent
-         */
-        writeUint16(dos,os2.getTypoAscender());
-        writeUint16(dos,os2.getTypoDescender());
-        writeUint16(dos,os2.getTypoLineGap());
-        writeUint16(dos,os2.getWinAscent());
-        writeUint16(dos,os2.getWinDescent());
-        
-        dos.flush();
-        LOG.debug("Finished table [OS/2].");
+
+        writeUint16(out, os2.getFsSelection());
+        writeUint16(out, first.getKey());
+        writeUint16(out, uniToGID.lastKey());
+        writeUint16(out, os2.getTypoAscender());
+        writeUint16(out, os2.getTypoDescender());
+        writeUint16(out, os2.getTypoLineGap());
+        writeUint16(out, os2.getWinAscent());
+        writeUint16(out, os2.getWinDescent());
+
+        out.flush();
         return bos.toByteArray();
     }
-    
-    private byte[] buildLocaTable(long[] newOffsets) throws IOException 
+
+    private byte[] buildLocaTable(long[] newOffsets) throws IOException
     {
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        DataOutputStream dos = new DataOutputStream(bos);
+        DataOutputStream out = new DataOutputStream(bos);
 
-        LOG.debug("Building table [loca]...");
-
-        for (long newOff : newOffsets)
-        {                
-            writeUint32(dos,newOff);
+        for (long offset : newOffsets)
+        {
+            writeUint32(out, offset);
         }
-        dos.flush();
-        LOG.debug("Finished table [loca].");
+
+        out.flush();
         return bos.toByteArray();
     }
 
-    private boolean addCompoundReferences() throws IOException 
+    /**
+     * Resolve compound glyph references.
+     */
+    private void addCompoundReferences() throws IOException
     {
-        GlyphTable g = this.baseTTF.getGlyph();
-        long[] offsets = this.baseTTF.getIndexToLocation().getOffsets();
-        InputStream is = this.baseTTF.getOriginalData();
-        Set<Integer> glyphIdsToAdd = null;
-        try 
+        if (hasAddedCompoundReferences)
         {
-            is.skip(g.getOffset());
-            long lastOff = 0L;
-            for (Integer glyphId : this.glyphIds) 
+            return;
+        }
+        hasAddedCompoundReferences = true;
+
+        boolean hasNested;
+        do
+        {
+            GlyphTable g = ttf.getGlyph();
+            long[] offsets = ttf.getIndexToLocation().getOffsets();
+            InputStream is = ttf.getOriginalData();
+            Set<Integer> glyphIdsToAdd = null;
+            try
             {
-                long offset = offsets[glyphId];
-                long len = offsets[glyphId+1] - offset;
-                is.skip(offset-lastOff);
-                byte[] buf= new byte[(int)len];
-                is.read(buf);
-                // rewrite glyphIds for compound glyphs
-                if (buf.length >= 2 && buf[0] == -1 && buf[1] == -1) 
+                is.skip(g.getOffset());
+                long lastOff = 0L;
+                for (Integer glyphId : glyphIds)
                 {
-                    int off = 2*5;
-                    int flags = 0;
-                    do 
+                    long offset = offsets[glyphId];
+                    long len = offsets[glyphId + 1] - offset;
+                    is.skip(offset - lastOff);
+                    byte[] buf = new byte[(int)len];
+                    is.read(buf);
+                    // rewrite glyphIds for compound glyphs
+                    if (buf.length >= 2 && buf[0] == -1 && buf[1] == -1)
                     {
-                        flags = ((((int)buf[off]) & 0xff) << 8) | (buf[off+1] & 0xff); 
-                        off +=2;
-                        int ogid = ((((int)buf[off]) & 0xff) << 8) | (buf[off+1] & 0xff);
-                        if (!this.glyphIds.contains(ogid)) 
+                        int off = 2*5;
+                        int flags;
+                        do
                         {
-                            LOG.debug("Adding referenced glyph "+ogid+" of compound glyph "+glyphId);
-                            if (glyphIdsToAdd == null) 
+                            flags = ((int)buf[off] & 0xff) << 8 | buf[off + 1] & 0xff;
+                            off +=2;
+                            int ogid = ((int)buf[off] & 0xff) << 8 | buf[off + 1] & 0xff;
+                            if (!glyphIds.contains(ogid))
                             {
-                                glyphIdsToAdd = new TreeSet<Integer>();
+                                if (glyphIdsToAdd == null)
+                                {
+                                    glyphIdsToAdd = new TreeSet<Integer>();
+                                }
+                                glyphIdsToAdd.add(ogid);
                             }
-                            glyphIdsToAdd.add(ogid);
-                        }
-                        off += 2;
-                        // ARG_1_AND_2_ARE_WORDS
-                        if ((flags & (1 << 0)) != 0) 
-                        {
-                            off += 2 * 2;
-                        }
-                        else 
-                        {
-                            off += 2;
-                        }
-                        // WE_HAVE_A_TWO_BY_TWO
-                        if ((flags & (1 << 7)) != 0) 
-                        {
-                            off += 2 * 4;
-                        }
-                        // WE_HAVE_AN_X_AND_Y_SCALE
-                        else if ((flags & (1 << 6)) != 0) 
-                        {
-                            off += 2 * 2;
-                        }
-                        // WE_HAVE_A_SCALE
-                        else if ((flags & (1 << 3)) != 0) 
-                        {
                             off += 2;
+                            // ARG_1_AND_2_ARE_WORDS
+                            if ((flags & 1 << 0) != 0)
+                            {
+                                off += 2 * 2;
+                            }
+                            else
+                            {
+                                off += 2;
+                            }
+                            // WE_HAVE_A_TWO_BY_TWO
+                            if ((flags & 1 << 7) != 0)
+                            {
+                                off += 2 * 4;
+                            }
+                            // WE_HAVE_AN_X_AND_Y_SCALE
+                            else if ((flags & 1 << 6) != 0)
+                            {
+                                off += 2 * 2;
+                            }
+                            // WE_HAVE_A_SCALE
+                            else if ((flags & 1 << 3) != 0)
+                            {
+                                off += 2;
+                            }
                         }
-                        
-                        // MORE_COMPONENTS
-                    } 
-                    while ((flags & (1 << 5)) != 0);
-                    
+                        while ((flags & 1 << 5) != 0); // MORE_COMPONENTS
+
+                    }
+                    lastOff = offsets[glyphId + 1];
                 }
-                lastOff = offsets[glyphId+1];
             }
-        }
-        finally 
-        {
-            is.close();
-        }
-        if (glyphIdsToAdd != null) 
-        {
-            this.glyphIds.addAll(glyphIdsToAdd);
-        }
-        return glyphIdsToAdd == null;
+            finally
+            {
+                is.close();
+            }
+            if (glyphIdsToAdd != null)
+            {
+                glyphIds.addAll(glyphIdsToAdd);
+            }
+            hasNested = glyphIdsToAdd != null;
+        } while (hasNested);
     }
-    
-    private byte[] buildGlyfTable(long[] newOffsets) throws IOException 
+
+    private byte[] buildGlyfTable(long[] newOffsets) throws IOException
     {
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        LOG.debug("Building table [glyf]...");
-        GlyphTable g = this.baseTTF.getGlyph();
-        long[] offsets = this.baseTTF.getIndexToLocation().getOffsets();
-        InputStream is = this.baseTTF.getOriginalData();
-        try 
+
+        GlyphTable g = ttf.getGlyph();
+        long[] offsets = ttf.getIndexToLocation().getOffsets();
+        InputStream is = ttf.getOriginalData();
+        try
         {
             is.skip(g.getOffset());
-            long lastOff = 0L;
-            long newOff = 0L;
-            int ioff = 0;
-            for (Integer glyphId : this.glyphIds) 
+
+            long prevEnd = 0;    // previously read glyph offset
+            long newOffset = 0;  // new offset for the glyph in the subset font
+            int newGid = 0;      // new GID in subset font
+
+            // for each glyph in the subset
+            for (Integer gid : glyphIds)
             {
-                long offset = offsets[glyphId];
-                long len = offsets[glyphId+1] - offset;
-                newOffsets[ioff++] = newOff;
-                is.skip(offset-lastOff);
-                byte[] buf= new byte[(int)len];
+                long offset = offsets[gid];
+                long length = offsets[gid + 1] - offset;
+
+                newOffsets[newGid++] = newOffset;
+                is.skip(offset - prevEnd);
+
+                byte[] buf = new byte[(int)length];
                 is.read(buf);
-                // rewrite glyphIds for compound glyphs
-                if (buf.length >= 2 && buf[0] == -1 && buf[1] == -1) 
+
+                // detect glyph type
+                if (buf.length >= 2 && buf[0] == -1 && buf[1] == -1)
                 {
-                    LOG.debug("Compound glyph "+glyphId);
+                    // compound glyph
                     int off = 2*5;
-                    int flags = 0;
-                    do 
+                    int flags;
+                    do
                     {
-                        // clear the WE_HAVE_INSTRUCTIONS bit. (bit 8 is lsb of the first byte)
-                        buf[off] &= 0xfe;
-                        flags = ((((int)buf[off]) & 0xff) << 8) | ((int)buf[off+1] & 0xff);                         
-                        off +=2;
-                        int ogid = ((((int)buf[off]) & 0xff) << 8) | ((int)buf[off+1] & 0xff);
-                        if (!this.glyphIds.contains(ogid)) 
-                        {
-                            this.glyphIds.add(ogid);
-                        }
-                        int ngid = this.getNewGlyphId(ogid);
-                        if (LOG.isDebugEnabled()) 
+                        // flags
+                        flags = ((int)buf[off] & 0xff) << 8 | (int)buf[off + 1] & 0xff;
+                        off += 2;
+
+                        // glyphIndex
+                        int componentGid = ((int)buf[off] & 0xff) << 8 | (int)buf[off + 1] & 0xff;
+                        if (!glyphIds.contains(componentGid))
                         {
-                            LOG.debug(String.format("mapped glyph  %d to %d in compound reference (flags=%04x)",
-                                    ogid,ngid,flags));
+                            glyphIds.add(componentGid);
                         }
-                        buf[off]   = (byte)(ngid >>> 8);
-                        buf[off+1] = (byte)ngid;
+
+                        int newComponentGid = getNewGlyphId(componentGid);
+                        buf[off]   = (byte)(newComponentGid >>> 8);
+                        buf[off + 1] = (byte)newComponentGid;
                         off += 2;
+
                         // ARG_1_AND_2_ARE_WORDS
-                        if ((flags & (1 << 0)) != 0) 
+                        if ((flags & 1 << 0) != 0)
                         {
                             off += 2 * 2;
                         }
-                        else 
+                        else
                         {
                             off += 2;
                         }
                         // WE_HAVE_A_TWO_BY_TWO
-                        if ((flags & (1 << 7)) != 0) 
+                        if ((flags & 1 << 7) != 0)
                         {
                             off += 2 * 4;
                         }
                         // WE_HAVE_AN_X_AND_Y_SCALE
-                        else if ((flags & (1 << 6)) != 0) 
+                        else if ((flags & 1 << 6) != 0)
                         {
                             off += 2 * 2;
                         }
                         // WE_HAVE_A_SCALE
-                        else if ((flags & (1 << 3)) != 0) 
+                        else if ((flags & 1 << 3) != 0)
                         {
                             off += 2;
                         }
-                        // MORE_COMPONENTS
-                    } 
-                    while ((flags & (1 << 5)) != 0);
+                    }
+                    while ((flags & 1 << 5) != 0); // MORE_COMPONENTS
+
+                    // WE_HAVE_INSTRUCTIONS
+                    if ((flags & 0x0100) == 0x0100)
+                    {
+                        // USHORT numInstr
+                        int numInstr = ((int)buf[off] & 0xff) << 8 | (int)buf[off + 1] & 0xff;
+                        off += 2;
+
+                        // BYTE instr[numInstr]
+                        off += numInstr;
+                    }
+
                     // write the compound glyph
-                    bos.write(buf,0,off);
-                    newOff += off;
+                    bos.write(buf, 0, off);
+
+                    // offset to start next glyph
+                    newOffset += off;
                 }
                 else if (buf.length > 0)
                 {
-                    /*
-                     * bail out instructions for simple glyphs, an excerpt from the specs is given below:
-                     *                         
-                     * int16    numberOfContours    If the number of contours is positive or zero, it is a single glyph;
-                     * If the number of contours is -1, the glyph is compound
-                     *  FWord    xMin    Minimum x for coordinate data
-                     *  FWord    yMin    Minimum y for coordinate data
-                     *  FWord    xMax    Maximum x for coordinate data
-                     *  FWord    yMax    Maximum y for coordinate data
-                     *  (here follow the data for the simple or compound glyph)
-                     *
-                     * Table 15: Simple glyph definition
-                     *  Type    Name    Description
-                     *  uint16  endPtsOfContours[n] Array of last points of each contour; n is the number of contours;
-                     *          array entries are point indices
-                     *  uint16  instructionLength Total number of bytes needed for instructions
-                     *  uint8   instructions[instructionLength] Array of instructions for this glyph
-                     *  uint8   flags[variable] Array of flags
-                     *  uint8 or int16  xCoordinates[] Array of x-coordinates; the first is relative to (0,0),
-                     *                                 others are relative to previous point
-                     *  uint8 or int16  yCoordinates[] Array of y-coordinates; the first is relative to (0,0), 
-                     *                                 others are relative to previous point
-                     */
-                                        
-                    int    numberOfContours = (((int)buf[0]& 0xff) << 8) | ((int)buf[1] & 0xff); 
-                    
-                    // offset of instructionLength
-                    int off = 2*5 + numberOfContours * 2;
-                    
-                    // write numberOfContours, xMin, yMin, xMax, yMax, endPtsOfContours[n]
-                    bos.write(buf,0,off);
-                    newOff += off;
-                    
-                    int instructionLength = ((((int)buf[off]) & 0xff) << 8) | ((int)buf[off+1] & 0xff);
-                        
-                    // zarro instructions.
-                    bos.write(0);
-                    bos.write(0);
-                    newOff += 2;
-                    
-                    off += 2 + instructionLength;
-                    
-                    // flags and coordinates
-                    bos.write(buf,off,buf.length - off);
-                    newOff += buf.length - off;
+                    // copy the entire glyph
+                    bos.write(buf, 0, buf.length);
+
+                    // offset to start next glyph
+                    newOffset += buf.length;
                 }
-                
-                
-                if ((newOff % 4L) != 0L) 
+
+                // 4-byte alignment
+                if (newOffset % 4 != 0)
                 {
-                    int np = (int)(4-newOff%4L);
-                    bos.write(PAD_BUF,0,np);
-                    newOff += np;
+                    int len = 4 - (int)(newOffset % 4);
+                    bos.write(PAD_BUF, 0, len);
+                    newOffset += len;
                 }
-                
-                lastOff = offsets[glyphId+1];
+
+                prevEnd = offset + length;
             }
-            newOffsets[ioff++] = newOff;
+            newOffsets[newGid++] = newOffset;
         }
-        finally 
+        finally
         {
             is.close();
         }
-        LOG.debug("Finished table [glyf].");
+
         return bos.toByteArray();
     }
 
-    private int getNewGlyphId(Integer oldGid) 
+    private int getNewGlyphId(Integer oldGid)
     {
-        return this.glyphIds.headSet(oldGid).size();
+        return glyphIds.headSet(oldGid).size();
     }
-    
-    private byte[] buildCmapTable() throws IOException 
+
+    private byte[] buildCmapTable() throws IOException
     {
+        if (ttf.getCmap() == null || keepTables != null && !keepTables.contains("cmap"))
+        {
+            return null;
+        }
+
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        DataOutputStream dos = new DataOutputStream(bos);
-        LOG.debug("Building table [cmap]...");
-        /*
-         * UInt16    version    Version number (Set to zero)
-         * UInt16    numberSubtables    Number of encoding subtables
-         */
-        writeUint16(dos,0);
-        writeUint16(dos,1);
-        /*
-         * UInt16    platformID    Platform identifier
-         * UInt16    platformSpecificID    Platform-specific encoding identifier
-         * UInt32    offset    Offset of the mapping table
-         */
-        writeUint16(dos,3); // unicode
-        writeUint16(dos,1); // Default Semantics
-        writeUint32(dos, 4 * 2 + 4);
-        // mapping of type 4.
-        Iterator<Entry<Integer, Integer>> it = this.characters.entrySet().iterator();
+        DataOutputStream out = new DataOutputStream(bos);
+
+        // cmap header
+        writeUint16(out, 0); // version
+        writeUint16(out, 1); // numberSubtables
+
+        // encoding record
+        writeUint16(out, CmapTable.PLATFORM_WINDOWS); // platformID
+        writeUint16(out, CmapTable.ENCODING_WIN_UNICODE_BMP); // platformSpecificID
+        writeUint32(out, 4 * 2 + 4); // offset
+
+        // build Format 4 subtable (Unicode BMP)
+        Iterator<Entry<Integer, Integer>> it = uniToGID.entrySet().iterator();
         it.next();
         Entry<Integer, Integer> lastChar = it.next();
         Entry<Integer, Integer> prevChar = lastChar;
-        int lastGid = this.getNewGlyphId(lastChar.getValue());
+        int lastGid = getNewGlyphId(lastChar.getValue());
+
+        int[] startCode = new int[uniToGID.size()];
+        int[] endCode = new int[uniToGID.size()];
+        int[] idDelta = new int[uniToGID.size()];
+        int segCount = 0;
+        while(it.hasNext())
+        {
+            Entry<Integer, Integer> curChar2Gid = it.next();
+            int curGid = getNewGlyphId(curChar2Gid.getValue());
+
+            // todo: need format Format 12 for non-BMP
+            if (curChar2Gid.getKey() > 0xFFFF)
+            {
+                throw new UnsupportedOperationException("non-BMP Unicode character");
+            }
 
-        int[] startCode = new int[this.characters.size()];
-        int[] endCode = new int[this.characters.size()];
-        int[] idDelta = new int[this.characters.size()];
-        int nseg = 0;
-        while(it.hasNext()) 
-        {
-            Entry<Integer, Integer> curChar = it.next();
-            int curGid = this.getNewGlyphId(curChar.getValue());
-            
-            if (curChar.getKey() != prevChar.getKey()+1 ||
-                    curGid - lastGid != curChar.getKey() - lastChar.getKey()) 
+            if (curChar2Gid.getKey() != prevChar.getKey()+1 ||
+                curGid - lastGid != curChar2Gid.getKey() - lastChar.getKey())
             {
-                // Don't emit ranges, which map to the undef glyph, the
-                // undef glyph is emitted a the very last segment.
-                if (lastGid != 0) 
+                if (lastGid != 0)
                 {
-                    startCode[nseg] = lastChar.getKey();
-                    endCode[nseg] = prevChar.getKey();
-                    idDelta[nseg] = lastGid - lastChar.getKey();
-                    ++nseg;
+                    // don't emit ranges, which map to GID 0, the
+                    // undef glyph is emitted a the very last segment
+                    startCode[segCount] = lastChar.getKey();
+                    endCode[segCount] = prevChar.getKey();
+                    idDelta[segCount] = lastGid - lastChar.getKey();
+                    segCount++;
                 }
-                // shorten ranges which start with undef by one.
-                else if (!lastChar.getKey().equals(prevChar.getKey())) 
+                else if (!lastChar.getKey().equals(prevChar.getKey()))
                 {
-                    startCode[nseg] = lastChar.getKey()+1;
-                    endCode[nseg] = prevChar.getKey();
-                    idDelta[nseg] = lastGid - lastChar.getKey();
-                    ++nseg;
+                    // shorten ranges which start with GID 0 by one
+                    startCode[segCount] = lastChar.getKey() + 1;
+                    endCode[segCount] = prevChar.getKey();
+                    idDelta[segCount] = lastGid - lastChar.getKey();
+                    segCount++;
                 }
                 lastGid = curGid;
-                lastChar = curChar;
+                lastChar = curChar2Gid;
             }
-            prevChar = curChar;
+            prevChar = curChar2Gid;
         }
+
         // trailing segment
-        startCode[nseg] = lastChar.getKey();
-        endCode[nseg] = prevChar.getKey();
-        idDelta[nseg] = lastGid -lastChar.getKey();
-        ++nseg;
-        // notdef character.
-        startCode[nseg] = 0xffff;
-        endCode[nseg] = 0xffff;
-        idDelta[nseg] = 1;
-        ++nseg;
-        
-        /*
-         * UInt16    format    Format number is set to 4     
-         * UInt16    length    Length of subtable in bytes     
-         * UInt16    language    Language code for this encoding subtable, or zero if language-independent     
-         * UInt16    segCountX2    2 * segCount     
-         * UInt16    searchRange    2 * (2**FLOOR(log2(segCount)))     
-         * UInt16    entrySelector    log2(searchRange/2)     
-         * UInt16    rangeShift    (2 * segCount) - searchRange     
-         * UInt16    endCode[segCount]    Ending character code for each segment, last = 0xFFFF.    
-         * UInt16    reservedPad    This value should be zero    
-         * UInt16    startCode[segCount]    Starting character code for each segment    
-         * UInt16    idDelta[segCount]    Delta for all character codes in segment     
-         * UInt16    idRangeOffset[segCount]    Offset in bytes to glyph indexArray, or 0     
-         * UInt16    glyphIndexArray[variable]    Glyph index array
-         */
-        
-        writeUint16(dos,4);
-        writeUint16(dos, 8*2 + nseg * (4*2));
-        writeUint16(dos,0);
-        writeUint16(dos,nseg*2);
-        int nsegHigh = Integer.highestOneBit(nseg);
-        writeUint16(dos,nsegHigh*2);
-        writeUint16(dos,log2i(nsegHigh));
-        writeUint16(dos,2*(nseg-nsegHigh));
-        
-        for (int i=0;i<nseg;++i) 
-        {    
-            writeUint16(dos,endCode[i]);
-        }
-        writeUint16(dos,0);
-        for (int i=0;i<nseg;++i) 
-        {    
-            writeUint16(dos,startCode[i]);
-        }
-        for (int i=0;i<nseg;++i) 
-        {    
-            writeUint16(dos,idDelta[i]);
-        }
-        for (int i=0;i<nseg;++i) 
-        {    
-            writeUint16(dos,0);
+        startCode[segCount] = lastChar.getKey();
+        endCode[segCount] = prevChar.getKey();
+        idDelta[segCount] = lastGid -lastChar.getKey();
+        segCount++;
+
+        // GID 0
+        startCode[segCount] = 0xffff;
+        endCode[segCount] = 0xffff;
+        idDelta[segCount] = 1;
+        segCount++;
+
+        // write format 4 subtable
+        int searchRange = 2 * (int)Math.pow(2, Math.floor(log2(segCount)));
+        writeUint16(out, 4); // format
+        writeUint16(out, 8 * 2 + segCount * 4*2); // length
+        writeUint16(out, 0); // language
+        writeUint16(out, segCount * 2); // segCountX2
+        writeUint16(out, searchRange); // searchRange
+        writeUint16(out, log2(searchRange / 2)); // entrySelector
+        writeUint16(out, 2 * segCount - searchRange); // rangeShift
+
+        // endCode[segCount]
+        for (int i = 0; i < segCount; i++)
+        {
+            writeUint16(out, endCode[i]);
         }
-        LOG.debug("Finished table [cmap].");
+
+        // reservedPad
+        writeUint16(out, 0);
+
+        // startCode[segCount]
+        for (int i = 0; i < segCount; i++)
+        {
+            writeUint16(out, startCode[i]);
+        }
+
+        // idDelta[segCount]
+        for (int i = 0; i < segCount; i++)
+        {
+            writeUint16(out, idDelta[i]);
+        }
+
+        for (int i = 0; i < segCount; i++)
+        {
+            writeUint16(out, 0);
+        }
+
         return bos.toByteArray();
     }
 
-    private byte[] buildPostTable() throws IOException 
+    private byte[] buildPostTable() throws IOException
     {
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        DataOutputStream dos = new DataOutputStream(bos);
-        LOG.debug("Building table [post]...");
-        PostScriptTable p = this.baseTTF.getPostScript();
-        if (p == null)
-        {
-            // sometimes there is no post table in an embedded subfonts
-            // create a dummy
-            p = new PostScriptTable();
-        }
-        String[] glyphNames = p.getGlyphNames();
-        /*
-            Fixed    format    Format of this table
-            Fixed    italicAngle    Italic angle in degrees
-            FWord    underlinePosition    Underline position
-            FWord    underlineThickness    Underline thickness
-            uint32    isFixedPitch    Font is monospaced; set to 1 if the font is monospaced and 0 otherwise 
-            (N.B., to maintain compatibility with older versions of the TrueType spec, accept any non-zero value
-             as meaning that the font is monospaced)
-            uint32    minMemType42    Minimum memory usage when a TrueType font is downloaded as a Type 42 font
-            uint32    maxMemType42    Maximum memory usage when a TrueType font is downloaded as a Type 42 font
-            uint32    minMemType1    Minimum memory usage when a TrueType font is downloaded as a Type 1 font
-            uint32    maxMemType1    Maximum memory usage when a TrueType font is downloaded as a Type 1 font
-            uint16    numberOfGlyphs    number of glyphs
-            uint16    glyphNameIndex[numberOfGlyphs]    Ordinal number of this glyph in 'post' string tables. 
-            This is not an offset.
-            Pascal string    names[numberNewGlyphs]  glyph names with length bytes [variable] (a Pascal string)
-         */
-        writeFixed(dos,2.0);
-        writeFixed(dos,p.getItalicAngle());
-        writeSint16(dos,p.getUnderlinePosition());
-        writeSint16(dos,p.getUnderlineThickness());
-        writeUint32(dos,p.getIsFixedPitch());
-        writeUint32(dos,p.getMinMemType42());
-        writeUint32(dos,p.getMaxMemType42());
-        writeUint32(dos,p.getMimMemType1());
-        writeUint32(dos,p.getMaxMemType1());
-        writeUint16(dos,baseTTF.getHorizontalHeader().getNumberOfHMetrics());
-            
-        List<String> additionalNames = new ArrayList<String>();
-        Map<String,Integer> additionalNamesIndices = new HashMap<String,Integer>();
-        
-        if (glyphNames != null)
+        PostScriptTable post = ttf.getPostScript();
+        if (post == null || keepTables != null && !keepTables.contains("post"))
         {
-            for (Integer glyphId : this.glyphIds)
-            {
-                String name = glyphNames[glyphId];
+            return null;
+        }
 
-                Integer macId = WGL4Names.MAC_GLYPH_NAMES_INDICES.get(name);
-                if (macId == null)
-                {
-                    Integer idx = additionalNamesIndices.get(name);
-                    if (idx == null)
-                    {
-                        idx = additionalNames.size();
-                        additionalNames.add(name);
-                        additionalNamesIndices.put(name,idx);
-                    }
-                    writeUint16(dos,idx+258);
-                }
-                else
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        DataOutputStream out = new DataOutputStream(bos);
+
+        writeFixed(out, 2.0); // version
+        writeFixed(out, post.getItalicAngle());
+        writeSInt16(out, post.getUnderlinePosition());
+        writeSInt16(out, post.getUnderlineThickness());
+        writeUint32(out, post.getIsFixedPitch());
+        writeUint32(out, post.getMinMemType42());
+        writeUint32(out, post.getMaxMemType42());
+        writeUint32(out, post.getMinMemType1());
+        writeUint32(out, post.getMaxMemType1());
+
+        // version 2.0
+
+        // numberOfGlyphs
+        writeUint16(out, glyphIds.size());
+
+        // glyphNameIndex[numGlyphs]
+        Map<String, Integer> names = new TreeMap<String, Integer>();
+        for (int gid : glyphIds)
+        {
+            String name = post.getName(gid);
+            Integer macId = WGL4Names.MAC_GLYPH_NAMES_INDICES.get(name);
+            if (macId != null)
+            {
+                // the name is implicit, as it's from MacRoman
+                writeUint16(out, macId);
+            }
+            else
+            {
+                // the name will be written explicitly
+                Integer ordinal = names.get(name);
+                if (ordinal == null)
                 {
-                    writeUint16(dos, macId);
+                    ordinal = names.size();
+                    names.put(name, ordinal);
                 }
+                writeUint16(out, 258 + ordinal);
             }
         }
-        
-        for (String name : additionalNames) 
+
+        // names[numberNewGlyphs]
+        for (String name : names.keySet())
         {
-            LOG.debug("additionalName=["+name+"].");
-            byte[] buf = name.getBytes("US-ASCII");
-            writeUint8(dos,buf.length);
-            dos.write(buf);
+            byte[] buf = name.getBytes(Charset.forName("US-ASCII"));
+            writeUint8(out, buf.length);
+            out.write(buf);
         }
-        dos.flush();
-        LOG.debug("Finished table [post].");
+
+        out.flush();
         return bos.toByteArray();
     }
 
-    private byte[] buildHmtxTable() throws IOException 
+    private byte[] buildHmtxTable() throws IOException
     {
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        LOG.debug("Building table [hmtx]...");
-        HorizontalHeaderTable h = this.baseTTF.getHorizontalHeader();
-        HorizontalMetricsTable hm = this.baseTTF.getHorizontalMetrics();
+
+        HorizontalHeaderTable h = ttf.getHorizontalHeader();
+        HorizontalMetricsTable hm = ttf.getHorizontalMetrics();
         byte [] buf = new byte[4];
-        InputStream is = this.baseTTF.getOriginalData();
-        try 
+        InputStream is = ttf.getOriginalData();
+        try
         {
             is.skip(hm.getOffset());
             long lastOff = 0;
-            for (Integer glyphId : this.glyphIds) 
+            for (Integer glyphId : glyphIds)
             {
-                // offset in original file.
+                // offset in original file
                 long off;
-                if (glyphId < h.getNumberOfHMetrics()) 
+                if (glyphId < h.getNumberOfHMetrics())
                 {
                     off = glyphId * 4;
                 }
-                else 
+                else
                 {
                     off = h.getNumberOfHMetrics() * 4 + (glyphId - h.getNumberOfHMetrics()) * 2;
                 }
-                // skip over from last original offset.
-                if (off != lastOff) 
+                // skip over from last original offset
+                if (off != lastOff)
                 {
                     long nskip = off-lastOff;
-                    if (nskip != is.skip(nskip)) 
+                    if (nskip != is.skip(nskip))
                     {
                         throw new EOFException("Unexpected EOF exception parsing glyphId of hmtx table.");
                     }
                 }
-                // read left side bearings only, if we are beyond numOfHMetrics.
+                // read left side bearings only, if we are beyond numOfHMetrics
                 int n = glyphId < h.getNumberOfHMetrics() ? 4 : 2;
-                if (n != is.read(buf,0,n)) 
+                if (n != is.read(buf, 0, n))
                 {
                     throw new EOFException("Unexpected EOF exception parsing glyphId of hmtx table.");
                 }
-                bos.write(buf,0,n);
+                bos.write(buf, 0, n);
                 lastOff = off + n;
             }
-            LOG.debug("Finished table [hmtx].");
+
             return bos.toByteArray();
         }
-        finally 
+        finally
         {
             is.close();
         }
@@ -1045,66 +941,134 @@ public class TTFSubsetter
 
     /**
      * Write the subfont to the given output stream.
-     * 
+     *
      * @param os the stream used for writing
      * @throws IOException if something went wrong.
      */
-    public void writeToStream(OutputStream os) throws IOException 
+    public void writeToStream(OutputStream os) throws IOException
     {
-        LOG.debug("glyphIds=[" + glyphIds + "]");
-        LOG.debug("numGlyphs=[" + glyphIds.size() + "]");
-        while (!addCompoundReferences()) 
-        {
-        }
-        DataOutputStream dos = new DataOutputStream(os);
+        addCompoundReferences();
+
+        DataOutputStream out = new DataOutputStream(os);
         try 
         {
-            /*
-                'cmap'    character to glyph mapping
-                'glyf'    glyph data
-                'head'    font header
-                'hhea'    horizontal header
-                'OS/2'  OS/2 compatibility table.
-                'hmtx'    horizontal metrics
-                'loca'    index to location
-                'maxp'    maximum profile
-                'name'    naming
-                'post'    PostScript
-             */
-            String[] tableNames = {"OS/2","cmap","glyf","head","hhea","hmtx","loca","maxp","name","post"};
-            byte [][] tables = new byte[tableNames.length][];
-            long[] newOffsets = new long[this.glyphIds.size()+1];
-            tables[3] = this.buildHeadTable();
-            tables[4] = this.buildHheaTable();
-            tables[7] = this.buildMaxpTable();
-            tables[8] = this.buildNameTable();
-            tables[0] = this.buildOS2Table();
-            tables[2] = this.buildGlyfTable(newOffsets);
-            tables[6] = this.buildLocaTable(newOffsets);
-            tables[1] = this.buildCmapTable();
-            tables[5] = this.buildHmtxTable();
-            tables[9] = this.buildPostTable();
-            long checksum = writeFileHeader(dos,tableNames.length);
-            long offset = 12L + 16L * tableNames.length;
-            for (int i=0;i<tableNames.length;++i) 
+            long[] newLoca = new long[glyphIds.size() + 1];
+
+            // generate tables in dependency order
+            byte[] head = buildHeadTable();
+            byte[] hhea = buildHheaTable();
+            byte[] maxp = buildMaxpTable();
+            byte[] name = buildNameTable();
+            byte[] os2  = buildOS2Table();
+            byte[] glyf = buildGlyfTable(newLoca);
+            byte[] loca = buildLocaTable(newLoca);
+            byte[] cmap = buildCmapTable();
+            byte[] hmtx = buildHmtxTable();
+            byte[] post = buildPostTable();
+
+            // save to TTF in optimized order
+            Map<String, byte[]> tables = new TreeMap<String, byte[]>();
+            if (os2 != null)  { tables.put("OS/2", os2); }
+            if (cmap != null) { tables.put("cmap", cmap); }
+            if (glyf != null) { tables.put("glyf", glyf); }
+            tables.put("head", head);
+            tables.put("hhea", hhea);
+            tables.put("hmtx", hmtx);
+            if (loca != null) { tables.put("loca", loca); }
+            tables.put("maxp", maxp);
+            if (name != null) { tables.put("name", name); }
+            if (post != null) { tables.put("post", post); }
+
+            // copy all other tables
+            for (Map.Entry<String, TTFTable> entry : ttf.getTableMap().entrySet())
             {
-                checksum += writeTableHeader(dos,tableNames[i],offset,tables[i]);
-                offset += ((tables[i].length + 3) / 4) * 4;
+                String tag = entry.getKey();
+                TTFTable table = entry.getValue();
+
+                if (!tables.containsKey(tag) && (keepTables == null || keepTables.contains(tag)))
+                {
+                    tables.put(tag, ttf.getTableBytes(table));
+                }
+            }
+
+            // calculate checksum
+            long checksum = writeFileHeader(out, tables.size());
+            long offset = 12L + 16L * tables.size();
+            for (Map.Entry<String, byte[]> entry : tables.entrySet())
+            {
+                checksum += writeTableHeader(out, entry.getKey(), offset, entry.getValue());
+                offset += (entry.getValue().length + 3) / 4 * 4;
             }
             checksum = 0xB1B0AFBAL - (checksum & 0xffffffffL);
-            // correct checksumAdjustment of 'head' table.
-            tables[3][8] = (byte)(checksum >>> 24);
-            tables[3][9] = (byte)(checksum >>> 16);
-            tables[3][10] = (byte)(checksum >>> 8);
-            tables[3][11] = (byte)(checksum);
-            for (int i=0;i<tableNames.length;++i) 
+
+            // update checksumAdjustment in 'head' table
+            head[8] = (byte)(checksum >>> 24);
+            head[9] = (byte)(checksum >>> 16);
+            head[10] = (byte)(checksum >>> 8);
+            head[11] = (byte)checksum;
+            for (byte[] bytes : tables.values())
             {
-                 writeTableBody(dos,tables[i]);
+                writeTableBody(out, bytes);
             }
         }
         finally 
         {
-            dos.close();
+            out.close();
         }
     }
+
+    private void writeFixed(DataOutputStream out, double f) throws IOException
+    {
+        double ip = Math.floor(f);
+        double fp = (f-ip) * 65536.0;
+        out.writeShort((int)ip);
+        out.writeShort((int)fp);
+    }
+
+    private void writeUint32(DataOutputStream out, long l) throws IOException
+    {
+        out.writeInt((int)l);
+    }
+
+    private void writeUint16(DataOutputStream out, int i) throws IOException
+    {
+        out.writeShort(i);
+    }
+
+    private void writeSInt16(DataOutputStream out, short i) throws IOException
+    {
+        out.writeShort(i);
+    }
+
+    private void writeUint8(DataOutputStream out, int i) throws IOException
+    {
+        out.writeByte(i);
+    }
+
+    private void writeLongDateTime(DataOutputStream out, Calendar calendar) throws IOException
+    {
+        // inverse operation of TTFDataStream.readInternationalDate()
+        GregorianCalendar cal = new GregorianCalendar( 1904, 0, 1 );
+        long millisFor1904 = cal.getTimeInMillis();
+        long secondsSince1904 = (calendar.getTimeInMillis() - millisFor1904) / 1000L;
+        out.writeLong(secondsSince1904);
+    }
+
+    private long toUInt32(int high, int low)
+    {
+        return ((long)high & 0xffffL) << 16 | (long)low & 0xffffL;
+    }
+
+    private long toUInt32(byte[] bytes)
+    {
+        return ((long)bytes[0] & 0xffL) << 24 |
+               ((long)bytes[1] & 0xffL) << 16 |
+               ((long)bytes[2] & 0xffL) << 8 |
+                (long)bytes[3] & 0xffL;
+    }
+
+    private int log2(int num)
+    {
+        return (int)Math.round(Math.log(num) / Math.log(2));
+    }
 }

Modified: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TrueTypeFont.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TrueTypeFont.java?rev=1645796&r1=1645795&r2=1645796&view=diff
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TrueTypeFont.java (original)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TrueTypeFont.java Mon Dec 15 23:54:55 2014
@@ -107,6 +107,23 @@ public class TrueTypeFont implements Typ
     {
         return tables;
     }
+
+    /**
+     * Returns the war bytes of the given table.
+     */
+    public synchronized byte[] getTableBytes(TTFTable table) throws IOException
+    {
+        // save current position
+        long currentPosition = data.getCurrentPosition();
+        data.seek(table.getOffset());
+
+        // read all data
+        byte[] bytes = data.read((int)table.getLength());
+
+        // restore current position
+        data.seek(currentPosition);
+        return bytes;
+    }
     
     /**
      * This will get the naming table for the true type font.

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/edit/PDPageContentStream.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/edit/PDPageContentStream.java?rev=1645796&r1=1645795&r2=1645796&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/edit/PDPageContentStream.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/edit/PDPageContentStream.java Mon Dec 15 23:54:55 2014
@@ -25,9 +25,13 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.text.NumberFormat;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 
+import java.util.Map;
+import java.util.Set;
 import java.util.Stack;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -42,7 +46,9 @@ import org.apache.pdfbox.pdmodel.PDPage;
 import org.apache.pdfbox.pdmodel.PDResources;
 import org.apache.pdfbox.pdmodel.common.COSStreamArray;
 import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.font.PDCIDFontType2;
 import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType0Font;
 import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
 import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
 import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
@@ -129,8 +135,11 @@ public class PDPageContentStream impleme
     // instance variables
     private OutputStream output;
     private PDResources resources;
+
     private boolean inTextMode = false;
     private final Stack<PDFont> fontStack = new Stack<PDFont>();
+    private final List<PDFont> fontsToSubset = new ArrayList<PDFont>();
+    private final Map<PDFont, Set<Integer>> subsetCodePoints = new HashMap<PDFont, Set<Integer>>();
 
     private PDColorSpace currentStrokingColorSpace = PDDeviceGray.INSTANCE;
     private PDColorSpace currentNonStrokingColorSpace = PDDeviceGray.INSTANCE;
@@ -312,6 +321,19 @@ public class PDPageContentStream impleme
      */
     public void setFont(PDFont font, float fontSize) throws IOException
     {
+        setFont(font, fontSize, true);
+    }
+
+    /**
+     * Set the font to draw text with.
+     *
+     * @param font The font to use.
+     * @param fontSize The font size to draw the text.
+     * @param embedSubset True to subset this font when embedding it. Affects all uses of this font.
+     * @throws IOException If there is an error writing the font information.
+     */
+    public void setFont(PDFont font, float fontSize, boolean embedSubset) throws IOException
+    {
         if (fontStack.isEmpty())
         {
             fontStack.add(font);
@@ -321,6 +343,16 @@ public class PDPageContentStream impleme
             fontStack.setElementAt(font, fontStack.size() - 1);
         }
 
+        if (embedSubset)
+        {
+            fontsToSubset.add(font);
+            subsetCodePoints.put(font, new HashSet<Integer>());
+        }
+        else
+        {
+            fontsToSubset.remove(font);
+        }
+
         appendCOSName(resources.add(font));
         appendRawCommands(SPACE);
         appendRawCommands(fontSize);
@@ -639,6 +671,20 @@ public class PDPageContentStream impleme
         }
 
         PDFont font = fontStack.peek();
+
+        // Unicode code points to keep when subsetting
+        Set<Integer> codePoints = subsetCodePoints.get(font);
+        if (codePoints != null)
+        {
+            for (int offset = 0; offset < text.length(); )
+            {
+                int codePoint = text.codePointAt(offset);
+                codePoints.add(codePoint);
+                offset += Character.charCount(codePoint);
+            }
+
+        }
+
         COSWriter.writeString(font.encode(text), output);
         appendRawCommands(SPACE);
         appendRawCommands(SHOW_TEXT);
@@ -1618,6 +1664,17 @@ public class PDPageContentStream impleme
      */
     public void close() throws IOException
     {
+        for (PDFont font : fontsToSubset)
+        {
+            // currently we only support subsetting Type0/CIDFontType2 fonts
+            if (font instanceof PDType0Font)
+            {
+                if (((PDType0Font)font).getDescendantFont() instanceof PDCIDFontType2)
+                {
+                    font.subset(subsetCodePoints.get(font));
+                }
+            }
+        }
         output.close();
         currentNonStrokingColorSpace = null;
         currentStrokingColorSpace = null;

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2.java?rev=1645796&r1=1645795&r2=1645796&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2.java Mon Dec 15 23:54:55 2014
@@ -351,7 +351,7 @@ public class PDCIDFontType2 extends PDCI
         if (cmap == null)
         {
             cmap = cmapTable.getSubtable(CmapTable.PLATFORM_WINDOWS,
-                                         CmapTable.ENCODING_WIN_UNICODE);
+                                         CmapTable.ENCODING_WIN_UNICODE_BMP);
         }
         if (cmap == null)
         {