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 2014/01/17 20:09:27 UTC

svn commit: r1559225 [2/2] - in /pdfbox/trunk: fontbox/src/main/java/org/apache/fontbox/cff/ fontbox/src/main/java/org/apache/fontbox/encoding/ fontbox/src/main/java/org/apache/fontbox/pfb/ fontbox/src/main/java/org/apache/fontbox/type1/ fontbox/src/te...

Added: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/type1/Type1Parser.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/type1/Type1Parser.java?rev=1559225&view=auto
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/type1/Type1Parser.java (added)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/type1/Type1Parser.java Fri Jan 17 19:09:26 2014
@@ -0,0 +1,749 @@
+/*
+ * 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.type1;
+
+import org.apache.fontbox.encoding.CustomEncoding;
+import org.apache.fontbox.encoding.StandardEncoding;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Parses an Adobe Type 1 (.pfb) font. It is used exclusively by Type1Font.
+ *
+ * The Type 1 font format is a free-text format which is somewhat difficult
+ * to parse. This is made worse by the fact that many Type 1 font files do
+ * not conform to the specification, especially those embedded in PDFs. This
+ * parser therefore tries to be as forgiving as possible.
+ *
+ * @see "Adobe Type 1 Font Format, Adobe Systems (1999)"
+ *
+ * @author John Hewson
+ */
+final class Type1Parser
+{
+    // constants for encryption
+    private static final int EEXEC_KEY = 55665;
+    private static final int CHARSTRING_KEY = 4330;
+
+    // state
+    private Type1Lexer lexer;
+    private Type1Font font;
+
+    /**
+     * Parses a Type 1 font and returns a Type1Font class which represents it.
+     *
+     * @param segment1 Segment 1: ASCII
+     * @param segment2 Segment 2: Binary
+     * @throws IOException
+     */
+    public Type1Font parse(byte[] segment1, byte[] segment2) throws IOException
+    {
+        font = new Type1Font();
+        parseASCII(segment1);
+        if (segment2.length > 0)
+        {
+            parseBinary(segment2);
+        }
+        return font;
+    }
+
+    /**
+     * Parses the ASCII porition of a Type 1 font.
+     */
+    private void parseASCII(byte[] bytes) throws IOException
+    {
+        // %!FontType1-1.0
+        // %!PS-AdobeFont-1.0
+        if (bytes[0] != '%' && bytes[1] != '!')
+        {
+            throw new IOException("Invalid start of ASCII segment");
+        }
+
+        lexer = new Type1Lexer(bytes);
+
+        // (corrupt?) synthetic font
+        if (lexer.peekToken().getText().equals("FontDirectory"))
+        {
+            read(Token.NAME, "FontDirectory");
+            read(Token.LITERAL); // font name
+            read(Token.NAME, "known");
+            read(Token.START_PROC);
+            readProc();
+            read(Token.START_PROC);
+            readProc();
+            read(Token.NAME, "ifelse");
+        }
+
+        // font dict
+        int length = read(Token.INTEGER).intValue();
+        read(Token.NAME, "dict");
+        read(Token.NAME, "begin");
+
+        for (int i = 0; i < length; i++)
+        {
+            // premature end
+            if (lexer.peekToken().getKind() == Token.NAME &&
+                    lexer.peekToken().getText().equals("currentdict"))
+            {
+                break;
+            }
+
+            // key/value
+            String key = read(Token.LITERAL).getText();
+            if (key.equals("FontInfo"))
+            {
+                readFontInfo(readSimpleDict());
+            }
+            else if (key.equals("Metrics"))
+            {
+                readSimpleDict();
+            }
+            else if (key.equals("Encoding"))
+            {
+                if (lexer.peekToken().getKind() == Token.NAME)
+                {
+                    String name = lexer.nextToken().getText();
+
+                    if (name.equals("StandardEncoding"))
+                    {
+                        font.encoding = StandardEncoding.INSTANCE;
+                    }
+                    else
+                    {
+                        throw new IOException("Unknown encoding: " + name);
+                    }
+                    read(Token.NAME, "def");
+                }
+                else
+                {
+                    read(Token.INTEGER).intValue();
+                    readMaybe(Token.NAME, "array");
+
+                    // 0 1 255 {1 index exch /.notdef put } for
+                    while (!(lexer.peekToken().getKind() == Token.NAME &&
+                            lexer.peekToken().getText().equals("dup")))
+                    {
+                        lexer.nextToken();
+                    }
+
+                    Map<Integer, String> codeToName = new HashMap<Integer, String>();
+                    while (lexer.peekToken().getKind() == Token.NAME &&
+                           lexer.peekToken().getText().equals("dup"))
+                    {
+                        read(Token.NAME, "dup");
+                        int code = read(Token.INTEGER).intValue();
+                        String name = read(Token.LITERAL).getText();
+                        read(Token.NAME, "put");
+                        codeToName.put(code, name);
+                    }
+                    font.encoding = new CustomEncoding(codeToName);
+                    readMaybe(Token.NAME, "readonly");
+                    read(Token.NAME, "def");
+                }
+            }
+            else
+            {
+                // simple value
+                List<Token> value = readDictValue();
+
+                if (key.equals("FontName"))
+                {
+                    font.fontName = value.get(0).getText();
+                }
+                else if (key.equals("PaintType"))
+                {
+                    font.paintType = value.get(0).intValue();
+                }
+                else if (key.equals("FontType"))
+                {
+                    font.fontType = value.get(0).intValue();
+                }
+                else if (key.equals("FontMatrix"))
+                {
+                    font.fontMatrix = arrayToNumbers(value);
+                }
+                else if (key.equals("FontBBox"))
+                {
+                    font.fontBBox = arrayToNumbers(value);
+                }
+                else if (key.equals("UniqueID"))
+                {
+                    font.uniqueID = value.get(0).intValue();
+                }
+                else if (key.equals("StrokeWidth"))
+                {
+                    font.strokeWidth = value.get(0).floatValue();
+                }
+                else if (key.equals("FID"))
+                {
+                    font.fontID = value.get(0).getText();
+                }
+            }
+        }
+
+        read(Token.NAME, "currentdict");
+        read(Token.NAME, "end");
+
+        read(Token.NAME, "currentfile");
+        read(Token.NAME, "eexec");
+    }
+
+    /**
+     * Extracts values from an array as numbers.
+     */
+    private List<Number> arrayToNumbers(List<Token> value) throws IOException
+    {
+        List<Number> numbers = new ArrayList<Number>();
+        for (int i = 1, size = value.size() - 1; i < size; i++)
+        {
+            Token token = value.get(i);
+            if (token.getKind() == Token.REAL)
+            {
+                numbers.add(token.floatValue());
+            }
+            else if (token.getKind() == Token.INTEGER)
+            {
+                numbers.add(token.intValue());
+            }
+            else
+            {
+               throw new IOException("Expected INTEGER or REAL but got " + token.getKind());
+            }
+        }
+        return numbers;
+    }
+
+    /**
+     * Extracts values from the /FontInfo dictionary.
+     */
+    private void readFontInfo(Map<String, List<Token>> fontInfo)
+    {
+        for (Map.Entry<String, List<Token>> entry : fontInfo.entrySet())
+        {
+            String key = entry.getKey();
+            List<Token> value = entry.getValue();
+
+            if (key.equals("version"))
+            {
+                font.version = value.get(0).getText();
+            }
+            else if (key.equals("Notice"))
+            {
+                font.notice = value.get(0).getText();
+            }
+            else if (key.equals("FullName"))
+            {
+                font.fullName = value.get(0).getText();
+            }
+            else if (key.equals("FamilyName"))
+            {
+                font.familyName = value.get(0).getText();
+            }
+            else if (key.equals("Weight"))
+            {
+                font.weight = value.get(0).getText();
+            }
+            else if (key.equals("ItalicAngle"))
+            {
+                font.italicAngle = value.get(0).floatValue();
+            }
+            else if (key.equals("isFixedPitch"))
+            {
+                font.isFixedPitch = value.get(0).booleanValue();
+            }
+            else if (key.equals("UnderlinePosition"))
+            {
+                font.underlinePosition = value.get(0).floatValue();
+            }
+            else if (key.equals("UnderlineThickness"))
+            {
+                font.underlineThickness = value.get(0).floatValue();
+            }
+        }
+    }
+
+    /**
+     * Reads a dictionary whose values are simple, i.e., do not contain
+     * nested dictionaries.
+     */
+    private Map<String, List<Token>> readSimpleDict() throws IOException
+    {
+        Map<String, List<Token>> dict = new HashMap<String, List<Token>>();
+
+        int length = read(Token.INTEGER).intValue();
+        read(Token.NAME, "dict");
+        readMaybe(Token.NAME, "dup");
+        read(Token.NAME, "begin");
+
+        for (int i = 0; i < length; i++)
+        {
+            // premature end
+            if (lexer.peekToken().getKind() == Token.NAME &&
+                    lexer.peekToken().getText().equals("end"))
+            {
+                break;
+            }
+
+            // simple value
+            String key = read(Token.LITERAL).getText();
+            List<Token> value = readDictValue();
+            dict.put(key, value);
+        }
+
+        read(Token.NAME, "end");
+        readMaybe(Token.NAME, "readonly");
+        read(Token.NAME, "def");
+
+        return dict;
+    }
+
+    /**
+     * Reads a simple value from a dictionary.
+     */
+    private List<Token> readDictValue() throws IOException
+    {
+        List<Token> value = readValue();
+        readDef();
+        return value;
+    }
+
+    /**
+     * Reads a simple value. This is either a number, a string,
+     * a name, a literal name, an array, a procedure, or a charstring.
+     * This method does not support reading nested dictionaries.
+     */
+    private List<Token> readValue() throws IOException
+    {
+        List<Token> value = new ArrayList<Token>();
+        Token token = lexer.nextToken();
+        value.add(token);
+
+        if (token.getKind() == Token.START_ARRAY)
+        {
+            int openArray = 1;
+            while (true)
+            {
+                if (lexer.peekToken().getKind() == Token.START_ARRAY)
+                {
+                    openArray++;
+                }
+
+                token = lexer.nextToken();
+                value.add(token);
+
+                if (token.getKind() == Token.END_ARRAY)
+                {
+                    openArray--;
+                    if (openArray == 0)
+                    {
+                        break;
+                    }
+                }
+            }
+        }
+        else if (token.getKind() == Token.START_PROC)
+        {
+            value.addAll(readProc());
+        }
+
+        // postscript wrapper (not in the Type 1 spec)
+        if (lexer.peekToken().getText().equals("systemdict"))
+        {
+            read(Token.NAME, "systemdict");
+            read(Token.LITERAL, "internaldict");
+            read(Token.NAME, "known");
+
+            read(Token.START_PROC);
+            readProc();
+
+            read(Token.START_PROC);
+            readProc();
+
+            read(Token.NAME, "ifelse");
+
+            // replace value
+            read(Token.START_PROC);
+            read(Token.NAME, "pop");
+            value.clear();
+            value.addAll(readValue());
+            read(Token.END_PROC);
+
+            read(Token.NAME, "if");
+        }
+        return value;
+    }
+
+    /**
+     * Reads a procedure.
+     */
+    private List<Token> readProc() throws IOException
+    {
+        List<Token> value = new ArrayList<Token>();
+
+        int openProc = 1;
+        while (true)
+        {
+            if (lexer.peekToken().getKind() == Token.START_PROC)
+            {
+                openProc++;
+            }
+
+            Token token = lexer.nextToken();
+            value.add(token);
+
+            if (token.getKind() == Token.END_PROC)
+            {
+                openProc--;
+                if (openProc == 0)
+                {
+                    break;
+                }
+            }
+        }
+        Token executeonly = readMaybe(Token.NAME, "executeonly");
+        if (executeonly != null)
+        {
+            value.add(executeonly);
+        }
+
+        return value;
+    }
+
+    /**
+     * Parses the binary portion of a Type 1 font.
+     */
+    private void parseBinary(byte[] bytes) throws IOException
+    {
+        byte[] decrypted = decrypt(bytes, EEXEC_KEY, 4);
+        lexer = new Type1Lexer(decrypted);
+
+        // find /Private dict
+        while (!lexer.peekToken().getText().equals("Private"))
+        {
+            lexer.nextToken();
+        }
+
+        // Private dict
+        read(Token.LITERAL, "Private");
+        int length = read(Token.INTEGER).intValue();
+        read(Token.NAME, "dict");
+        readMaybe(Token.NAME, "dup");
+        read(Token.NAME, "begin");
+
+        int levIV = 4; // number of random bytes at start of charstring
+
+        for (int i = 0; i < length; i++)
+        {
+            // premature end
+            if (lexer.peekToken().getKind() != Token.LITERAL)
+            {
+                break;
+            }
+
+            // key/value
+            String key = read(Token.LITERAL).getText();
+
+            if (key.equals("Subrs"))
+            {
+                readSubrs(levIV);
+            }
+            else if (key.equals("levIV"))
+            {
+                levIV = readDictValue().get(0).intValue();
+            }
+            else if (key.equals("ND"))
+            {
+                read(Token.START_PROC);
+                read(Token.NAME, "noaccess");
+                read(Token.NAME, "def");
+                read(Token.END_PROC);
+                read(Token.NAME, "executeonly");
+                read(Token.NAME, "def");
+            }
+            else if (key.equals("NP"))
+            {
+                read(Token.START_PROC);
+                read(Token.NAME, "noaccess");
+                read(Token.NAME, "put");
+                read(Token.END_PROC);
+                read(Token.NAME, "executeonly");
+                read(Token.NAME, "def");
+            }
+            else
+            {
+                readPrivate(key, readDictValue());
+            }
+        }
+
+        // some fonts have "2 index" here, others have "end noaccess put"
+        // sometimes followed by "put". Either way, we just skip until
+        // the /CharStrings dict is found
+        while (!(lexer.peekToken().getKind() == Token.LITERAL &&
+                lexer.peekToken().getText().equals("CharStrings")))
+        {
+            lexer.nextToken();
+        }
+
+        // CharStrings dict
+        read(Token.LITERAL, "CharStrings");
+        readCharStrings(levIV);
+    }
+
+    /**
+     * Extracts values from the /Private dictionary.
+     */
+    private void readPrivate(String key, List<Token> value) throws IOException
+    {
+        if (key.equals("BlueValues"))
+        {
+            font.blueValues = arrayToNumbers(value);
+        }
+        else if (key.equals("OtherBlues"))
+        {
+            font.otherBlues = arrayToNumbers(value);
+        }
+        else if (key.equals("FamilyBlues"))
+        {
+            font.familyBlues = arrayToNumbers(value);
+        }
+        else if (key.equals("FamilyOtherBlues"))
+        {
+            font.familyOtherBlues = arrayToNumbers(value);
+        }
+        else if (key.equals("BlueScale"))
+        {
+            font.blueScale = value.get(0).floatValue();
+        }
+        else if (key.equals("BlueShift"))
+        {
+            font.blueShift = value.get(0).intValue();
+        }
+        else if (key.equals("BlueFuzz"))
+        {
+            font.blueScale = value.get(0).intValue();
+        }
+        else if (key.equals("StdHW"))
+        {
+            font.stdHW = arrayToNumbers(value);
+        }
+        else if (key.equals("StdVW"))
+        {
+            font.stdVW = arrayToNumbers(value);
+        }
+        else if (key.equals("StemSnapH"))
+        {
+            font.stemSnapH = arrayToNumbers(value);
+        }
+        else if (key.equals("StemSnapV"))
+        {
+            font.stemSnapV = arrayToNumbers(value);
+        }
+        else if (key.equals("ForceBold"))
+        {
+            font.forceBold = value.get(0).booleanValue();
+        }
+        else if (key.equals("LanguageGroup"))
+        {
+            font.languageGroup = value.get(0).intValue();
+        }
+    }
+
+    /**
+     * Reads the /Subrs array.
+     * @param lenIV The number of random bytes used in charstring encryption.
+     */
+    private void readSubrs(int lenIV) throws IOException
+    {
+        int length = read(Token.INTEGER).intValue();
+        read(Token.NAME, "array");
+
+        for (int i = 0; i < length; i++)
+        {
+            // premature end
+            if (!(lexer.peekToken().getKind() == Token.NAME &&
+                    lexer.peekToken().getText().equals("dup")))
+            {
+                break;
+            }
+
+            read(Token.NAME, "dup");
+            read(Token.INTEGER);
+            read(Token.INTEGER);
+
+            // RD
+            Token charstring = read(Token.CHARSTRING);
+            font.subrs.add(decrypt(charstring.getData(), CHARSTRING_KEY, lenIV));
+            readPut();
+        }
+        readDef();
+    }
+
+    /**
+     * Reads the /CharStrings dictionary.
+     * @param lenIV The number of random bytes used in charstring encryption.
+     */
+    private void readCharStrings(int lenIV) throws IOException
+    {
+        int length = read(Token.INTEGER).intValue();
+        read(Token.NAME, "dict");
+        read(Token.NAME, "dup");
+        read(Token.NAME, "begin");
+
+        for (int i = 0; i < length; i++)
+        {
+            // key/value
+            String name = read(Token.LITERAL).getText();
+
+            // RD
+            read(Token.INTEGER);
+            Token charstring = read(Token.CHARSTRING);
+            font.charstrings.put(name, decrypt(charstring.getData(), CHARSTRING_KEY, lenIV));
+            readDef();
+        }
+
+        // some fonts have one "end", others two
+        read(Token.NAME, "end");
+    }
+
+    /**
+     * Reads the sequence "noaccess def" or equivalent.
+     */
+    private void readDef() throws IOException
+    {
+        readMaybe(Token.NAME, "readonly");
+
+        Token token = read(Token.NAME);
+        if (token.getText().equals("ND") || token.getText().equals("|-"))
+        {
+            return;
+        }
+        else if (token.getText().equals("noaccess")) {
+            token = read(Token.NAME);
+        }
+
+        if (token.getText().equals("def")) {
+            return;
+        }
+        throw new IOException("Found " + token + " but expected ND");
+    }
+
+    /**
+     * Reads the sequence "noaccess put" or equivalent.
+     */
+    private void readPut() throws IOException
+    {
+        readMaybe(Token.NAME, "readonly");
+
+        Token token = read(Token.NAME);
+        if (token.getText().equals("NP") || token.getText().equals("|"))
+        {
+            return;
+        }
+        else if (token.getText().equals("noaccess")) 
+        {
+            token = read(Token.NAME);
+        }
+
+        if (token.getText().equals("put")) 
+        {
+            return;
+        }
+        throw new IOException("Found " + token + " but expected NP");
+    }
+
+    /**
+     * Reads the next token and throws an error if it is not of the given kind.
+     */
+    private Token read(Token.Kind kind) throws IOException
+    {
+        Token token = lexer.nextToken();
+        if (token.getKind() != kind)
+        {
+            throw new IOException("Found " + token + " but expected " + kind);
+        }
+        return token;
+    }
+
+    /**
+     * Reads the next token and throws an error if it is not of the given kind
+     * and does not have the given value.
+     */
+    private void read(Token.Kind kind, String name) throws IOException
+    {
+        Token token = read(kind);
+        if (!token.getText().equals(name))
+        {
+            throw new IOException("Found " + token + " but expected " + name);
+        }
+    }
+
+    /**
+     * Reads the next token if and only if it is of the given kind and
+     * has the given value.
+     */
+    private Token readMaybe(Token.Kind kind, String name) throws IOException
+    {
+        Token token = lexer.peekToken();
+        if (token.getKind() == kind && token.getText().equals(name))
+        {
+            return lexer.nextToken();
+        }
+        return null;
+    }
+
+    /**
+     * Type 1 Decryption (eexec, charstring).
+     *
+     * @param cipherBytes cipher text
+     * @param r key
+     * @param n number of random bytes (lenIV)
+     * @return plain text
+     */
+    private byte[] decrypt(byte[] cipherBytes, int r, int n)
+    {
+        // lenIV of -1 means no encryption (not documented)
+        if (n == -1)
+        {
+            return cipherBytes;
+        }
+        // empty charstrings and charstrings of insufficient length
+        if (cipherBytes.length == 0 || n > cipherBytes.length)
+        {
+            return new byte[] {};
+        }
+        // decrypt
+        int c1 = 52845;
+        int c2 = 22719;
+        byte[] plainBytes = new byte[cipherBytes.length - n];
+        for (int i = 0; i < cipherBytes.length; i++)
+        {
+            int cipher = cipherBytes[i] & 0xFF;
+            int plain = cipher ^ r >> 8;
+            if (i >= n)
+            {
+                plainBytes[i - n] = (byte) plain;
+            }
+            r = (cipher + r) * c1 + c2 & 0xffff;
+        }
+        return plainBytes;
+    }
+}

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

