You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by le...@apache.org on 2012/11/26 19:47:36 UTC

svn commit: r1413777 - in /pdfbox/trunk/fontbox: ./ src/main/java/org/apache/fontbox/encoding/ src/main/java/org/apache/fontbox/ttf/

Author: lehmi
Date: Mon Nov 26 18:47:35 2012
New Revision: 1413777

URL: http://svn.apache.org/viewvc?rev=1413777&view=rev
Log:
PDFBOX-922: added TTFSubFont support based on code written by Wolfgang Glas

Added:
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubFont.java   (with props)
Modified:
    pdfbox/trunk/fontbox/pom.xml
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/encoding/Encoding.java
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/encoding/MacRomanEncoding.java
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/OS2WindowsMetricsTable.java
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/PostScriptTable.java

Modified: pdfbox/trunk/fontbox/pom.xml
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/pom.xml?rev=1413777&r1=1413776&r2=1413777&view=diff
==============================================================================
--- pdfbox/trunk/fontbox/pom.xml (original)
+++ pdfbox/trunk/fontbox/pom.xml Mon Nov 26 18:47:35 2012
@@ -39,6 +39,11 @@
 
   <dependencies>
     <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging</artifactId>
+      <version>1.1.1</version>
+    </dependency>
+    <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <version>4.8.1</version>

Modified: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/encoding/Encoding.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/encoding/Encoding.java?rev=1413777&r1=1413776&r2=1413777&view=diff
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/encoding/Encoding.java (original)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/encoding/Encoding.java Mon Nov 26 18:47:35 2012
@@ -27,11 +27,77 @@ import java.util.Map;
  * @author Ben Litchfield
  * @version $Revision: 1.1 $
  * 
