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/08/30 04:27:00 UTC

svn commit: r1621411 [4/7] - in /pdfbox/trunk: ./ examples/src/main/java/org/apache/pdfbox/examples/pdmodel/ fontbox/src/main/java/org/apache/fontbox/afm/ fontbox/src/main/java/org/apache/fontbox/cff/ fontbox/src/main/java/org/apache/fontbox/cff/charse...

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFont.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFont.java?rev=1621411&r1=1621410&r2=1621411&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFont.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFont.java Sat Aug 30 02:26:57 2014
@@ -17,47 +17,185 @@
 package org.apache.pdfbox.pdmodel.font;
 
 import java.io.IOException;
-import java.io.InputStream;
 import java.util.HashMap;
 import java.util.Map;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
+import org.apache.fontbox.util.BoundingBox;
 import org.apache.pdfbox.cos.COSArray;
 import org.apache.pdfbox.cos.COSBase;
 import org.apache.pdfbox.cos.COSDictionary;
 import org.apache.pdfbox.cos.COSName;
 import org.apache.pdfbox.cos.COSNumber;
-import org.apache.pdfbox.io.IOUtils;
-import org.apache.pdfbox.pdmodel.common.PDRectangle;
-import org.apache.pdfbox.util.ResourceLoader;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.Vector;
 
 /**
- * A CIDFont.
+ * A CIDFont. A CIDFont is a PDF object that contains information about a CIDFont program. Although
+ * its Type value is Font, a CIDFont is not actually a font.
  *
  * @author Ben Litchfield
  */