Added: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/type1/package.html
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/type1/package.html?rev=1559225&view=auto
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/type1/package.html (added)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/type1/package.html Fri Jan 17 19:09:26 2014
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package holds classes used to parse Type1-Fonts.
+</body>
+</html>

Propchange: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/type1/package.html
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: pdfbox/trunk/fontbox/src/test/java/org/apache/fontbox/cff/Type1CharStringTest.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/test/java/org/apache/fontbox/cff/Type1CharStringTest.java?rev=1559225&r1=1559224&r2=1559225&view=diff
==============================================================================
--- pdfbox/trunk/fontbox/src/test/java/org/apache/fontbox/cff/Type1CharStringTest.java (original)
+++ pdfbox/trunk/fontbox/src/test/java/org/apache/fontbox/cff/Type1CharStringTest.java Fri Jan 17 19:09:26 2014
@@ -42,8 +42,8 @@ public class Type1CharStringTest
                 new int[] { 12, 0 }, new int[] { 31 });
 
         byte[] encodedCommands = new Type1CharStringFormatter().format(commands);
-        List<Object> decodedCommands = new Type1CharStringParser()
-                .parse(encodedCommands, new IndexData(0));
+        List<Object> decodedCommands = new Type1CharStringParser("TestFont", "TestGlyph")
+                .parse(encodedCommands, new ArrayList<byte[]>());
 
         assertEquals(1 + 2 + 1, encodedCommands.length);
 