- * @deprecated no longer needed by fontbox
  * 
  */
 public abstract class Encoding
 {
+    /**
+     * The number of standard mac glyph names.
+     */
+    public static final int NUMBER_OF_MAC_GLYPHS = 258;
+    
+    /**
+     * The 258 standard mac glyph names a used in 'post' format 1 and 2.
+     */
+    public static final String[] MAC_GLYPH_NAMES = new String[] 
+    { 
+        ".notdef",".null", "nonmarkingreturn", "space", "exclam", "quotedbl",
+            "numbersign", "dollar", "percent", "ampersand", "quotesingle",
+            "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen",
+            "period", "slash", "zero", "one", "two", "three", "four", "five",
+            "six", "seven", "eight", "nine", "colon", "semicolon", "less",
+            "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F",
+            "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S",
+            "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash",
+            "bracketright", "asciicircum", "underscore", "grave", "a", "b",
+            "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
+            "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft",
+            "bar", "braceright", "asciitilde", "Adieresis", "Aring",
+            "Ccedilla", "Eacute", "Ntilde", "Odieresis", "Udieresis", "aacute",
+            "agrave", "acircumflex", "adieresis", "atilde", "aring",
+            "ccedilla", "eacute", "egrave", "ecircumflex", "edieresis",
+            "iacute", "igrave", "icircumflex", "idieresis", "ntilde", "oacute",
+            "ograve", "ocircumflex", "odieresis", "otilde", "uacute", "ugrave",
+            "ucircumflex", "udieresis", "dagger", "degree", "cent", "sterling",
+            "section", "bullet", "paragraph", "germandbls", "registered",
+            "copyright", "trademark", "acute", "dieresis", "notequal", "AE",
+            "Oslash", "infinity", "plusminus", "lessequal", "greaterequal",
+            "yen", "mu", "partialdiff", "summation", "product", "pi",
+            "integral", "ordfeminine", "ordmasculine", "Omega", "ae", "oslash",
+            "questiondown", "exclamdown", "logicalnot", "radical", "florin",
+            "approxequal", "Delta", "guillemotleft", "guillemotright",
+            "ellipsis", "nonbreakingspace", "Agrave", "Atilde", "Otilde", "OE",
+            "oe", "endash", "emdash", "quotedblleft", "quotedblright",
+            "quoteleft", "quoteright", "divide", "lozenge", "ydieresis",
+            "Ydieresis", "fraction", "currency", "guilsinglleft",
+            "guilsinglright", "fi", "fl", "daggerdbl", "periodcentered",
+            "quotesinglbase", "quotedblbase", "perthousand", "Acircumflex",
+            "Ecircumflex", "Aacute", "Edieresis", "Egrave", "Iacute",
+            "Icircumflex", "Idieresis", "Igrave", "Oacute", "Ocircumflex",
+            "apple", "Ograve", "Uacute", "Ucircumflex", "Ugrave", "dotlessi",
+            "circumflex", "tilde", "macron", "breve", "dotaccent", "ring",
+            "cedilla", "hungarumlaut", "ogonek", "caron", "Lslash", "lslash",
+            "Scaron", "scaron", "Zcaron", "zcaron", "brokenbar", "Eth", "eth",
+            "Yacute", "yacute", "Thorn", "thorn", "minus", "multiply",
+            "onesuperior", "twosuperior", "threesuperior", "onehalf",
+            "onequarter", "threequarters", "franc", "Gbreve", "gbreve",
+            "Idotaccent", "Scedilla", "scedilla", "Cacute", "cacute", "Ccaron",
+            "ccaron", "dcroat" 
+    };
+
+    /**
+     * The indices of the standard mac glyph names.
+     */
+    public static Map<String,Integer> MAC_GLYPH_NAMES_INDICES;
+    
+    static 
+    {
+        MAC_GLYPH_NAMES_INDICES = new HashMap<String,Integer>();
+        for (int i=0;i<Encoding.NUMBER_OF_MAC_GLYPHS;++i) 
+        {
+            MAC_GLYPH_NAMES_INDICES.put(Encoding.MAC_GLYPH_NAMES[i],i);
+        }
+    }
 
     /**
      * Identifies a non-mapped character. 

Modified: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/encoding/MacRomanEncoding.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/encoding/MacRomanEncoding.java?rev=1413777&r1=1413776&r2=1413777&view=diff
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/encoding/MacRomanEncoding.java (original)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/encoding/MacRomanEncoding.java Mon Nov 26 18:47:35 2012
@@ -16,17 +16,22 @@
  */
 package org.apache.fontbox.encoding;
 
+
 /**
- * This is an interface to a text encoder.
+ * This is the MacRomanEncoding.
  *
  * @author Ben Litchfield
- * @version $Revision: 1.1 $
  * 
- * @deprecated no longer needed by fontbox
  */
 public class MacRomanEncoding extends Encoding
 {
     /**
+     * Singleton instance of this class.
+     *
+     */
+    public static final MacRomanEncoding INSTANCE = new MacRomanEncoding();
+    
+    /**
      * Constructor.
      */
     public MacRomanEncoding()

Modified: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/OS2WindowsMetricsTable.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/OS2WindowsMetricsTable.java?rev=1413777&r1=1413776&r2=1413777&view=diff
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/OS2WindowsMetricsTable.java (original)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/OS2WindowsMetricsTable.java Mon Nov 26 18:47:35 2012
@@ -630,7 +630,7 @@ public class OS2WindowsMetricsTable exte
     private long unicodeRange2;
     private long unicodeRange3;
     private long unicodeRange4;
-    private String achVendId;
+    private String achVendId = "XXXX";
     private int fsSelection;
     private int firstCharIndex;
     private int lastCharIndex;

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=1413777&r1=1413776&r2=1413777&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 Nov 26 18:47:35 2012
@@ -17,6 +17,7 @@
 package org.apache.fontbox.ttf;
 
 import java.io.IOException;
+import org.apache.fontbox.encoding.Encoding;
 
 /**
  * A table in a true type font.
@@ -37,56 +38,6 @@ public class PostScriptTable extends TTF
     private long maxMemType1;
     private String[] glyphNames = null;
     
-    /**
-     * The 258 standard mac glyph names a used in 'post' format 1 and 2.
-     */
-    private static final int NUMBER_OF_MAC_GLYPHS = 258;
-    
-    private static final String[] MAC_GLYPH_NAMES = new String[] 
-    { 
-        ".notdef",".null", "nonmarkingreturn", "space", "exclam", "quotedbl",
-            "numbersign", "dollar", "percent", "ampersand", "quotesingle",
-            "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen",
-            "period", "slash", "zero", "one", "two", "three", "four", "five",
-            "six", "seven", "eight", "nine", "colon", "semicolon", "less",
-            "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F",
-            "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S",
-            "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash",
-            "bracketright", "asciicircum", "underscore", "grave", "a", "b",
-            "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
-            "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft",
-            "bar", "braceright", "asciitilde", "Adieresis", "Aring",
-            "Ccedilla", "Eacute", "Ntilde", "Odieresis", "Udieresis", "aacute",
-            "agrave", "acircumflex", "adieresis", "atilde", "aring",
-            "ccedilla", "eacute", "egrave", "ecircumflex", "edieresis",
-            "iacute", "igrave", "icircumflex", "idieresis", "ntilde", "oacute",
-            "ograve", "ocircumflex", "odieresis", "otilde", "uacute", "ugrave",
-            "ucircumflex", "udieresis", "dagger", "degree", "cent", "sterling",
-            "section", "bullet", "paragraph", "germandbls", "registered",
-            "copyright", "trademark", "acute", "dieresis", "notequal", "AE",
-            "Oslash", "infinity", "plusminus", "lessequal", "greaterequal",
-            "yen", "mu", "partialdiff", "summation", "product", "pi",
-            "integral", "ordfeminine", "ordmasculine", "Omega", "ae", "oslash",
-            "questiondown", "exclamdown", "logicalnot", "radical", "florin",
-            "approxequal", "Delta", "guillemotleft", "guillemotright",
-            "ellipsis", "nonbreakingspace", "Agrave", "Atilde", "Otilde", "OE",
-            "oe", "endash", "emdash", "quotedblleft", "quotedblright",
-            "quoteleft", "quoteright", "divide", "lozenge", "ydieresis",
-            "Ydieresis", "fraction", "currency", "guilsinglleft",
-            "guilsinglright", "fi", "fl", "daggerdbl", "periodcentered",
-            "quotesinglbase", "quotedblbase", "perthousand", "Acircumflex",
-            "Ecircumflex", "Aacute", "Edieresis", "Egrave", "Iacute",
-            "Icircumflex", "Idieresis", "Igrave", "Oacute", "Ocircumflex",
-            "apple", "Ograve", "Uacute", "Ucircumflex", "Ugrave", "dotlessi",
-            "circumflex", "tilde", "macron", "breve", "dotaccent", "ring",
-            "cedilla", "hungarumlaut", "ogonek", "caron", "Lslash", "lslash",
-            "Scaron", "scaron", "Zcaron", "zcaron", "brokenbar", "Eth", "eth",
-            "Yacute", "yacute", "Thorn", "thorn", "minus", "multiply",
-            "onesuperior", "twosuperior", "threesuperior", "onehalf",
-            "onequarter", "threequarters", "franc", "Gbreve", "gbreve",
-            "Idotaccent", "Scedilla", "scedilla", "Cacute", "cacute", "Ccaron",
-            "ccaron", "dcroat" 
-    };
 
     /**
      * A tag that identifies this table type.
@@ -119,8 +70,8 @@ public class PostScriptTable extends TTF
              * This TrueType font file contains exactly the 258 glyphs in the standard 
              * Macintosh TrueType.
              */
-            glyphNames = new String[NUMBER_OF_MAC_GLYPHS];
-            System.arraycopy(MAC_GLYPH_NAMES, 0, glyphNames, 0, NUMBER_OF_MAC_GLYPHS);
+            glyphNames = new String[Encoding.NUMBER_OF_MAC_GLYPHS];
+            System.arraycopy(Encoding.MAC_GLYPH_NAMES, 0, glyphNames, 0, Encoding.NUMBER_OF_MAC_GLYPHS);
         }
         else if( formatType == 2.0f )
         {
@@ -140,10 +91,10 @@ public class PostScriptTable extends TTF
                 }
             }
             String[] nameArray = null;
