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;
+ }
}