@@ -61,8 +61,8 @@ public class Type1CharStringTest
                 0, 107, 108, 1131, 10000);
 
         byte[] encodedNumbers = new Type1CharStringFormatter().format(numbers);
-        List<Object> decodedNumbers = new Type1CharStringParser()
-                .parse(encodedNumbers, new IndexData(0));
+        List<Object> decodedNumbers = new Type1CharStringParser("TestFont", "TestGlyph")
+                .parse(encodedNumbers, new ArrayList<byte[]>());
 
         assertEquals(5 + 2 * 2 + 3 * 1 + 2 * 2 + 5, encodedNumbers.length);
 

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/PageDrawer.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/PageDrawer.java?rev=1559225&r1=1559224&r2=1559225&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/PageDrawer.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/PageDrawer.java Fri Jan 17 19:09:26 2014
@@ -21,7 +21,6 @@ import java.awt.Color;
 import java.awt.Composite;
 import java.awt.Dimension;
 import java.awt.Font;
-import java.awt.FontFormatException;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
 import java.awt.Image;
@@ -44,11 +43,12 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.fontbox.cff.CFFFont;
 import org.apache.fontbox.ttf.TrueTypeFont;
+import org.apache.fontbox.type1.Type1Font;
 import org.apache.pdfbox.cos.COSName;
 import org.apache.pdfbox.cos.COSStream;