-            if( maxIndex >= NUMBER_OF_MAC_GLYPHS )
+            if( maxIndex >= Encoding.NUMBER_OF_MAC_GLYPHS )
             {
-                nameArray = new String[ maxIndex-NUMBER_OF_MAC_GLYPHS +1 ];
-                for( int i=0; i<maxIndex-NUMBER_OF_MAC_GLYPHS+1; i++ )
+                nameArray = new String[ maxIndex-Encoding.NUMBER_OF_MAC_GLYPHS +1 ];
+                for( int i=0; i<maxIndex-Encoding.NUMBER_OF_MAC_GLYPHS+1; i++ )
                 {
                     int numberOfChars = data.read();
                     nameArray[i]=data.readString( numberOfChars );
@@ -152,13 +103,13 @@ public class PostScriptTable extends TTF
             for( int i=0; i<numGlyphs; i++ )
             {
                 int index = glyphNameIndex[i];
-                if( index < NUMBER_OF_MAC_GLYPHS )
+                if( index < Encoding.NUMBER_OF_MAC_GLYPHS )
                 {
-                    glyphNames[i] = MAC_GLYPH_NAMES[index];
+                    glyphNames[i] = Encoding.MAC_GLYPH_NAMES[index];
                 }
-                else if( index >= NUMBER_OF_MAC_GLYPHS && index <= 32767 )
+                else if( index >= Encoding.NUMBER_OF_MAC_GLYPHS && index <= 32767 )
                 {
-                    glyphNames[i] = nameArray[index-NUMBER_OF_MAC_GLYPHS];
+                    glyphNames[i] = nameArray[index-Encoding.NUMBER_OF_MAC_GLYPHS];
                 }
                 else
                 {
@@ -179,7 +130,7 @@ public class PostScriptTable extends TTF
             glyphNames = new String[glyphNameIndex.length];
             for( int i=0; i<glyphNames.length; i++)
             {
-                String name = MAC_GLYPH_NAMES[glyphNameIndex[i]];
+                String name = Encoding.MAC_GLYPH_NAMES[glyphNameIndex[i]];
                 if( name != null )
                 {
                     glyphNames[i] = name;

Added: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubFont.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubFont.java?rev=1413777&view=auto
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubFont.java (added)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubFont.java Mon Nov 26 18:47:35 2012
@@ -0,0 +1,1089 @@
+/*
+ * 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.
+ */
+package org.apache.fontbox.ttf;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.apache.fontbox.encoding.Encoding;
+import org.apache.fontbox.encoding.MacRomanEncoding;
+
+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
+ */
+public class TTFSubFont 
+{
+
+    private static final Log LOG = LogFactory.getLog(TTFSubFont.class);
+    private static final byte[] PAD_BUF = new byte[] {0,0,0};
+    
+    private final TrueTypeFont baseTTF;
+    private final String nameSuffix;
+    private final CMAPEncodingEntry 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;
+    
+    /**
+     * 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
+     * 
+     */
+    public TTFSubFont(TrueTypeFont baseFont, String suffix) 
+    {
+        baseTTF = baseFont;
+        nameSuffix = suffix;
+        characters = new TreeMap<Integer, Integer>();
+        glyphIds = new TreeSet<Integer>();
+        
+        CMAPEncodingEntry[] cmaps = this.baseTTF.getCMAP().getCmaps();
+        CMAPEncodingEntry unicodeCmap = null;
+        
+        for (CMAPEncodingEntry 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);
+    }
+    
+    /**
+     * Add the given charcode to the subfpont.
+     * 
+     * @param charCode the charCode to be added
+     * 
+     */
+    public void addCharCode(int charCode) 
+    {
+        Integer gid = Integer.valueOf(baseCmap.getGlyphId(charCode));
+        if (charCode == 0 || gid != 0) 
+        {
+            characters.put(charCode,gid);
+            glyphIds.add(gid);
+        }
+    }
+
+    private static int log2i(int i) 
+    {
+        int ret = -1;
+        if ((i & 0xffff0000) != 0) 
+        {
+            i >>>= 16;
+            ret += 16;
+        }
+        if ((i & 0xff00) != 0) 
+        {
+            i >>>= 8;
+            ret += 8;
+        }
+        if ((i & 0xf0) != 0) 
+        {
+            i >>>= 4;
+            ret += 4;
+        }
+        if ((i & 0xc) != 0) 
+        {
+            i >>>= 2;
+            ret += 2;
+        }
+        if ((i & 0x2) != 0) 
+        {
+            i >>>= 1;
+            ++ret;
+        }
+        if (i != 0) 
+        {
+            ++ret;
+        }
+        return ret;
+    }
+    
+    private static long buildUint32(int high, int low) 
+    {
+        return ((((long)high)&0xffffL) << 16) | (((long)low)&0xffffL);
+    }
+    
+    private static long buildUint32(byte[] bytes) 
+    {
+        return ((((long)bytes[3])&0xffL) << 24) |
+                ((((long)bytes[2])&0xffL) << 16) |
+                ((((long)bytes[1])&0xffL) << 8) |
+                (((long)bytes[0])&0xffL);
+    }
+
+    /**
+     * @param dos 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 
+    {
+        dos.writeInt(0x00010000);
+        dos.writeShort(nTables);
+        
+        int mask = Integer.highestOneBit(nTables);
+        int searchRange = mask * 16;
+        dos.writeShort(searchRange);
+        
+        int entrySelector=log2i(mask);
+    
+        dos.writeShort(entrySelector);
+        
+        // numTables * 16 - searchRange
+        int last = 16 * nTables - searchRange;
+        dos.writeShort(last);
+        
+        return 0x00010000L + buildUint32(nTables,searchRange) + buildUint32(entrySelector,last);
+    }
+        
+    private static long writeTableHeader(DataOutputStream dos, String tag, long offset, byte[] bytes) 
+            throws IOException 
+    {
+        
+        int n = bytes.length;
+        int nup;
+        long checksum = 0L;
+        
+        for (nup=0;nup<n;++nup) 
+        {
+            checksum += (((long)bytes[nup]) & 0xffL)<<(24-(nup%4)*8);
+        }
+        
+        checksum &= 0xffffffffL;
+        
+        if ((n%4)!=0) 
+        {
+            nup += 4-(n%4);
+        }
+        
+        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);
+        
+        return buildUint32(tagbytes) + checksum + offset + bytes.length;
+    }
+    
+    private static void writeTableBody(OutputStream os, byte[] bytes) throws IOException 
+    {
+        int n = bytes.length;
+        os.write(bytes);
+        if ((n%4)!=0) 
+        {
+            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 
+    {
+        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].");
+
+        return bos.toByteArray();
+    }
+    
+    private byte[] buildHheaTable() throws IOException 
+    {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        DataOutputStream dos = new DataOutputStream(bos);
+
+        LOG.debug("Building table [hhea]...");
+
+        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].");
+        return bos.toByteArray();
+    }
+
+    private static boolean replicateNameRecord(NameRecord nr) 
+    {
+        return nr.getPlatformId() == NameRecord.PLATFORM_WINDOWS 
+                && nr.getPlatformEncodingId() == NameRecord.PLATFORM_ENCODING_WINDOWS_UNICODE 
+                && nr.getLanguageId() == 0 
+                && nr.getNameId() >= 0 && nr.getNameId() < 7;
+    }
+    
+    private byte[] buildNameTable() throws IOException 
+    {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        DataOutputStream dos = new DataOutputStream(bos);
+        
+        LOG.debug("Building table [name]...");
+
+        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.PLATFORM_ENCODING_WINDOWS_UNICODE);
+            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.PLATFORM_ENCODING_WINDOWS_UNICODE);
+            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) 
+        {
+            NameRecord nr = nameRecords.get(i);
+            if (replicateNameRecord(nr)) 
+            {
+                LOG.debug("Writing name record ["+nr.getNameId()+"], ["+nr.getString()+"],");
+                ++nrep;
+            }
+        }
+        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) 
+        {
+            NameRecord nr = nameRecords.get(i);
+            if (replicateNameRecord(nr)) 
+            {
+                int platform = nr.getPlatformId();
+                int encoding = nr.getPlatformEncodingId();
+                String charset = "ISO-8859-1";
+                if( platform == 3 && encoding == 1 )
+                {
+                    charset = "UTF-16BE";
+                }
+                else if( platform == 2 )
+                {
+                    if( encoding == 0 )
+                    {
+                        charset = "US-ASCII";
+                    }
+                    else if( encoding == 1 )
+                    {
+                        //not sure is this is correct??
+                        charset = "UTF16-BE";
+                    }
+                    else if( encoding == 2 )
+                    {
+                        charset = "ISO-8859-1";
+                    }
+                }
+                String value = nr.getString();
+                if (nr.getNameId() == 6 && this.nameSuffix != null) 
+                {
+                    value += this.nameSuffix;
+                }
+                names[j] = value.getBytes(charset);
+                ++j;
+            }
+        }
+        
+        int offset = 0;
+        j = 0;
+        for (int i=0;i<numberOfRecords;++i) 
+        {
+            NameRecord nr = nameRecords.get(i);
+            if (replicateNameRecord(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);
+                offset += names[j].length;
+                ++j;
+            }
+        }
+        
+        for (int i=0;i<nrep;++i) 
+        {
+            dos.write(names[i]);
+        }
+        dos.flush();
+        LOG.debug("Finished table [name].");
+        return bos.toByteArray();
+    }
+    
+    private byte[] buildMaxpTable() throws IOException 
+    {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        DataOutputStream dos = new DataOutputStream(bos);
+
+        LOG.debug("Building table [maxp]...");
+
+        MaximumProfileTable p = this.baseTTF.getMaximumProfile();
+
+        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,p.getMaxInstructionDefs());
+        writeUint16(dos,p.getMaxStackElements());
+        writeUint16(dos,p.getMaxSizeOfInstructions());
+        writeUint16(dos,p.getMaxComponentElements());
+        writeUint16(dos,p.getMaxComponentDepth());
+
+        dos.flush();
+        LOG.debug("Finished table [maxp].");
+        return bos.toByteArray();
+    }
+    
+    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();
+        }
+        
+        LOG.debug("Building table [OS/2]...");
+
+        writeUint16(dos,1);
+        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,os2.getUnicodeRange1());
+        writeUint32(dos,os2.getUnicodeRange2());
+        writeUint32(dos,os2.getUnicodeRange3());
+        writeUint32(dos,os2.getUnicodeRange4());
+        
+        dos.write(os2.getAchVendId().getBytes("ISO-8859-1"));
+
+        Iterator<Entry<Integer, Integer>> it = characters.entrySet().iterator();
+        it.next();
+        Entry<Integer, Integer> first = it.next();
+        
+        writeUint16(dos,os2.getFsSelection());
+        writeUint16(dos,first.getKey());
+        writeUint16(dos,characters.lastKey());
+        writeUint16(dos,os2.getTypoAscender());
+        writeUint16(dos,os2.getTypoDescender());
+        writeUint16(dos,os2.getWinAscent());
+        writeUint16(dos,os2.getWinDescent());
+        
+        dos.flush();
+        LOG.debug("Finished table [OS/2].");
+        return bos.toByteArray();
+    }
+    
+    private byte[] buildLocaTable() throws IOException 
+    {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        DataOutputStream dos = new DataOutputStream(bos);
+
+        LOG.debug("Building table [loca]...");
+
+        long[] offsets = this.baseTTF.getIndexToLocation().getOffsets();
+        long offset = 0L;
+        for (Integer glyphId : this.glyphIds) 
+        {
+            LOG.debug(String.format("glyphId={%d},offset={%08x}",glyphId,offset));
+            writeUint32(dos,offset);
+            offset += offsets[glyphId.intValue()+1] - offsets[glyphId.intValue()];
+        }
+        writeUint32(dos,offset);
+        dos.flush();
+        LOG.debug("Finished table [loca].");
+        return bos.toByteArray();
+    }
+
+    private boolean addCompoundReferences() throws IOException 
+    {
+        GlyphTable g = this.baseTTF.getGlyph();
+        long[] offsets = this.baseTTF.getIndexToLocation().getOffsets();
+        InputStream is = this.baseTTF.getOriginalData();
+        Set<Integer> glyphIdsToAdd = null;
+        try 
+        {
+            is.skip(g.getOffset());
+            long lastOff = 0L;
+            for (Integer glyphId : this.glyphIds) 
+            {
+                long offset = offsets[glyphId.intValue()];
+                long len = offsets[glyphId.intValue()+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) 
+                {
+                    int off = 2*5;
+                    int flags = 0;
+                    do 
+                    {
+                        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)) 
+                        {
+                            LOG.debug("Adding referenced glyph "+ogid+" of compound glyph "+glyphId);
+                            if (glyphIdsToAdd == null) 
+                            {
+                                glyphIdsToAdd = new TreeSet<Integer>();
+                            }
+                            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;
+                        }
+                        
+                        // MORE_COMPONENTS
+                    } 
+                    while ((flags & (1 << 5)) != 0);
+                    
+                }
+                lastOff = offsets[glyphId.intValue()+1];
+            }
+        }
+        finally 
+        {
+            is.close();
+        }
+        if (glyphIdsToAdd != null) 
+        {
+            this.glyphIds.addAll(glyphIdsToAdd);
+        }
+        return glyphIdsToAdd == null;
+    }
+    
+    private byte[] buildGlyfTable() 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 
+        {
+            is.skip(g.getOffset());
+            long lastOff = 0L;
+            for (Integer glyphId : this.glyphIds) 
+            {
+                long offset = offsets[glyphId.intValue()];
+                long len = offsets[glyphId.intValue()+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) 
+                {
+                    LOG.debug("Compound glyph "+glyphId);
+                    int off = 2*5;
+                    int flags = 0;
+                    do 
+                    {
+                        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)) 
+                        {
+                            this.glyphIds.add(ogid);
+                        }
+                        int ngid = this.getNewGlyphId(ogid);
+                        if (LOG.isDebugEnabled()) 
+                        {
+                            LOG.debug(String.format("mapped glyph  %d to %d in compound reference (flags=%04x)",
+                                    ogid,ngid,flags));
+                        }
+                        buf[off]   = (byte)(ngid >>> 8);
+                        buf[off+1] = (byte)ngid;
+                        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);
+                }
+                bos.write(buf);
+                lastOff = offsets[glyphId.intValue()+1];
+            }
+        }
+        finally 
+        {
+            is.close();
+        }
+        LOG.debug("Finished table [glyf].");
+        return bos.toByteArray();
+    }
+
+    private int getNewGlyphId(Integer oldGid) 
+    {
+        return this.glyphIds.headSet(oldGid).size();
+    }
+    
+    private byte[] buildCmapTable() throws IOException 
+    {
+        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();
+        it.next();
+        Entry<Integer, Integer> lastChar = it.next();
+        Entry<Integer, Integer> prevChar = lastChar;
+        int lastGid = this.getNewGlyphId(lastChar.getValue());
+
+        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()) 
+            {
+                // Don't emit ranges, which map to the undef glyph, the
+                // undef glyph is emitted a the very last segment.
+                if (lastGid != 0) 
+                {
+                    startCode[nseg] = lastChar.getKey();
+                    endCode[nseg] = prevChar.getKey();
+                    idDelta[nseg] = lastGid - lastChar.getKey();
+                    ++nseg;
+                }
+                // shorten ranges which start with undef by one.
+                else if (!lastChar.getKey().equals(prevChar.getKey())) 
+                {
+                    startCode[nseg] = lastChar.getKey()+1;
+                    endCode[nseg] = prevChar.getKey();
+                    idDelta[nseg] = lastGid - lastChar.getKey();
+                    ++nseg;
+                }
+                lastGid = curGid;
+                lastChar = curChar;
+            }
+            prevChar = curChar;
+        }
+        // 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 * (8*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);
+        }
+        LOG.debug("Finished table [cmap].");
+        return bos.toByteArray();
+    }
+
+    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) 
+        {
+            Encoding enc = MacRomanEncoding.INSTANCE;
+            int[] gidToUC = this.baseCmap.getGlyphIdToCharacterCode();
+            for (Integer glyphId : this.glyphIds) 
+            {
+                int uc = gidToUC[glyphId.intValue()];
+                String name = null;
+                if (uc < 0x8000) 
+                {
+                    try 
+                    {
+                        name = enc.getNameFromCharacter((char)uc);
+                    }
+                    catch (IOException e) 
+                    {
+                        // TODO
+                    }
+                }
+                if (name == null) 
+                {
+                    name = String.format(Locale.ENGLISH,"uni%04X",uc);
+                }
+                Integer macId = Encoding.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.intValue()+258);
+                }
+                else 
+                {
+                    writeUint16(dos,macId.intValue());
+                }
+            }
+        }
+        else 
+        { 
+            for (Integer glyphId : this.glyphIds) 
+            {
+                String name = glyphNames[glyphId.intValue()];
+                Integer macId = Encoding.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.intValue()+258);
+                }
+                else 
+                {
+                    writeUint16(dos,macId.intValue());
+                }
+            }
+        }
+        
+        for (String name : additionalNames) 
+        {
+            LOG.debug("additionalName=["+name+"].");
+            byte[] buf = name.getBytes("US-ASCII");
+            writeUint8(dos,buf.length);
+            dos.write(buf);
+        }
+        dos.flush();
+        LOG.debug("Finished table [post].");
+        return bos.toByteArray();
+    }
+
+    private byte[] buildHmtxTable() throws IOException 
+    {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        LOG.debug("Building table [hmtx]...");
+        HorizontalHeaderTable h = this.baseTTF.getHorizontalHeader();
+        HorizontalMetricsTable hm = this.baseTTF.getHorizontalMetrics();
+        byte [] buf = new byte[4];
+        InputStream is = this.baseTTF.getOriginalData();
+        try 
+        {
+            is.skip(hm.getOffset());
+            long lastOff = 0;
+            for (Integer glyphId : this.glyphIds) 
+            {
+                // offset in original file.
+                long off;
+                if (glyphId < h.getNumberOfHMetrics()) 
+                {
+                    off = glyphId * 4;
+                }
+                else 
+                {
+                    off = h.getNumberOfHMetrics() * 4 + (glyphId - h.getNumberOfHMetrics()) * 2;
+                }
+                // skip over from last original offset.
+                if (off != lastOff) 
+                {
+                    long nskip = off-lastOff;
+                    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.
+                int n = glyphId < h.getNumberOfHMetrics() ? 4 : 2;
+                if (n != is.read(buf,0,n)) 
+                {
+                    throw new EOFException("Unexpected EOF exception parsing glyphId of hmtx table.");
+                }
+                bos.write(buf,0,n);
+                lastOff = off + n;
+            }
+            LOG.debug("Finished table [hmtx].");
+            return bos.toByteArray();
+        }
+        finally 
+        {
+            is.close();
+        }
+    }
+
+    /**
+     * 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 
+    {
+        LOG.debug("glyphIds=[" + glyphIds + "]");
+        LOG.debug("numGlyphs=[" + glyphIds.size() + "]");
+        while (!addCompoundReferences()) 
+        {
+        }
+        DataOutputStream dos = 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 = {"head","hhea","maxp","name","OS/2","loca","cmap","hmtx","glyf","post"};
+            byte [][] tables = new byte[tableNames.length][];
+            tables[0] = this.buildHeadTable();
+            tables[1] = this.buildHheaTable();
+            tables[2] = this.buildMaxpTable();
+            tables[3] = this.buildNameTable();
+            tables[4] = this.buildOS2Table();
+            tables[5] = this.buildLocaTable();
+            tables[6] = this.buildCmapTable();
+            tables[7] = this.buildHmtxTable();
+            tables[8] = this.buildGlyfTable();
+            tables[9] = this.buildPostTable();
+
+            long checksum = writeFileHeader(dos,tableNames.length);
+            long offset = 12L + 16L * tableNames.length;
+            for (int i=0;i<tableNames.length;++i) 
+            {
+                checksum += writeTableHeader(dos,tableNames[i],offset,tables[i]);
+                offset += ((tables[i].length + 3) / 4) * 4;
+            }
+            checksum = 0xB1B0AFBAL - (checksum & 0xffffffffL);
+            // correct checksumAdjustment of 'head' table.
+            tables[0][8] = (byte)(checksum >>> 24);
+            tables[0][9] = (byte)(checksum >>> 16);
+            tables[0][10] = (byte)(checksum >>> 8);
+            tables[0][11] = (byte)(checksum);
+            for (int i=0;i<tableNames.length;++i) 
+            {
+                 writeTableBody(dos,tables[i]);
+            }
+        }
+        finally 
+        {
+            dos.close();
+        }
+    }
+}

Propchange: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubFont.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubFont.java
------------------------------------------------------------------------------
    svn:executable = *