-public abstract class PDCIDFont extends PDFont
+public abstract class PDCIDFont implements COSObjectable
 {
-    private static final Log LOG = LogFactory.getLog(PDCIDFont.class);
+    protected final PDType0Font parent;
 
-    private PDType0Font parent;
-    private Map<Integer, Float> widthCache;
-    private long defaultWidth;
+    private Map<Integer, Float> widths;
+    private float defaultWidth;
+
+    private Map<Integer, Float> verticalDisplacementY = new HashMap<Integer, Float>(); // w1y
+    private Map<Integer, Vector> positionVectors = new HashMap<Integer, Vector>();     // v
+    private float[] dw2;
+
+    protected final COSDictionary dict;
+    private PDFontDescriptor fontDescriptor;
 
     /**
      * Constructor.
      *
      * @param fontDictionary The font dictionary according to the PDF specification.
      */
-    protected PDCIDFont(COSDictionary fontDictionary, PDType0Font parent)
+    protected PDCIDFont(COSDictionary fontDictionary, PDType0Font parent) throws IOException
     {
-        super(fontDictionary);
+        this.dict = fontDictionary;
         this.parent = parent;
-        extractWidths();
+        readWidths();
+        readVerticalDisplacements();
+    }
+
+    private void readWidths()
+    {
+        widths = new HashMap<Integer, Float>();
+        COSArray widths = (COSArray) dict.getDictionaryObject(COSName.W);
+        if (widths != null)
+        {
+            int size = widths.size();
+            int counter = 0;
+            while (counter < size)
+            {
+                COSNumber firstCode = (COSNumber) widths.getObject(counter++);
+                COSBase next = widths.getObject(counter++);
+                if (next instanceof COSArray)
+                {
+                    COSArray array = (COSArray) next;
+                    int startRange = firstCode.intValue();
+                    int arraySize = array.size();
+                    for (int i = 0; i < arraySize; i++)
+                    {
+                        COSNumber width = (COSNumber) array.get(i);
+                        this.widths.put(startRange + i, width.floatValue());
+                    }
+                }
+                else
+                {
+                    COSNumber secondCode = (COSNumber) next;
+                    COSNumber rangeWidth = (COSNumber) widths.getObject(counter++);
+                    int startRange = firstCode.intValue();
+                    int endRange = secondCode.intValue();
+                    float width = rangeWidth.floatValue();
+                    for (int i = startRange; i <= endRange; i++)
+                    {
+                        this.widths.put(i, width);
+                    }
+                }
+            }
+        }
+
+    }
+
+    private void readVerticalDisplacements()
+    {
+        // default position vector and vertical displacement vector
+        COSArray cosDW2 = (COSArray) dict.getDictionaryObject(COSName.DW2);
+        if (cosDW2 != null)
+        {
+            dw2 = new float[2];
+            dw2[0] = ((COSNumber)cosDW2.get(0)).floatValue();
+            dw2[1] = ((COSNumber)cosDW2.get(1)).floatValue();
+        }
+        else
+        {
+            dw2 = new float[] { 880, -1000 };
+        }
+
+        // vertical metrics for individual CIDs.
+        COSArray w2 = (COSArray) dict.getDictionaryObject(COSName.W2);
+        if (w2 != null)
+        {
+            for (int i = 0; i < w2.size(); i++)
+            {
+                COSNumber c = (COSNumber)w2.get(i);
+                COSBase next = w2.get(++i);
+                if (next instanceof COSArray)
+                {
+                    COSArray array = (COSArray)next;
+                    for (int j = 0; j < array.size(); j++)
+                    {
+                        int cid = c.intValue() + j;
+                        COSNumber w1y = (COSNumber) array.get(j);
+                        COSNumber v1x = (COSNumber) array.get(++j);
+                        COSNumber v1y = (COSNumber) array.get(++j);
+                        verticalDisplacementY.put(cid, w1y.floatValue());
+                        positionVectors.put(cid, new Vector(v1x.floatValue(), v1y.floatValue()));
+                    }
+                }
+                else
+                {
+                    int first = c.intValue();
+                    int last = ((COSNumber) next).intValue();
+                    COSNumber w1y = (COSNumber) w2.get(++i);
+                    COSNumber v1x = (COSNumber) w2.get(++i);
+                    COSNumber v1y = (COSNumber) w2.get(++i);
+                    for (int cid = first; cid <= last; cid++)
+                    {
+                        verticalDisplacementY.put(cid, w1y.floatValue());
+                        positionVectors.put(cid, new Vector(v1x.floatValue(), v1y.floatValue()));
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public COSDictionary getCOSObject()
+    {
+        return dict;
+    }
+
+    /**
+     * The PostScript name of the font.
+     *
+     * @return The postscript name of the font.
+     */
+    public String getBaseFont()
+    {
+        return dict.getNameAsString(COSName.BASE_FONT);
     }
 
     /**
+     * This will get the font descriptor for this font. A font descriptor is required for a CIDFont.
+     *
+     * @return The font descriptor for this font.
+     */
+    public PDFontDescriptor getFontDescriptor()
+    {
+        if (fontDescriptor == null)
+        {
+            COSDictionary fd = (COSDictionary) dict.getDictionaryObject(COSName.FONT_DESC);
+            if (fd != null)
+            {
+                fontDescriptor = new PDFontDescriptorDictionary(fd);
+            }
+        }
+        return fontDescriptor;
+    }
+
+    /**
+     * Returns the font matrix, which represents the transformation from glyph space to text space.
+     */
+    public abstract Matrix getFontMatrix();
+
+    /**
      * Returns the Type 0 font which is the parent of this font.
      *
      * @return parent Type 0 font
@@ -68,30 +206,23 @@ public abstract class PDCIDFont extends 
     }
 
     /**
-     * This will get the fonts bounding box.
-     *
-     * @return The fonts bounding box.
-     * @throws IOException If there is an error getting the font bounding box.
+     * Returns the font's bounding box.
      */
-    @Override
-    public PDRectangle getFontBoundingBox() throws IOException
-    {
-        throw new RuntimeException("getFontBoundingBox(): Not yet implemented");
-    }
+    public abstract BoundingBox getBoundingBox() throws IOException;
 
     /**
-     * This will get the default width.  The default value for the default width is 1000.
+     * This will get the default width. The default value for the default width is 1000.
      *
      * @return The default width for the glyphs in this font.
      */
-    public long getDefaultWidth()
+    private float getDefaultWidth()
     {
         if (defaultWidth == 0)
         {
             COSNumber number = (COSNumber) dict.getDictionaryObject(COSName.DW);
             if (number != null)
             {
-                defaultWidth = number.intValue();
+                defaultWidth = number.floatValue();
             }
             else
             {
@@ -102,121 +233,131 @@ public abstract class PDCIDFont extends 
     }
 
     /**
-     * This will get the font width for a character.
+     * Returns the default position vector (v).
      *
-     * @param c The character code to get the width for.
-     * @param offset The offset into the array.
-     * @param length The length of the data.
-     * @return The width is in 1000 unit of text space, ie 333 or 777
-     * @throws IOException If an error occurs while parsing.
+     * @param cid CID
      */
-    @Override
-    public float getFontWidth(byte[] c, int offset, int length) throws IOException
+    private Vector getDefaultPositionVector(int cid)
     {
-        float retval = getDefaultWidth();
-        int code = getCodeFromArray(c, offset, length);
-
-        Float widthFloat = widthCache.get(code);
-        if (widthFloat != null)
+        float w0;
+        if (widths.containsKey(cid))
         {
-            retval = widthFloat;
+            Float w = widths.get(cid);
+            if (w != null)
+            {
+                w0 = w;
+            }
+            else
+            {
+                w0 = getDefaultWidth();
+            }
         }
-        return retval;
+        else
+        {
+            w0 = getDefaultWidth();
+        }
+
+        return new Vector(w0 / 2, dw2[0]);
     }
 
-    private void extractWidths()
+    /**
+     * Returns the position vector (v) in 1/1000 text space, for the given character code.
+     *
+     * @param code character code
+     * @return position vector (v)
+     */
+    public Vector getPositionVector(int code)
     {
-        if (widthCache == null)
+        int cid = codeToCID(code);
+        Vector v = positionVectors.get(cid);
+        if (v != null)
         {
-            widthCache = new HashMap<Integer, Float>();
-            COSArray widths = (COSArray) dict.getDictionaryObject(COSName.W);
-            if (widths != null)
-            {
-                int size = widths.size();
-                int counter = 0;
-                while (counter < size)
-                {
-                    COSNumber firstCode = (COSNumber) widths.getObject(counter++);
-                    COSBase next = widths.getObject(counter++);
-                    if (next instanceof COSArray)
-                    {
-                        COSArray array = (COSArray) next;
-                        int startRange = firstCode.intValue();
-                        int arraySize = array.size();
-                        for (int i = 0; i < arraySize; i++)
-                        {
-                            COSNumber width = (COSNumber) array.get(i);
-                            widthCache.put(startRange + i, width.floatValue());
-                        }
-                    }
-                    else
-                    {
-                        COSNumber secondCode = (COSNumber) next;
-                        COSNumber rangeWidth = (COSNumber) widths.getObject(counter++);
-                        int startRange = firstCode.intValue();
-                        int endRange = secondCode.intValue();
-                        float width = rangeWidth.floatValue();
-                        for (int i = startRange; i <= endRange; i++)
-                        {
-                            widthCache.put(i, width);
-                        }
-                    }
-                }
-            }
+            return v;
+        }
+        else
+        {
+            return getDefaultPositionVector(cid);
         }
     }
 
     /**
-     * This will get the font height for a character.
-     *
-     * @param c The character code to get the height for.
-     * @param offset The offset into the array.
-     * @param length The length of the data.
-     *
-     * @return The width is in 1000 unit of text space, ie 333 or 777
+     * Returns the y-component of the vertical displacement vector (w1).
      *
-     * @throws IOException If an error occurs while parsing.
+     * @param code character code
+     * @return w1y
      */
-    @Override
-    public float getFontHeight(byte[] c, int offset, int length) throws IOException
+    public float getVerticalDisplacementVectorY(int code)
     {
-        float retval = 0;
-        PDFontDescriptor desc = getFontDescriptor();
-        float xHeight = desc.getXHeight();
-        float capHeight = desc.getCapHeight();
-        if (xHeight != 0f && capHeight != 0)
+        int cid = codeToCID(code);
+        Float w1y = verticalDisplacementY.get(cid);
+        if (w1y != null)
         {
-            // do an average of these two. Can we do better???
-            retval = (xHeight + capHeight) / 2f;
+            return w1y;
         }
-        else if (xHeight != 0)
+        else
         {
-            retval = xHeight;
+            return dw2[1];
         }
-        else if (capHeight != 0)
+    }
+
+    /**
+     * This will get the font height for a character.
+     *
+     * @param code character code
+     * @return The height is in 1000 unit of text space, ie 333 or 777
+     */
+    public abstract float getHeight(int code) throws IOException;
+
+    /**
+     * Returns the width of the given character.
+     *
+     * @param code character code
+     */
+    public float getWidth(int code) throws IOException
+    {
+        // these widths are supposed to be consistent with the actual widths given in the CIDFont
+        // program, but PDFBOX-563 shows that when they are not, Acrobat overrides the embedded
+        // font widths with the widths given in the font dictionary
+
+        int cid = codeToCID(code);
+        if (widths.containsKey(cid))
         {
-            retval = capHeight;
+            Float w = widths.get(cid);
+            if (w != null)
+            {
+                return w;
+            }
+            else
+            {
+                return getDefaultWidth();
+            }
         }
         else
         {
-            retval = 0;
-        }
-        if (retval == 0)
-        {
-            retval = desc.getAscent();
+            return getWidthFromFont(code);
         }
-        return retval;
     }
 
     /**
+     * Returns the width of a glyph in the embedded font file.
+     *
+     * @param code character code
+     * @return width in glyph space
+     * @throws IOException if the font could not be read
+     */
+    protected abstract float getWidthFromFont(int code) throws IOException;
+
+    /**
+     * Returns true if the font file is embedded in the PDF.
+     */
+    public abstract boolean isEmbedded();
+
+    /**
      * This will get the average font width for all characters.
      *
      * @return The width is in 1000 unit of text space, ie 333 or 777
-     *
-     * @throws IOException If an error occurs while parsing.
      */
-    @Override
-    public float getAverageFontWidth() throws IOException
+    public float getAverageFontWidth()
     {
         float totalWidths = 0.0f;
         float characterCount = 0.0f;
@@ -258,117 +399,28 @@ public abstract class PDCIDFont extends 
         return average;
     }
 
-    @Override
-    public float getFontWidth(int charCode)
-    {
-        float width = getDefaultWidth();
-        if (widthCache.containsKey(charCode))
-        {
-            width = widthCache.get(charCode);
-        }
-        return width;
-    }
-
     /**
-     * Extract the CIDSystemInfo.
-     * @return the CIDSystemInfo as String
+     * Returns the CID for the given character code. If not found then CID 0 is returned.
+     *
+     * @param code character code
+     * @return CID
      */
-    private String getCIDSystemInfo()
-    {
-        String cidSystemInfo = null;
-        COSDictionary dict = (COSDictionary) this.dict.getDictionaryObject(COSName.CIDSYSTEMINFO);
-        if (dict != null)
-        {
-            String ordering = dict.getString(COSName.ORDERING);
-            String registry = dict.getString(COSName.REGISTRY);
-            int supplement = dict.getInt(COSName.SUPPLEMENT);
-            cidSystemInfo = registry + "-" + ordering + "-" + supplement;
-        }
-        return cidSystemInfo;
-    }
-
-    // todo: do we want to do this at all? Isn't the parent Type0 font responsible for this?
-    @Override
-    protected void determineEncoding()
-    {
-        String cidSystemInfo = getCIDSystemInfo();
-        if (cidSystemInfo == null)
-        {
-            // todo: CIDSystemInfo is required, so this is an error (perform recovery?)
-            LOG.error("Missing CIDSystemInfo in CIDFont dictionary");
-            return;
-        }
+    public abstract int codeToCID(int code);
 
-        if (cidSystemInfo.contains("Identity"))
-        {
-            cidSystemInfo = "Identity-H";
-        }
-        else if (cidSystemInfo.startsWith("Adobe-UCS-"))
-        {
-            cidSystemInfo = "Adobe-Identity-UCS";
-        }
-        else
-        {
-            cidSystemInfo = cidSystemInfo.substring(0, cidSystemInfo.lastIndexOf('-')) + "-UCS2";
-        }
-
-        cmap = cmapObjects.get(cidSystemInfo);
-        if (cmap == null)
-        {
-            InputStream cmapStream = null;
-            try
-            {
-                // look for a predefined CMap with the given name
-                cmapStream = ResourceLoader.loadResource(resourceRootCMAP + cidSystemInfo);
-                if (cmapStream != null)
-                {
-                    cmap = parseCmap(resourceRootCMAP, cmapStream);
-                    if (cmap == null)
-                    {
-                        LOG.error("Could not parse predefined CMAP file for '" +
-                                cidSystemInfo + "'");
-                    }
-                }
-                else
-                {
-                    LOG.debug("'" + cidSystemInfo + "' isn't a predefined CMap, most " +
-                              "likely it's embedded in the pdf itself.");
-                }
-            }
-            catch (IOException exception)
-            {
-                LOG.error("Could not find predefined CMAP file for '" + cidSystemInfo + "'");
-            }
-            finally
-            {
-                IOUtils.closeQuietly(cmapStream);
-            }
-        }
-    }
+    /**
+     * Returns the GID for the given character code.
+     *
+     * @param code character code
+     * @return GID
+     */
+    public abstract int codeToGID(int code) throws IOException;
 
-    @Override
-    public String encode(byte[] c, int offset, int length) throws IOException
-    {
-        String result;
-        if (cmap != null)
-        {
-            result = cmapEncoding(getCodeFromArray(c, offset, length), length, true, cmap);
-        }
-        else
-        {
-            result = super.encode(c, offset, length);
-        }
-        return result;
-    }
-    
-    @Override
     public void clear()
     {
-        super.clear();
-        if (widthCache != null)
+        if (widths != null)
         {
-            widthCache.clear();
-            widthCache = null;
+            widths.clear();
+            widths = null;
         }
     }
 }

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFont.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFont.java?rev=1621411&r1=1621410&r2=1621411&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFont.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFont.java Sat Aug 30 02:26:57 2014
@@ -18,33 +18,24 @@ package org.apache.pdfbox.pdmodel.font;
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
-import org.apache.fontbox.afm.FontMetric;
 import org.apache.fontbox.cmap.CMap;
-import org.apache.fontbox.cmap.CMapParser;
+import org.apache.fontbox.util.BoundingBox;
 import org.apache.pdfbox.cos.COSArray;
 import org.apache.pdfbox.cos.COSBase;
 import org.apache.pdfbox.cos.COSDictionary;
-import org.apache.pdfbox.cos.COSFloat;
-import org.apache.pdfbox.cos.COSInteger;
 import org.apache.pdfbox.cos.COSName;
 import org.apache.pdfbox.cos.COSNumber;
 import org.apache.pdfbox.cos.COSStream;
-import org.apache.pdfbox.encoding.DictionaryEncoding;
-import org.apache.pdfbox.encoding.Encoding;
 import org.apache.pdfbox.io.IOUtils;
 import org.apache.pdfbox.pdmodel.common.COSArrayList;
 import org.apache.pdfbox.pdmodel.common.COSObjectable;
-import org.apache.pdfbox.pdmodel.common.PDMatrix;
-import org.apache.pdfbox.pdmodel.common.PDRectangle;
-import org.apache.pdfbox.util.ResourceLoader;
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.Vector;
 
 /**
  * This is the base class for all PDF fonts.
@@ -54,334 +45,203 @@ import org.apache.pdfbox.util.ResourceLo
 public abstract class PDFont implements COSObjectable
 {
     private static final Log LOG = LogFactory.getLog(PDFont.class);
-    private static final byte[] SPACE_BYTES = { (byte) 32 }; // formerly in PDSimpleFont
+    private static final Matrix DEFAULT_FONT_MATRIX = new Matrix(0.001f, 0, 0, 0.001f, 0, 0);
 
-    protected static final String resourceRootCMAP = "org/apache/pdfbox/resources/cmap/";
-    protected static Map<String, CMap> cmapObjects =
-            Collections.synchronizedMap(new HashMap<String, CMap>()); // todo: why synchronized?
+    protected final COSDictionary dict;
+    private final CMap toUnicodeCMap;
+    private  PDFontDescriptor fontDescriptor;
 
-    private static final String[] SINGLE_CHAR_STRING = new String[256];
-    private static final String[][] DOUBLE_CHAR_STRING = new String[256][256];
-    static
+    private List<Integer> widths;
+    private float avgFontWidth;
+    private float fontWidthOfSpace = -1f;
+    private Boolean isSymbolic;
+
+    /**
+     * Constructor for embedding.
+     */
+    protected PDFont()
     {
-        for (int i = 0; i < 256; i++)
-        {
-            try
-            {
-                SINGLE_CHAR_STRING[i] = new String(new byte[] { (byte) i }, "ISO-8859-1");
-            }
-            catch (UnsupportedEncodingException e)
-            {
-                // Nothing should happen here
-                LOG.error(e,e);
-            }
-            for (int j = 0; j < 256; j++)
-            {
-                try
-                {
-                    DOUBLE_CHAR_STRING[i][j] = new String(new byte[] { (byte) i, (byte) j },
-                            "UTF-16BE");
-                }
-                catch (UnsupportedEncodingException e)
-                {
-                    // Nothing should happen here
-                    LOG.error(e, e);
-                }
-            }
-        }
+        dict = new COSDictionary();
+        dict.setItem(COSName.TYPE, COSName.FONT);
+        toUnicodeCMap = null;
     }
 
-    private static String getStringFromArray(byte[] c, int offset, int length) throws IOException
+    /**
+     * Constructor.
+     *
+     * @param fontDictionary Font dictionary.
+     */
+    protected PDFont(COSDictionary fontDictionary) throws IOException
     {
-        String retval;
-        if (length == 1)
+        dict = fontDictionary;
+
+        // font descriptor
+        COSDictionary fd = (COSDictionary) dict.getDictionaryObject(COSName.FONT_DESC);
+        if (fd != null)
         {
-            retval = SINGLE_CHAR_STRING[(c[offset] + 256) % 256];
+            fontDescriptor = new PDFontDescriptorDictionary(fd);
         }
-        else if (length == 2)
+        else
         {
-            retval = DOUBLE_CHAR_STRING[(c[offset] + 256) % 256][(c[offset + 1] + 256) % 256];
+            fontDescriptor = null;
+        }
+
+        // ToUnicode CMap
+        COSBase toUnicode = dict.getDictionaryObject(COSName.TO_UNICODE);
+        if (toUnicode != null)
+        {
+            toUnicodeCMap = readCMap(toUnicode);
+            if (toUnicodeCMap != null && !toUnicodeCMap.hasUnicodeMappings())
+            {
+                LOG.warn("Invalid ToUnicode CMap in font " + getName());
+            }
         }
         else
         {
-            throw new IOException("Error:Unknown character length:" + length);
+            toUnicodeCMap = null;
         }
-        return retval;
     }
 
     /**
-     * The Font dictionary.
+     * Returns the font descriptor, may be null.
      */
-    protected COSDictionary dict;
+    public PDFontDescriptor getFontDescriptor()
+    {
+        return fontDescriptor;
+    }
 
     /**
-     * The font matrix.
+     * Sets the font descriptor.
      */
-    protected PDMatrix fontMatrix = null;
-
-    // CMap / Encoding
-    protected CMap cmap = null; // only used when this is a Type0 font with a CMap
-    protected Encoding fontEncoding = null; // only used when this font has an encoding
-
-    // the CMap holding the ToUnicode mapping
-    private CMap toUnicodeCmap = null;
-    private boolean hasToUnicode = false;
-
-    private List<Integer> widths = null;
-
-    protected PDFontDescriptor fontDescriptor = null;
-    private boolean widthsAreMissing = false;
-
-    // formerly in PDSimpleFont
-    private final HashMap<Integer, Float> fontSizes = new HashMap<Integer, Float>(128);
-    private float avgFontWidth = 0.0f;
-    private float avgFontHeight = 0.0f;
-    private float fontWidthOfSpace = -1f;
+    void setFontDescriptor(PDFontDescriptor fontDescriptor)
+    {
+        this.fontDescriptor = fontDescriptor;
+    }
 
     /**
-     * This will clear AFM resources that are stored statically. This is usually not a problem
-     * unless you want to reclaim resources for a long running process.
-     *
-     * SPECIAL NOTE: The font calculations are currently in COSObject, which is where they will
-     * reside until PDFont is mature enough to take them over. PDFont is the appropriate place for
-     * them and not in COSObject but we need font calculations for text extraction. THIS METHOD WILL
-     * BE MOVED OR REMOVED TO ANOTHER LOCATION IN A FUTURE VERSION OF PDFBOX.
+     * Reads a CMap given a COS Stream or Name. May return null if a predefined CMap does not exist.
      *
-     * @deprecated This method will be removed in a future version of PDFBox.
+     * @param base COSName or COSStream
      */
-    @Deprecated
-    public static void clearResources()
+    protected final CMap readCMap(COSBase base) throws IOException
     {
-        cmapObjects.clear();
+        if (base instanceof COSName)
+        {
+            // predefined CMap
+            String name = ((COSName)base).getName();
+            return CMapManager.getPredefinedCMap(name);
+        }
+        else if (base instanceof COSStream)
+        {
+            // embedded CMap
+            InputStream input = null;
+            try
+            {
+                input = ((COSStream)base).getUnfilteredStream();
+                return CMapManager.parseCMap(input);
+            }
+            finally
+            {
+                IOUtils.closeQuietly(input);
+            }
+        }
+        else
+        {
+            throw new IOException("Expected Name or Stream");
+        }
     }
 
-    /**
-     * Constructor.
-     */
-    protected PDFont()
+    @Override
+    public COSDictionary getCOSObject()
     {
-        dict = new COSDictionary();
-        dict.setItem(COSName.TYPE, COSName.FONT);
+        return dict;
     }
 
     /**
-     * Constructor.
-     * 
-     * @param fontDictionary The font dictionary according to the PDF specification.
+     * Returns the position vector (v), in text space, for the given character.
+     * This represents the position of vertical origin relative to horizontal origin, for
+     * horizontal writing it will always be (0, 0). For vertical writing both x and y are set.
+     *
+     * @param code character code
+     * @return position vector
      */
-    protected PDFont(COSDictionary fontDictionary)
+    public Vector getPositionVector(int code)
     {
-        dict = fontDictionary;
-        determineEncoding();
+        throw new UnsupportedOperationException("Horizontal fonts have no position vector");
     }
 
     /**
-     * This will get the font descriptor for this font.
-     * 
-     * @return The font descriptor for this font.
+     * Returns the displacement vector (w0, w1) in text space, for the given character.
+     * For horizontal text only the x component is used, for vertical text only the y component.
+     *
+     * @param code character code
+     * @return displacement vector
      */
-    public PDFontDescriptor getFontDescriptor()
+    public Vector getDisplacement(int code) throws IOException
     {
-        if (fontDescriptor == null)
-        {
-            COSDictionary fd = (COSDictionary) dict.getDictionaryObject(COSName.FONT_DESC);
-            if (fd != null)
-            {
-                fontDescriptor = new PDFontDescriptorDictionary(fd);
-            }
-            else
-            {
-                FontMetric afm = getAFM();
-                if (afm != null)
-                {
-                    fontDescriptor = new PDFontDescriptorAFM(afm);
-                }
-            }
-        }
-        return fontDescriptor;
+        return new Vector(getWidth(code) / 1000, 0);
     }
 
     /**
-     * Determines the encoding for the font. This method as to be overwritten, as there are
-     * different possibilities to define a mapping.
+     * Returns the advance width of the given character, in glyph space.
+     *
+     * @param code character code
      */
-    protected void determineEncoding()
+    public float getWidth(int code) throws IOException
     {
-        COSBase encoding = dict.getDictionaryObject(COSName.ENCODING);
-        Encoding fontEncoding = null;
-        if (encoding != null)
+        // Acrobat overrides the widths in the font program on the conforming reader's system with
+        // the widths specified in the font dictionary." (Adobe Supplement to the ISO 32000)
+        //
+        // Note: The Adobe Supplement says that the override happens "If the font program is not
+        // embedded", however PDFBOX-427 shows that it also applies to embedded fonts.
+
+        // Type1, Type1C, Type3
+        int firstChar = dict.getInt(COSName.FIRST_CHAR, -1);
+        int lastChar = dict.getInt(COSName.LAST_CHAR, -1);
+        if (getWidths().size() > 0 && code >= firstChar && code <= lastChar)
         {
-            if (encoding instanceof COSName)
-            {
-                COSName encodingName = (COSName)encoding;
-                try
-                {
-                    fontEncoding = Encoding.getInstance(encodingName);
-                }
-                catch (IOException exception)
-                {
-                    LOG.debug("Debug: Could not find encoding for " + encodingName);
-                }
-            }
-            else if (encoding instanceof COSDictionary)
-            {
-                try
-                {
-                    fontEncoding = new DictionaryEncoding((COSDictionary) encoding);
-                }
-                catch (IOException exception)
-                {
-                    LOG.error("Error: Could not create the DictionaryEncoding");
-                }
-            }
+            return getWidths().get(code - firstChar).floatValue();
         }
-        this.fontEncoding = fontEncoding;
-        extractToUnicodeEncoding();
-    }
-
-    protected final void extractToUnicodeEncoding()
-    {
-        COSName encodingName;
-        String cmapName;
-        COSBase toUnicode = dict.getDictionaryObject(COSName.TO_UNICODE);
-        if (toUnicode != null)
+        else
         {
-            hasToUnicode = true;
-            if (toUnicode instanceof COSStream)
+            PDFontDescriptor fd = getFontDescriptor();
+            if (fd instanceof PDFontDescriptorDictionary &&
+                    ((PDFontDescriptorDictionary) fd).hasWidths())
             {
-                try
-                {
-                    InputStream is = ((COSStream) toUnicode).getUnfilteredStream();
-                    toUnicodeCmap = parseCmap(resourceRootCMAP, is);
-                    IOUtils.closeQuietly(is);
-                }
-                catch (IOException exception)
-                {
-                    LOG.error("Error: Could not load embedded ToUnicode CMap");
-                }
+                return fd.getMissingWidth();
             }
-            else if (toUnicode instanceof COSName)
+            else
             {
-                encodingName = (COSName) toUnicode;
-                toUnicodeCmap = cmapObjects.get(encodingName.getName());
-                if (toUnicodeCmap == null)
-                {
-                    cmapName = encodingName.getName();
-                    String resourceName = resourceRootCMAP + cmapName;
-                    try
-                    {
-                        toUnicodeCmap = parseCmap(resourceRootCMAP,
-                                ResourceLoader.loadResource(resourceName));
-                    }
-                    catch (IOException exception)
-                    {
-                        LOG.error("Error: Could not find predefined ToUnicode CMap file for '" +
-                                cmapName + "'");
-                    }
-                    if (toUnicodeCmap == null)
-                    {
-                        LOG.error("Error: Could not parse predefined ToUnicode CMap file for '" +
-                                cmapName + "'");
-                    }
-                }
+                // if there's nothing to override with, then obviously we fall back to the font
+                return getWidthFromFont(code);
             }
         }
     }
 
-    @Override
-    public COSBase getCOSObject()
-    {
-        return dict;
-    }
+    /**
+     * Returns the width of a glyph in the embedded font file.
+     *
+     * @param code character code
+     * @return width in glyph space
+     * @throws IOException if the font could not be read
+     */
+    protected abstract float getWidthFromFont(int code) throws IOException;
 
     /**
-     * This will get the font width for a character.
-     * 
-     * @param c The character code to get the width for.
-     * @param offset The offset into the array.
-     * @param length The length of the data.
-     * @return The width is in 1000 unit of text space, ie 333 or 777
-     * @throws IOException If an error occurs while parsing.
+     * Returns true if the font file is embedded in the PDF.
      */
-    public float getFontWidth(byte[] c, int offset, int length) throws IOException
-    {
-        int code = getCodeFromArray(c, offset, length);
-        Float fontWidth = fontSizes.get(code);
-        if (fontWidth == null)
-        {
-            fontWidth = getFontWidth(code);
-            if (fontWidth <= 0)
-            {
-                // TODO should this be in PDType1Font??
-                fontWidth = getFontWidthFromAFMFile(code);
-            }
-            fontSizes.put(code, fontWidth);
-        }
-        return fontWidth;
-    }
+    public abstract boolean isEmbedded();
 
     /**
-     * This will get the font height for a character.
+     * Returns the height of the given character, in glyph space. This can be expensive to
+     * calculate. Results are only approximate.
      * 
-     * @param c The character code to get the height for.
-     * @param offset The offset into the array.
-     * @param length The length of the data.
-     * @return The height is in 1000 unit of text space, ie 333 or 777
-     * @throws IOException If an error occurs while parsing.
-     */
-    public float getFontHeight(byte[] c, int offset, int length) throws IOException
-    {
-        // maybe there is already a precalculated value
-        if (avgFontHeight > 0)
-        {
-            return avgFontHeight;
-        }
-        float retval = 0;
-        FontMetric metric = getAFM();
-        if (metric != null)
-        {
-            int code = getCodeFromArray(c, offset, length);
-            Encoding encoding = getFontEncoding();
-            String characterName = encoding.getName(code);
-            retval = metric.getCharacterHeight(characterName);
-        }
-        else
-        {
-            PDFontDescriptor desc = getFontDescriptor();
-            if (desc != null)
-            {
-                // the following values are all more or less accurate at least all are average
-                // values. Maybe we'll find another way to get those value for every single glyph
-                // in the future if needed
-                PDRectangle fontBBox = desc.getFontBoundingBox();
-                if (fontBBox != null)
-                {
-                    retval = fontBBox.getHeight() / 2;
-                }
-                if (retval == 0)
-                {
-                    retval = desc.getCapHeight();
-                }
-                if (retval == 0)
-                {
-                    retval = desc.getAscent();
-                }
-                if (retval == 0)
-                {
-                    retval = desc.getXHeight();
-                    if (retval > 0)
-                    {
-                        retval -= desc.getDescent();
-                    }
-                }
-                avgFontHeight = retval;
-            }
-        }
-        return retval;
-    }
+     * @param code character code
+     */
+    public abstract float getHeight(int code) throws IOException;
 
     /**
-     * This will get the width of this string for this font.
+     * Returns the width of the given Unicode string.
      * 
      * @param string The string to get the width of.
      * @return The width of the string in 1000 units of text space, ie 333 567...
@@ -389,11 +249,11 @@ public abstract class PDFont implements 
      */
     public float getStringWidth(String string) throws IOException
     {
-        byte[] data = string.getBytes("ISO-8859-1");
+        byte[] data = string.getBytes("ISO-8859-1"); // todo: *no*, these are *not* character codes
         float totalWidth = 0;
         for (int i = 0; i < data.length; i++)
         {
-            totalWidth += getFontWidth(data, i, 1);
+            totalWidth += getWidth(data[i]);
         }
         return totalWidth;
     }
@@ -402,9 +262,9 @@ public abstract class PDFont implements 
      * This will get the average font width for all characters.
      * 
      * @return The width is in 1000 unit of text space, ie 333 or 777
-     * @throws IOException If an error occurs while parsing.
      */
-    public float getAverageFontWidth() throws IOException
+    // todo: this method is highly suspicious, the average glyph width is not usually a good metric
+    public float getAverageFontWidth()
     {
         float average;
         if (avgFontWidth != 0.0f)
@@ -435,7 +295,7 @@ public abstract class PDFont implements 
             }
             else
             {
-                average = getAverageFontWidthFromAFMFile();
+                average = 0;
             }
             avgFontWidth = average;
         }
@@ -443,185 +303,42 @@ public abstract class PDFont implements 
     }
 
     /**
-     * Used for multibyte encodings.
-     * 
-     * @param data The array of data.
-     * @param offset The offset into the array.
-     * @param length The number of bytes to use.
-     * @return The int value of data from the array.
-     */
-    public int getCodeFromArray(byte[] data, int offset, int length)
-    {
-        int code = 0;
-        for (int i = 0; i < length; i++)
-        {
-            code <<= 8;
-            code |= (data[offset + i] + 256) % 256;
-        }
-        return code;
-    }
-
-    /**
-     * This will attempt to get the font width from an AFM file.
-     * 
-     * @param code The character code we are trying to get.
-     * @return The font width from the AFM file.
-     * @throws IOException if we cannot find the width.
-     */
-    private float getFontWidthFromAFMFile(int code) throws IOException
-    {
-        float retval = 0;
-        FontMetric metric = getAFM();
-        if (metric != null)
-        {
-            String characterName = fontEncoding.getName(code);
-            retval = metric.getCharacterWidth(characterName);
-        }
-        return retval;
-    }
-
-    /**
-     * This will attempt to get the average font width from an AFM file.
-     * 
-     * @return The average font width from the AFM file.
-     * @throws IOException if we cannot find the width.
-     */
-    private float getAverageFontWidthFromAFMFile() throws IOException
-    {
-        float retval = 0;
-        FontMetric metric = getAFM();
-        if (metric != null)
-        {
-            retval = metric.getAverageCharacterWidth();
-        }
-        return retval;
-    }
-
-    /**
-     * This will get an AFM object if one exists.
-     * 
-     * @return The afm object from the name.
-     */
-    protected FontMetric getAFM()
-    {
-        return null;
-    }
-
-    /**
-     * Encode the given value using the CMap of the font.
-     * 
-     * @param code the code to encode.
-     * @param length the byte length of the given code.
-     * @param isCIDFont indicates that the used font is a CID font.
-     * 
-     * @return The value of the encoded character.
-     * @throws IOException if something went wrong
-     */
-    protected final String cmapEncoding(int code, int length, boolean isCIDFont, CMap sourceCmap)
-            throws IOException
-    {
-        String retval = null;
-        // there is not sourceCmap if this is a descendant font
-        if (sourceCmap == null)
-        {
-            sourceCmap = cmap;
-        }
-        if (sourceCmap != null)
-        {
-            retval = sourceCmap.lookup(code, length);
-            if (retval == null && isCIDFont)
-            {
-                retval = sourceCmap.lookupCID(code);
-            }
-        }
-        return retval;
-    }
-
-    /**
-     * This will perform the encoding of a character if needed.
-     * 
-     * @param c The character to encode.
-     * @param offset The offset into the array to get the data
-     * @param length The number of bytes to read.
-     * @return The value of the encoded character.
-     * @throws IOException If there is an error during the encoding.
+     * Reads a character code from a content stream string. Codes may be up to 4 bytes long.
+     *
+     * @param in string stream
+     * @return character code
+     * @throws IOException if the CMap or stream cannot be read
      */
-    public String encode(byte[] c, int offset, int length) throws IOException
-    {
-        String retval = null;
-        int code = getCodeFromArray(c, offset, length);
-        if (toUnicodeCmap != null)
-        {
-            retval = cmapEncoding(code, length, false, toUnicodeCmap);
-        }
-        if (retval == null && cmap != null)
-        {
-            retval = cmapEncoding(code, length, false, cmap);
-        }
-
-        // there is no cmap but probably an encoding with a suitable mapping
-        if (retval == null)
-        {
-            if (fontEncoding != null)
-            {
-                retval = fontEncoding.getCharacter(code);
-            }
-            if (retval == null && (cmap == null || length == 2))
-            {
-                retval = getStringFromArray(c, offset, length);
-            }
-        }
-        return retval;
-    }
-
-    public int encodeToCID(byte[] c, int offset, int length) throws IOException
-    {
-        int code = -1;
-        if (encode(c, offset, length) != null)
-        {
-            code = getCodeFromArray(c, offset, length);
-        }
-        return code;
-    }
+    public abstract int readCode(InputStream in) throws IOException;
 
     /**
-     * Parse the given CMap.
-     * 
-     * @param cmapRoot the root path pointing to the provided CMaps
-     * @param cmapStream the CMap to be read
-     * @return the parsed CMap
+     * Returns the Unicode character sequence which corresponds to the given character code.
+     *
+     * @param code character code
+     * @return Unicode character(s)
      */
-    protected final CMap parseCmap(String cmapRoot, InputStream cmapStream)
+    public String toUnicode(int code)
     {
-        CMap targetCmap = null;
-        if (cmapStream != null)
+        // if the font dictionary contains a ToUnicode CMap, use that CMap
+        if (toUnicodeCMap != null)
         {
-            CMapParser parser = new CMapParser();
-            try
+            if (toUnicodeCMap.getName() != null && toUnicodeCMap.getName().startsWith("Identity-"))
             {
-                targetCmap = parser.parse(cmapRoot, cmapStream);
-                // limit the cache to external CMaps
-                if (cmapRoot != null)
-                {
-                    cmapObjects.put(targetCmap.getName(), targetCmap);
-                }
+                // handle the undocumented case of using Identity-H/V as a ToUnicode CMap, this
+                // isn't  actually valid as the Identity-x CMaps are code->CID maps, not
+                // code->Unicode maps. See sample_fonts_solidconvertor.pdf for an example.
+                return new String(new char[] { (char) code });
             }
-            catch (IOException exception)
+            else
             {
-                LOG.error("An error occurs while reading a CMap", exception);
+                // proceed as normal
+                return toUnicodeCMap.toUnicode(code);
             }
         }
-        return targetCmap;
-    }
 
-    /**
-     * This will get or create the encoder.
-     * 
-     * @return The encoding to use.
-     */
-    public Encoding getFontEncoding()
-    {
-        return fontEncoding;
+        // if no value has been produced, there is no way to obtain Unicode for the character.
+        // this behaviour can be overridden is subclasses, but this method *must* return null here
+        return null;
     }
 
     /**
@@ -636,8 +353,6 @@ public abstract class PDFont implements 
 
     /**
      * This will get the subtype of font.
-     * 
-     * @return The type of font that this is.
      */
     public String getSubType()
     {
@@ -645,59 +360,52 @@ public abstract class PDFont implements 
     }
 
     /**
-     * Determines if the font is a type 1 font.
-     * 
-     * @return returns true if the font is a type 1 font
-     */
-    public boolean isType1Font()
-    {
-        return "Type1".equals(getSubType());
-    }
-
-    /**
-     * Determines if the font is a type 3 font.
-     * 
-     * @return returns true if the font is a type 3 font
-     */
-    public boolean isType3Font()
-    {
-        return "Type3".equals(getSubType());
-    }
-
-    /**
-     * Determines if the font is a type 0 font.
-     * 
-     * @return returns true if the font is a type 0 font
+     * Returns true the font is a symbolic (that is, it does not use the Adobe Standard Roman
+     * character set).
      */
-    public boolean isType0Font()
+    public final boolean isSymbolic()
     {
-        return "Type0".equals(getSubType());
+        if (isSymbolic == null)
+        {
+            Boolean result = isFontSymbolic();
+            if (result != null)
+            {
+                isSymbolic = result;
+            }
+            else
+            {
+                // unless we can prove that the font is symbolic, we assume that it is not
+                isSymbolic = true;
+            }
+        }
+        return isSymbolic;
     }
 
     /**
-     * Determines if the font is a true type font.
-     * 
-     * @return returns true if the font is a true type font
+     * Internal implementation of isSymbolic, allowing for the fact that the result may be
+     * indeterminate.
      */
-    public boolean isTrueTypeFont()
+    protected Boolean isFontSymbolic()
     {
-        return "TrueType".equals(getSubType());
+        return getSymbolicFlag();
     }
 
     /**
-     * Determines if the font is a symbolic font.
-     * 
-     * @return returns true if the font is a symbolic font
+     * Returns the value of the symbolic flag,  allowing for the fact that the result may be
+     * indeterminate.
      */
-    public boolean isSymbolicFont()
+    protected Boolean getSymbolicFlag()
     {
-        return getFontDescriptor().isSymbolic();
+        if (getFontDescriptor() != null)
+        {
+            // fixme: isSymbolic() defaults to false if the flag is missing so we can't trust this
+            return getFontDescriptor().isSymbolic();
+        }
+        return null;
     }
 
     /**
-     * The PostScript name of the font.
-     * 
-     * @return The postscript name of the font.
+     * Returns the PostScript name of the font.
      */
     public String getBaseFont()
     {
@@ -705,33 +413,26 @@ public abstract class PDFont implements 
     }
 
     /**
-     * The code for the first char or -1 if there is none.
-     * 
-     * @return The code for the first character.
+     * Returns the name of this font, either the PostScript "BaseName" or the Type 3 "Name".
      */
-    public int getFirstChar()
+    public String getName()
     {
-        return dict.getInt(COSName.FIRST_CHAR, -1);
+        return getBaseFont();
     }
 
     /**
-     * The code for the last char or -1 if there is none.
-     * 
-     * @return The code for the last character.
+     * Returns the font's bounding box.
      */
-    public int getLastChar()
-    {
-        return dict.getInt(COSName.LAST_CHAR, -1);
-    }
+    public abstract BoundingBox getBoundingBox() throws IOException;
 
     /**
      * The widths of the characters. This will be null for the standard 14 fonts.
-     * 
+     *
      * @return The widths of the characters.
      */
-    public List<Integer> getWidths()
+    protected final List<Integer> getWidths()
     {
-        if (widths == null && !widthsAreMissing)
+        if (widths == null)
         {
             COSArray array = (COSArray) dict.getDictionaryObject(COSName.WIDTHS);
             if (array != null)
@@ -740,94 +441,18 @@ public abstract class PDFont implements 
             }
             else
             {
-                widthsAreMissing = true;
+                widths = Collections.emptyList();
             }
         }
         return widths;
     }
 
     /**
-     * This will get the matrix that is used to transform glyph space to text space. By default
-     * there are 1000 glyph units to 1 text space unit, but type3 fonts can use any value.
-     * 
-     * Note: If this is a type3 font then it can be modified via the PDType3Font.setFontMatrix,
-     * otherwise this is a read-only property.
-     * 
-     * @return The matrix to transform from glyph space to text space.
+     * Returns the font matrix, which represents the transformation from glyph space to text space.
      */
-    public PDMatrix getFontMatrix()
+    public Matrix getFontMatrix()
     {
-        if (fontMatrix == null)
-        {
-            COSArray array = (COSArray) dict.getDictionaryObject(COSName.FONT_MATRIX);
-            if (array == null)
-            {
-                array = new COSArray();
-                array.add(new COSFloat(0.001f));
-                array.add(COSInteger.ZERO);
-                array.add(COSInteger.ZERO);
-                array.add(new COSFloat(0.001f));
-                array.add(COSInteger.ZERO);
-                array.add(COSInteger.ZERO);
-            }
-            fontMatrix = new PDMatrix(array);
-        }
-        return fontMatrix;
-    }
-
-    /**
-     * This will get the fonts bounding box.
-     * 
-     * @return The fonts bounding box.
-     * @throws IOException If there is an error getting the bounding box.
-     */
-    public PDRectangle getFontBoundingBox() throws IOException
-    {
-        return getFontDescriptor().getFontBoundingBox();
-    }
-
-    /**
-     * Determines the width of the given character.
-     * 
-     * @param charCode the code of the given character
-     * @return the width of the character
-     */
-    public float getFontWidth(int charCode)
-    {
-        float width = -1;
-        int firstChar = getFirstChar();
-        int lastChar = getLastChar();
-        if (charCode >= firstChar && charCode <= lastChar)
-        {
-            // maybe the font doesn't provide any widths
-            if (!widthsAreMissing)
-            {
-                getWidths();
-                if (widths != null)
-                {
-                    width = widths.get(charCode - firstChar).floatValue();
-                }
-            }
-        }
-        else
-        {
-            PDFontDescriptor fd = getFontDescriptor();
-            if (fd instanceof PDFontDescriptorDictionary)
-            {
-                width = fd.getMissingWidth();
-            }
-        }
-        return width;
-    }
-
-    /**
-     * Determines if a font as a ToUnicode entry.
-     * 
-     * @return true if the font has a ToUnicode entry
-     */
-    public boolean hasToUnicode()
-    {
-        return hasToUnicode;
+        return DEFAULT_FONT_MATRIX;
     }
 
     /**
@@ -844,15 +469,15 @@ public abstract class PDFont implements 
             {
                 if (toUnicode != null)
                 {
-                    int spaceMapping = toUnicodeCmap.getSpaceMapping();
+                    int spaceMapping = toUnicodeCMap.getSpaceMapping();
                     if (spaceMapping > -1)
                     {
-                        fontWidthOfSpace = getFontWidth(spaceMapping);
+                        fontWidthOfSpace = getWidth(spaceMapping);
                     }
                 }
                 else
                 {
-                    fontWidthOfSpace = getFontWidth(SPACE_BYTES, 0, 1);
+                    fontWidthOfSpace = getWidth(32);
                 }
                 // use the average font width as fall back
                 if (fontWidthOfSpace <= 0)
@@ -870,24 +495,9 @@ public abstract class PDFont implements 
     }
 
     /**
-     * Returns the toUnicode mapping if present.
-     * 
-     * @return the CMap representing the toUnicode mapping
+     * Returns true if the font uses vertical writing mode.
      */
-    public CMap getToUnicodeCMap()
-    {
-        return toUnicodeCmap;
-    }
-
-    /**
-     * Returns the CMap if present.
-     * 
-     * @return the CMap representing the character encoding
-     */
-    public CMap getCMap()
-    {
-        return cmap;
-    }
+    public abstract boolean isVertical();
 
     /**
      * Calling this will release all cached information.
@@ -907,4 +517,10 @@ public abstract class PDFont implements 
     {
         return this.getCOSObject().hashCode();
     }
+
+    @Override
+    public String toString()
+    {
+        return getClass().getSimpleName() + " " + getName();
+    }
 }

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFontDescriptorAFM.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFontDescriptorAFM.java?rev=1621411&r1=1621410&r2=1621411&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFontDescriptorAFM.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFontDescriptorAFM.java Sat Aug 30 02:26:57 2014
@@ -17,30 +17,26 @@
 package org.apache.pdfbox.pdmodel.font;
 
 import java.io.IOException;
-
-import org.apache.fontbox.afm.FontMetric;
-
+import org.apache.fontbox.afm.FontMetrics;
 import org.apache.pdfbox.pdmodel.common.PDRectangle;
-
 import org.apache.fontbox.util.BoundingBox;
 
 /**
  * This class represents the font descriptor when the font information
  * is coming from an AFM file.
  *
- * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
- * @version $Revision: 1.3 $
+ * @author Ben Litchfield
  */
 public class PDFontDescriptorAFM extends PDFontDescriptor
 {
-    private FontMetric afm;
+    private FontMetrics afm;
 
     /**
      * Constructor.
      *
      * @param afmFile The AFM file.
      */
-    public PDFontDescriptorAFM( FontMetric afmFile )
+    public PDFontDescriptorAFM( FontMetrics afmFile )
     {
         afm = afmFile;
     }
@@ -158,6 +154,12 @@ public class PDFontDescriptorAFM extends
         throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
     }
 
+    @Override
+    public boolean isSymbolic()
+    {
+        return afm.getEncodingScheme().equals("FontSpecific");
+    }
+
     /**
      * This will get the fonts bouding box.
      *

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFontDescriptorDictionary.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFontDescriptorDictionary.java?rev=1621411&r1=1621410&r2=1621411&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFontDescriptorDictionary.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFontDescriptorDictionary.java Sat Aug 30 02:26:57 2014
@@ -42,16 +42,16 @@ public class PDFontDescriptorDictionary 
     private int flags = -1;
 
     /**
-     * Constructor.
+     * Package-private constructor, for internal PDFBox use only.
      */
-    public PDFontDescriptorDictionary()
+    PDFontDescriptorDictionary()
     {
         dic = new COSDictionary();
         dic.setItem( COSName.TYPE, COSName.FONT_DESC );
     }
 
     /**
-     * Constructor.
+     * Creates a PDFontDescriptor from a COS dictionary.
      *
      * @param desc The wrapped COS Dictionary.
      */
@@ -475,6 +475,14 @@ public class PDFontDescriptorDictionary 
     }
 
     /**
+     * Returns true if widths are present in the font descriptor.
+     */
+    public boolean hasWidths()
+    {
+        return dic.containsKey(COSName.WIDTHS) || dic.containsKey(COSName.MISSING_WIDTH);
+    }
+
+    /**
      * This will get the missing width for the font.
      *
      * @return The missing width value.

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFontFactory.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFontFactory.java?rev=1621411&r1=1621410&r2=1621411&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFontFactory.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFontFactory.java Sat Aug 30 02:26:57 2014
@@ -18,6 +18,7 @@ package org.apache.pdfbox.pdmodel.font;
 
 import java.io.IOException;
 
+import org.apache.pdfbox.cos.COSBase;
 import org.apache.pdfbox.cos.COSDictionary;
 import org.apache.pdfbox.cos.COSName;
 import org.apache.commons.logging.Log;
@@ -53,6 +54,14 @@ public class PDFontFactory
         COSName subType = dictionary.getCOSName(COSName.SUBTYPE);
         if (COSName.TYPE1.equals(subType))
         {
+            COSBase fd = dictionary.getDictionaryObject(COSName.FONT_DESC);
+            if (fd != null && fd instanceof COSDictionary)
+            {
+                if (((COSDictionary)fd).containsKey(COSName.FONT_FILE3))
+                {
+                    return new PDType1CFont(dictionary);
+                }
+            }
             return new PDType1Font(dictionary);
         }
         else if (COSName.MM_TYPE1.equals(subType))
@@ -101,17 +110,17 @@ public class PDFontFactory
         COSName type = dictionary.getCOSName(COSName.TYPE, COSName.FONT);
         if (!COSName.FONT.equals(type))
         {
-            throw new IOException("Expected 'Font' dictionary but found '" + type.getName() + "'");
+            throw new IllegalArgumentException("Expected 'Font' dictionary but found '" + type.getName() + "'");
         }
 
         COSName subType = dictionary.getCOSName(COSName.SUBTYPE);
         if (COSName.CID_FONT_TYPE0.equals(subType))
         {
-            return new PDCIDFontType0Font(dictionary, parent);
+            return new PDCIDFontType0(dictionary, parent);
         }
         else if (COSName.CID_FONT_TYPE2.equals(subType))
         {
-            return new PDCIDFontType2Font(dictionary, parent);
+            return new PDCIDFontType2(dictionary, parent);
         }
         else
         {

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDMMType1Font.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDMMType1Font.java?rev=1621411&r1=1621410&r2=1621411&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDMMType1Font.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDMMType1Font.java Sat Aug 30 02:26:57 2014
@@ -17,7 +17,8 @@
 package org.apache.pdfbox.pdmodel.font;
 
 import org.apache.pdfbox.cos.COSDictionary;
-import org.apache.pdfbox.cos.COSName;
+
+import java.io.IOException;
 
 /**
  * Type 1 Multiple Master Font.
@@ -27,19 +28,11 @@ import org.apache.pdfbox.cos.COSName;
 public class PDMMType1Font extends PDType1Font
 {
     /**
-     * Constructor.
-     */
-    public PDMMType1Font()
-    {
-        dict.setItem(COSName.SUBTYPE, COSName.MM_TYPE1);
-    }
-
-    /**
-     * Constructor.
+     * Creates an MMType1Font from a Font dictionary in a PDF.
      *
-     * @param fontDictionary The font dictionary according to the PDF specification.
+     * @param fontDictionary font dictionary
      */
-    public PDMMType1Font(COSDictionary fontDictionary)
+    public PDMMType1Font(COSDictionary fontDictionary) throws IOException
     {
         super(fontDictionary);
     }