-import org.apache.pdfbox.pdfviewer.font.CFFGlyph2D;
 import org.apache.pdfbox.pdfviewer.font.Glyph2D;
 import org.apache.pdfbox.pdfviewer.font.TTFGlyph2D;
+import org.apache.pdfbox.pdfviewer.font.Type1Glyph2D;
 import org.apache.pdfbox.pdmodel.PDPage;
 import org.apache.pdfbox.pdmodel.PDResources;
 import org.apache.pdfbox.pdmodel.common.PDMatrix;
@@ -291,8 +291,8 @@ public class PageDrawer extends PDFStrea
                 }
                 else
                 {
-                    // Use AWT to render the font (Type1 fonts, standard14 fonts, if the embedded font is substituted)
-                    // TODO to be removed in the long run?
+                    // Use AWT to render the font (standard14 fonts, substituted embedded fonts)
+                    // TODO to be removed in the long run
                     drawString((PDSimpleFont) font, text.getCharacter(), at);
                 }
             }
@@ -316,7 +316,7 @@ public class PageDrawer extends PDFStrea
         graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
         for (int i = 0; i < codePoints.length; i++)
         {
-            GeneralPath path = glyph2D.getPathForCharactercode(codePoints[i]);
+            GeneralPath path = glyph2D.getPathForCharacterCode(codePoints[i]);
             if (path != null)
             {
                 AffineTransform atInverse = null;
@@ -382,12 +382,11 @@ public class PageDrawer extends PDFStrea
 
     /**
      * This will draw a string on a canvas using the font.
-     * 
+     *
      * @param font the font to be used to draw the string
      * @param string The string to draw.
-     * @param g The graphics to draw onto.
      * @param at The transformation matrix with all information for scaling and shearing of the font.
-     * 
+     *
      * @throws IOException If there is an error drawing the specific string.
      */
     private void drawString(PDSimpleFont font, String string, AffineTransform at) throws IOException
@@ -442,19 +441,7 @@ public class PageDrawer extends PDFStrea
                 if (fd instanceof PDFontDescriptorDictionary)
                 {
                     PDFontDescriptorDictionary fdDictionary = (PDFontDescriptorDictionary) fd;
-                    if (fdDictionary.getFontFile() != null)
-                    {
-                        try
-                        {
-                            // create a type1 font with the embedded data
-                            awtFont = Font.createFont(Font.TYPE1_FONT, fdDictionary.getFontFile().createInputStream());
-                        }
-                        catch (FontFormatException e)
-                        {
-                            LOG.info("Can't read the embedded type1 font " + fd.getFontName());
-                        }
-                    }
-                    if (awtFont == null)
+                    if (fdDictionary.getFontFile() == null)
                     {
                         // check if the font is part of our environment
                         awtFont = FontManager.getAwtFont(fd.getFontName());
@@ -490,19 +477,16 @@ public class PageDrawer extends PDFStrea
                 LOG.info("Using font " + awtFont.getName() + " instead");
                 font.setIsFontSubstituted(true);
             }
-            if (awtFont != null)
-            {
-                awtFonts.put(font, awtFont);
-            }
+            awtFonts.put(font, awtFont);
         }
         return awtFont;
     }
 
     /**
-     * Provide a Glyh2d for the given font if supported.
+     * Provide a Glyph2D for the given font.
      * 
      * @param font the font
-     * @return the implementation of the Glyph2D interface for the given font if supported
+     * @return the implementation of the Glyph2D interface for the given font
      * @throws IOException if something went wrong
      */
     private Glyph2D createGlyph2D(PDFont font) throws IOException
@@ -528,15 +512,24 @@ public class PageDrawer extends PDFStrea
             }
             else if (font instanceof PDType1Font)
             {
-                PDType1Font type1Font = (PDType1Font) font;
-                PDType1CFont type1CFont = type1Font.getType1CFont();
+                PDType1Font pdType1Font = (PDType1Font) font;
+                PDType1CFont type1CFont = pdType1Font.getType1CFont();
                 if (type1CFont != null)
                 {
                     // get the cffFont raw data
                     CFFFont cffFont = type1CFont.getCFFFont();
                     if (cffFont != null)
                     {
-                        glyph2D = new CFFGlyph2D(cffFont, type1CFont.getFontEncoding());
+                        glyph2D = new Type1Glyph2D(cffFont, type1CFont.getFontEncoding());
+                    }
+                }
+                else
+                {
+                    // get the pfb raw data
+                    Type1Font type1Font = pdType1Font.getType1Font();
+                    if (type1Font != null)
+                    {
+                        glyph2D = new Type1Glyph2D(type1Font, pdType1Font.getFontEncoding());
                     }
                 }
             }
@@ -565,7 +558,7 @@ public class PageDrawer extends PDFStrea
                         CFFFont cffFont = type1CFont.getCFFFont();
                         if (cffFont != null)
                         {
-                            glyph2D = new CFFGlyph2D(cffFont, type1CFont.getFontEncoding());
+                            glyph2D = new Type1Glyph2D(cffFont, type1CFont.getFontEncoding());
                         }
                     }
                 }

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/font/Glyph2D.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/font/Glyph2D.java?rev=1559225&r1=1559224&r2=1559225&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/font/Glyph2D.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/font/Glyph2D.java Fri Jan 17 19:09:26 2014
@@ -28,16 +28,6 @@ import java.awt.geom.GeneralPath;
  */
 public interface Glyph2D
 {
-
-    /**
-     * Returns the path describing the glyph for the given glyphId.
-     * 
-     * @param glyphId the glyphId
-     * 
-     * @return the GeneralPath for the given glyphId
-     */
-    public GeneralPath getPathForGlyphId(int glyphId);
-
     /**
      * Returns the path describing the glyph for the given character code.
      * 
@@ -45,7 +35,7 @@ public interface Glyph2D
      * 
      * @return the GeneralPath for the given character code
      */
-    public GeneralPath getPathForCharactercode(int code);
+    public GeneralPath getPathForCharacterCode(int code);
 
     /**
      * Returns the number of glyphs provided by the given font.

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/font/TTFGlyph2D.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/font/TTFGlyph2D.java?rev=1559225&r1=1559224&r2=1559225&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/font/TTFGlyph2D.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/font/TTFGlyph2D.java Fri Jan 17 19:09:26 2014
@@ -193,9 +193,12 @@ public class TTFGlyph2D implements Glyph
     }
 
     /**
-     * {@inheritDoc}
+     * Returns the path describing the glyph for the given glyphId.
+     *
+     * @param glyphId the glyphId
+     *
+     * @return the GeneralPath for the given glyphId
      */
-    @Override
     public GeneralPath getPathForGlyphId(int glyphId)
     {
         GeneralPath glyphPath = null;
@@ -334,7 +337,7 @@ public class TTFGlyph2D implements Glyph
      * {@inheritDoc}
      */
     @Override
-    public GeneralPath getPathForCharactercode(int code)
+    public GeneralPath getPathForCharacterCode(int code)
     {
 
         int glyphId = getGlyphcode(code);

Added: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/font/Type1Glyph2D.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/font/Type1Glyph2D.java?rev=1559225&view=auto
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/font/Type1Glyph2D.java (added)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/font/Type1Glyph2D.java Fri Jan 17 19:09:26 2014
@@ -0,0 +1,160 @@
+/*
+ * 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.pdfbox.pdfviewer.font;
+
+import java.awt.geom.GeneralPath;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.fontbox.cff.CFFFont;
+import org.apache.fontbox.type1.Type1Font;
+import org.apache.fontbox.type1.Type1Mapping;
+import org.apache.pdfbox.encoding.Encoding;
+
+/**
+ * This class provides a glyph to GeneralPath conversion for Type 1 PFB and CFF fonts.
+ */
+public class Type1Glyph2D implements Glyph2D
+{
+    /**
+     * Log instance.
+     */
+    private static final Log LOG = LogFactory.getLog(Type1Glyph2D.class);
+
+    private HashMap<String, GeneralPath> glyphs = new HashMap<String, GeneralPath>();
+    private Map<Integer, String> codeToName = new HashMap<Integer, String>();
+    private String fontName = null;
+
+    /**
+     * Constructs a new Type1Glyph2D object for a CFF/Type2 font.
+     *
+     * @param font CFF/Type2 font
+     * @param encoding PDF Encoding or null
+     */
+    public Type1Glyph2D(CFFFont font, Encoding encoding)
+    {
+        this(font.getName(), font.getType1Mappings(), encoding);
+    }
+
+    /**
+     * Constructs a new Type1Glyph2D object for a Type 1 (PFB) font.
+     *
+     * @param font Type 1 (PFB) font
+     * @param encoding PDF Encoding or null
+     */
+    public Type1Glyph2D(Type1Font font, Encoding encoding)
+    {
+        this(font.getFontName(), font.getType1Mappings(), encoding);
+    }
+
+    /**
+     * Private constructor.
+     */
+    private Type1Glyph2D(String fontName, Collection<? extends Type1Mapping> mappings, Encoding encoding)
+    {
+        this.fontName = fontName;
+        // start with built-in encoding
+        for (Type1Mapping mapping : mappings)
+        {
+            codeToName.put(mapping.getCode(), mapping.getName());
+        }
+        // override existing entries with an optional PDF Encoding
+        if (encoding != null) 
+        {
+            Map<Integer, String> encodingCodeToName = encoding.getCodeToNameMap();
+            for (Integer key : encodingCodeToName.keySet())
+            {
+                codeToName.put(key, encodingCodeToName.get(key));
+            }
+        }
+        for (Type1Mapping mapping : mappings)
+        {
+            GeneralPath path;
+            try
+            {
+                path = mapping.getType1CharString().getPath();
+                glyphs.put(mapping.getName(), path);
+            }
+            catch (IOException exception)
+            {
+                LOG.error("Type 1 glyph rendering failed", exception);
+            }
+        }
+    }
+
+    /**
+     * Returns the path describing the glyph for the given name.
+     *
+     * @param name the name of the glyph
+     * @return the GeneralPath for the given glyph
+     */
+    public GeneralPath getPathForGlyphName(String name)
+    {
+        return glyphs.get(name);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GeneralPath getPathForCharacterCode(int code)
+    {
+        if (codeToName.containsKey(code))
+        {
+            String name = codeToName.get(code);
+            return glyphs.get(name);
+        }
+        else
+        {
+            LOG.debug(fontName + ": glyph mapping for " + code + " not found");
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getNumberOfGlyphs()
+    {
+        if (glyphs != null)
+        {
+            return glyphs.size();
+        }
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void dispose()
+    {
+        if (glyphs != null)
+        {
+            glyphs.clear();
+        }
+        if (codeToName != null)
+        {
+            codeToName.clear();
+        }
+    }
+}

Propchange: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/font/Type1Glyph2D.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1Font.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1Font.java?rev=1559225&r1=1559224&r2=1559225&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1Font.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1Font.java Fri Jan 17 19:09:26 2014
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -29,11 +30,13 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.fontbox.afm.AFMParser;
 import org.apache.fontbox.afm.FontMetric;
+import org.apache.fontbox.type1.Type1Font;
 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.COSName;
+import org.apache.pdfbox.cos.COSStream;
 import org.apache.pdfbox.cos.COSString;
 import org.apache.pdfbox.encoding.AFMEncoding;
 import org.apache.pdfbox.encoding.Encoding;
@@ -59,6 +62,8 @@ public class PDType1Font extends PDSimpl
     private static final Log LOG = LogFactory.getLog(PDType1Font.class);
 
     private PDType1CFont type1CFont = null;
+    private Type1Font type1font = null;
+
     /**
      * Standard Base 14 Font.
      */
@@ -218,9 +223,32 @@ public class PDType1Font extends PDSimpl
                 {
                     type1CFont = new PDType1CFont(super.font);
                 }
-                catch (IOException exception)
+                catch (IOException e)
+                {
+                    LOG.error("Can't read the embedded Type1C font " + fd.getFontName(), e);
+                }
+            }
+
+            // or it may contain a PFB
+            PDStream fontFile = ((PDFontDescriptorDictionary) fd).getFontFile();
+            if (fontFile != null)
+            {
+                try
                 {
-                    LOG.info("Can't read the embedded type1C font " + fd.getFontName());
+                    COSStream stream = fontFile.getStream();
+                    int length1 = stream.getInt(COSName.LENGTH1);
+                    int length2 = stream.getInt(COSName.LENGTH2);
+
+                    // the PFB embedded as two segments back-to-back
+                    byte[] bytes = fontFile.getByteArray();
+                    byte[] segment1 = Arrays.copyOfRange(bytes, 0, length1);
+                    byte[] segment2 = Arrays.copyOfRange(bytes, length1, length1 + length2);
+
+                    type1font =  Type1Font.createWithSegments(segment1, segment2);
+                }
+                catch (IOException e)
+                {
+                    LOG.error("Can't read the embedded Type1 font " + fd.getFontName(), e);
                 }
             }
         }
@@ -311,7 +339,7 @@ public class PDType1Font extends PDSimpl
 
     /**
      * Tries to get the encoding for the type1 font.
-     * 
+     *
      */
     private void getEncodingFromFont(boolean extractEncoding)
     {
@@ -518,10 +546,20 @@ public class PDType1Font extends PDSimpl
     /**
      * Returns the embedded Type1C font if available.
      * 
-     * @return the type1C font
+     * @return the Type1C font
      */
     public PDType1CFont getType1CFont()
     {
         return type1CFont;
     }
+
+    /**
+     * Returns the embedded Type font if available.
+     *
+     * @return the Type1 font
+     */
+    public Type1Font getType1Font()
+    {
+        return type1font;
+    }
 }