xmlgraphics-fop-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rme...@apache.org
Subject svn commit: r1597112 [1/2] - in /xmlgraphics/fop/trunk: src/java/org/apache/fop/fonts/ src/java/org/apache/fop/fonts/truetype/ src/java/org/apache/fop/fonts/type1/ src/java/org/apache/fop/pdf/ src/java/org/apache/fop/render/ps/ test/java/org/apache/fop...
Date Fri, 23 May 2014 15:05:20 GMT
Author: rmeyer
Date: Fri May 23 15:05:19 2014
New Revision: 1597112

URL: http://svn.apache.org/r1597112
Log:
FOP-2354: Subset support for Type 1 fonts

Added:
    xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/PostscriptParser.java
    xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/Type1SubsetFile.java
    xmlgraphics/fop/trunk/test/java/org/apache/fop/fonts/type1/PostscriptParserTestCase.java
    xmlgraphics/fop/trunk/test/java/org/apache/fop/fonts/type1/Type1SubsetFileTestCase.java
    xmlgraphics/fop/trunk/test/resources/fonts/type1/
    xmlgraphics/fop/trunk/test/resources/fonts/type1/c0419bt_.afm
    xmlgraphics/fop/trunk/test/resources/fonts/type1/c0419bt_.pfb   (with props)
Modified:
    xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/AbstractCodePointMapping.java
    xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/FontLoader.java
    xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/FontReader.java
    xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/SingleByteFont.java
    xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/truetype/OFFontLoader.java
    xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/AdobeStandardEncoding.java
    xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/PFBData.java
    xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java
    xmlgraphics/fop/trunk/src/java/org/apache/fop/pdf/PDFFactory.java
    xmlgraphics/fop/trunk/src/java/org/apache/fop/render/ps/PSFontUtils.java

Modified: xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/AbstractCodePointMapping.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/AbstractCodePointMapping.java?rev=1597112&r1=1597111&r2=1597112&view=diff
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/AbstractCodePointMapping.java (original)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/AbstractCodePointMapping.java Fri May 23 15:05:19 2014
@@ -179,6 +179,10 @@ public class AbstractCodePointMapping im
         return -1;
     }
 
+    public String getNameFromCodePoint(int idx) {
+        return getCharNameMap()[idx];
+    }
+
     /** {@inheritDoc} */
     public String[] getCharNameMap() {
         if (this.charNameMap != null) {

Modified: xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/FontLoader.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/FontLoader.java?rev=1597112&r1=1597111&r2=1597112&view=diff
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/FontLoader.java (original)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/FontLoader.java Fri May 23 15:05:19 2014
@@ -99,11 +99,8 @@ public abstract class FontLoader {
                 throw new IllegalArgumentException(
                         "CID encoding mode not supported for Type 1 fonts");
             }
-            if (embeddingMode == EmbeddingMode.SUBSET) {
-                throw new IllegalArgumentException(
-                        "Subset embedding for Type 1 fonts is not supported");
-            }
-            loader = new Type1FontLoader(fontFileURI, embedded, useKerning, resourceResolver);
+            loader = new Type1FontLoader(fontFileURI, embedded, embeddingMode, useKerning,
+                    resourceResolver);
         } else {
             loader = new OFFontLoader(fontFileURI, subFontName, embedded, embeddingMode,
                     encodingMode, useKerning, useAdvanced, resourceResolver);

Modified: xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/FontReader.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/FontReader.java?rev=1597112&r1=1597111&r2=1597112&view=diff
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/FontReader.java (original)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/FontReader.java Fri May 23 15:05:19 2014
@@ -162,13 +162,13 @@ public class FontReader extends DefaultH
                 isCID = true;
                 TTFReader.checkMetricsVersion(attributes);
             } else if ("TRUETYPE".equals(attributes.getValue("type"))) {
-                singleFont = new SingleByteFont(resourceResolver);
+                singleFont = new SingleByteFont(resourceResolver, EmbeddingMode.AUTO);
                 singleFont.setFontType(FontType.TRUETYPE);
                 returnFont = singleFont;
                 isCID = false;
                 TTFReader.checkMetricsVersion(attributes);
             } else {
-                singleFont = new SingleByteFont(resourceResolver);
+                singleFont = new SingleByteFont(resourceResolver, EmbeddingMode.AUTO);
                 singleFont.setFontType(FontType.TYPE1);
                 returnFont = singleFont;
                 isCID = false;

Modified: xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/SingleByteFont.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/SingleByteFont.java?rev=1597112&r1=1597111&r2=1597112&view=diff
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/SingleByteFont.java (original)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/SingleByteFont.java Fri May 23 15:05:19 2014
@@ -21,7 +21,9 @@ package org.apache.fop.fonts;
 
 import java.awt.Rectangle;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -34,6 +36,7 @@ import org.apache.xmlgraphics.fonts.Glyp
 
 import org.apache.fop.apps.io.InternalResourceResolver;
 import org.apache.fop.fonts.truetype.OpenFont.PostScriptVersion;
+import org.apache.fop.util.CharUtilities;
 
 /**
  * Generic SingleByte font
@@ -56,14 +59,30 @@ public class SingleByteFont extends Cust
 
     private PostScriptVersion ttPostScriptVersion;
 
-    /**
-     * @param resourceResolver the URI resolver for controlling file access
-     */
+    private int usedGlyphsCount;
+    private LinkedHashMap<Integer, String> usedGlyphNames;
+    private Map<Integer, Integer> usedGlyphs;
+    private Map<Integer, Character> usedCharsIndex;
+
     public SingleByteFont(InternalResourceResolver resourceResolver) {
         super(resourceResolver);
         setEncoding(CodePointMapping.WIN_ANSI_ENCODING);
     }
 
+    public SingleByteFont(InternalResourceResolver resourceResolver, EmbeddingMode embeddingMode) {
+        this(resourceResolver);
+        setEmbeddingMode(embeddingMode);
+        if (embeddingMode != EmbeddingMode.FULL) {
+            usedGlyphNames = new LinkedHashMap<Integer, String>();
+            usedGlyphs = new HashMap<Integer, Integer>();
+            usedCharsIndex = new HashMap<Integer, Character>();
+
+            // The zeroth value is reserved for .notdef
+            usedGlyphs.put(0, 0);
+            usedGlyphsCount++;
+        }
+    }
+
     /** {@inheritDoc} */
     public boolean isEmbeddable() {
         return (!(getEmbedFileURI() == null
@@ -72,7 +91,7 @@ public class SingleByteFont extends Cust
 
     /** {@inheritDoc} */
     public boolean isSubsetEmbedded() {
-        return false;
+        return getEmbeddingMode() != EmbeddingMode.FULL;
     }
 
     /** {@inheritDoc} */
@@ -182,22 +201,53 @@ public class SingleByteFont extends Cust
         return d;
     }
 
+    private boolean isSubset() {
+        return getEmbeddingMode() == EmbeddingMode.SUBSET;
+    }
+
     /** {@inheritDoc} */
     @Override
     public char mapChar(char c) {
         notifyMapOperation();
         char d = lookupChar(c);
-        if (d != SingleByteEncoding.NOT_FOUND_CODE_POINT) {
-            return d;
-        } else {
+        if (d == SingleByteEncoding.NOT_FOUND_CODE_POINT) {
             // Check for alternative
             d = findAlternative(c);
             if (d != SingleByteEncoding.NOT_FOUND_CODE_POINT) {
                 return d;
+            } else {
+                this.warnMissingGlyph(c);
+                return Typeface.NOT_FOUND;
             }
         }
-        this.warnMissingGlyph(c);
-        return Typeface.NOT_FOUND;
+        if (isEmbeddable() && isSubset()) {
+            mapChar(d, c);
+        }
+        return d;
+    }
+
+    private int mapChar(int glyphIndex, char unicode) {
+        // Reencode to a new subset font or get the reencoded value
+        // IOW, accumulate the accessed characters and build a character map for them
+        Integer subsetCharSelector = usedGlyphs.get(glyphIndex);
+        if (subsetCharSelector == null) {
+            int selector = usedGlyphsCount;
+            usedGlyphs.put(glyphIndex, selector);
+            usedCharsIndex.put(selector, unicode);
+            usedGlyphsCount++;
+            return selector;
+        } else {
+            return subsetCharSelector;
+        }
+    }
+
+    private char getUnicode(int index) {
+        Character mapValue = usedCharsIndex.get(index);
+        if (mapValue != null) {
+            return mapValue.charValue();
+        } else {
+            return CharUtilities.NOT_A_CHARACTER;
+        }
     }
 
     private char mapUnencodedChar(char ch) {
@@ -457,5 +507,34 @@ public class SingleByteFont extends Cust
         return ttPostScriptVersion;
     }
 
+    /**
+     * Returns a Map of used Glyphs.
+     * @return Map Map of used Glyphs
+     */
+    public Map<Integer, Integer> getUsedGlyphs() {
+        return Collections.unmodifiableMap(usedGlyphs);
+    }
+
+    public char getUnicodeFromSelector(int selector) {
+        return getUnicode(selector);
+    }
+
+    public void mapUsedGlyphName(int gid, String value) {
+        usedGlyphNames.put(gid, value);
+    }
+
+    public Map<Integer, String> getUsedGlyphNames() {
+        return usedGlyphNames;
+    }
+
+    public String getGlyphName(int idx) {
+        if (idx < mapping.getCharNameMap().length) {
+            return mapping.getCharNameMap()[idx];
+        } else {
+            int selector = usedGlyphs.get(idx);
+            char theChar = usedCharsIndex.get(selector);
+            return unencodedCharacters.get(theChar).getCharacter().getName();
+        }
+    }
 }
 

Modified: xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/truetype/OFFontLoader.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/truetype/OFFontLoader.java?rev=1597112&r1=1597111&r2=1597112&view=diff
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/truetype/OFFontLoader.java (original)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/truetype/OFFontLoader.java Fri May 23 15:05:19 2014
@@ -137,7 +137,7 @@ public class OFFontLoader extends FontLo
             returnFont = multiFont;
             multiFont.setTTCName(ttcFontName);
         } else {
-            singleFont = new SingleByteFont(resourceResolver);
+            singleFont = new SingleByteFont(resourceResolver, embeddingMode);
             returnFont = singleFont;
         }
 

Modified: xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/AdobeStandardEncoding.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/AdobeStandardEncoding.java?rev=1597112&r1=1597111&r2=1597112&view=diff
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/AdobeStandardEncoding.java (original)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/AdobeStandardEncoding.java Fri May 23 15:05:19 2014
@@ -26,7 +26,7 @@ import java.util.Map;
  * Enumerates the {@linkplain http://unicode.org/Public/MAPPINGS/VENDORS/ADOBE/stdenc.txt} for
  * characters  found in a Type1 font.
  */
-enum AdobeStandardEncoding {
+public enum AdobeStandardEncoding {
     /** space character */
     space(0x0020, 0x20, "SPACE", "space"),
     /** space character */
@@ -407,4 +407,13 @@ enum AdobeStandardEncoding {
         AdobeStandardEncoding encoding = CACHE.get(adobeName);
         return encoding != null ? encoding.getAdobeCodePoint() : -1;
     }
+
+    public static String getCharFromCodePoint(int codePoint) {
+        for (AdobeStandardEncoding encoding : CACHE.values()) {
+            if (encoding.getAdobeCodePoint() == codePoint) {
+                return encoding.getAdobeName();
+            }
+        }
+        return "";
+    }
 }

Modified: xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/PFBData.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/PFBData.java?rev=1597112&r1=1597111&r2=1597112&view=diff
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/PFBData.java (original)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/PFBData.java Fri May 23 15:05:19 2014
@@ -85,6 +85,14 @@ public class PFBData {
     }
 
     /**
+     * Gets the header segment of the font file
+     * @return Header segment as a byte array
+     */
+    public byte[] getHeaderSegment() {
+        return this.headerSegment.clone();
+    }
+
+    /**
      * Sets the encrypted segment of the font file.
      * @param encryptedSeg the encrypted segment
      */
@@ -93,6 +101,14 @@ public class PFBData {
     }
 
     /**
+     * Gets the encrypted segment of the font file
+     * @return The encrypted segment as a byte array
+     */
+    public byte[] getEncryptedSegment() {
+        return this.encryptedSegment.clone();
+    }
+
+    /**
      * Sets the trailer segment of the font file.
      * @param trailerSeg the trailer segment
      */
@@ -101,6 +117,14 @@ public class PFBData {
     }
 
     /**
+     * Gets the trailer segment of the font file
+     * @return The trailer segment as a byte array
+     */
+    public byte[] getTrailerSegment() {
+        return this.trailerSegment.clone();
+    }
+
+    /**
      * Returns the full length of the raw font file.
      * @return int the raw file length
      */

Added: xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/PostscriptParser.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/PostscriptParser.java?rev=1597112&view=auto
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/PostscriptParser.java (added)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/PostscriptParser.java Fri May 23 15:05:19 2014
@@ -0,0 +1,655 @@
+/*
+ * 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.fonts.type1;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Scanner;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+class PostscriptParser {
+
+    protected static final Log LOG = LogFactory.getLog(PostscriptParser.class);
+    /* Patterns used to identify Postscript elements */
+    private static final String DICTIONARY = "dict";
+    private static final String FIXED_ARRAY = "array";
+    private static final String VARIABLE_ARRAY = "[";
+    private static final String SUBROUTINE = "{";
+    /* A list of parsed subroutines so if they are encountered during the parsing
+     * phase of another element, they can be read and pattern matched. */
+    private HashMap<String, PSSubroutine> subroutines = new HashMap<String, PSSubroutine>();
+
+    /**
+     * Parses the postscript document and returns a list of elements
+     * @param segment The byte array containing the postscript data
+     * @return A list of found Postscript elements
+     * @throws IOException
+     */
+    public List<PSElement> parse(byte[] segment) throws IOException {
+        List<PSElement> parsedElements = new ArrayList<PSElement>();
+        /* Currently only scan and store the top level element. For deeper
+         * Postscript parsing you can push and pop elements from a stack */
+        PSElement foundElement = null;
+        String operator = null;
+        StringBuilder token = new StringBuilder();
+        List<String> tokens = new ArrayList<String>();
+        int startPoint = -1;
+        boolean specialDelimiter = false;
+        boolean lastWasSpecial = false;
+        for (int i = 0; i < segment.length; i++) {
+            byte cur = segment[i];
+            if (foundElement != null && foundElement.hasMore()) {
+                foundElement.parse(cur, i);
+                continue;
+            } else {
+                char c = (char)cur;
+                if (!lastWasSpecial) {
+                    specialDelimiter = (c == '{' || c == '}' || c == '[' || c == ']'
+                            || (!token.toString().equals("") && c == '/'));
+                    boolean isNotBreak = !(c == ' ' || c == '\r' || cur == 15 || cur == 12
+                            || cur == 10);
+                    if (isNotBreak && !specialDelimiter) {
+                        token.append(c);
+                        continue;
+                    }
+                } else {
+                    lastWasSpecial = false;
+                    token.append(c);
+                    if (token.toString().equals("/")) {
+                        continue;
+                    }
+                }
+            }
+            try {
+                boolean setOp = false;
+                if ((foundElement == null || !foundElement.hasMore()) && token.length() > 1
+                        && token.charAt(0) == '/' && tokens.size() != 1 || hasEndToken(token.toString())) {
+                    operator = token.toString();
+                    setOp = true;
+                    if (tokens.size() > 2 && tokens.get(tokens.size() - 1).equals("def")) {
+                        PSVariable newVar = new PSVariable(tokens.get(0), startPoint);
+                        newVar.setValue(tokens.get(1));
+                        newVar.setEndPoint(i - operator.length());
+                        parsedElements.add(newVar);
+                    }
+                    tokens.clear();
+                    startPoint = i - token.length();
+                }
+                if (operator != null) {
+                    if (foundElement instanceof PSSubroutine) {
+                        PSSubroutine sub = (PSSubroutine)foundElement;
+                        subroutines.put(sub.getOperator(), sub);
+                        parsedElements.add(sub);
+                        if (!setOp) {
+                            operator = "";
+                        }
+                    } else {
+                        if (foundElement != null) {
+                            if (!hasMatch(foundElement.getOperator(), parsedElements)) {
+                                parsedElements.add(foundElement);
+                            } else {
+                                LOG.warn("Duplicate " + foundElement.getOperator()
+                                        + " in font file, Ignoring.");
+                            }
+                        }
+                    }
+                    //Compare token against patterns and create an element if matched
+                    foundElement = createElement(operator, token.toString(), startPoint);
+                }
+            } finally {
+                tokens.add(token.toString());
+                token = new StringBuilder();
+                if (specialDelimiter) {
+                    specialDelimiter = false;
+                    lastWasSpecial = true;
+                    //Retrace special postscript character so it can be processed separately
+                    i--;
+                }
+            }
+        }
+        return parsedElements;
+    }
+
+    private boolean hasEndToken(String token) {
+        return token.equals("currentdict");
+    }
+
+    private boolean hasMatch(String operator, List<PSElement> elements) {
+        for (PSElement element : elements) {
+            if (element.getOperator().equals(operator)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public PSElement createElement(String operator, String elementID, int startPoint) {
+        if (operator.equals("")) {
+            return null;
+        }
+        if (elementID.equals(FIXED_ARRAY)) {
+            return new PSFixedArray(operator, startPoint);
+        } else if (elementID.equals(VARIABLE_ARRAY)) {
+            return new PSVariableArray(operator, startPoint);
+        } else if (elementID.equals(SUBROUTINE)) {
+            return new PSSubroutine(operator, startPoint);
+        } else if (!operator.equals("/Private") && elementID.equals(DICTIONARY)) {
+            return new PSDictionary(operator, startPoint);
+        }
+        return null;
+    }
+
+    /**
+     * A base Postscript element class
+     */
+    public abstract class PSElement {
+        /* The identifying operator for this element */
+        protected String operator;
+        private List<Byte> token;
+        /* Determines whether there is any more data to be read whilst parsing */
+        protected boolean hasMore = true;
+        /* The locations of any entries containing binary data (e.g. arrays) */
+        protected LinkedHashMap<String, int[]> binaryEntries;
+        /* The tokens parsed from the current element */
+        protected List<String> tokens;
+        /* Determines whether binary data is currently being read / parsed */
+        protected boolean readBinary = false;
+        /* The location of the element within the binary data */
+        private int startPoint = -1;
+        protected int endPoint = -1;
+        /* A flag to determine if unexpected postscript has been found in the element */
+        private boolean foundUnexpected = false;
+
+        public PSElement(String operator, int startPoint) {
+            this.operator = operator;
+            this.startPoint = startPoint;
+            token = new ArrayList<Byte>();
+            binaryEntries = new LinkedHashMap<String, int[]>();
+            tokens = new ArrayList<String>();
+        }
+
+        /**
+         * Gets the Postscript element operator
+         * @return The operator returned as a string
+         */
+        public String getOperator() {
+            return operator;
+        }
+
+        /**
+         * The start location of the element within the source binary data
+         * @return The start location returned as an integer
+         */
+        public int getStartPoint() {
+            return startPoint;
+        }
+
+        /**
+         * The end location of the element within the source binary data
+         * @return The end location returned as an integer
+         */
+        public int getEndPoint() {
+            return endPoint;
+        }
+
+        /**
+         * Takes over the task of tokenizing the byte data
+         * @param cur The current byte being read
+         */
+        public void parse(byte cur, int pos) throws UnsupportedEncodingException {
+            if (!readBinary) {
+                char c = (char)cur;
+                boolean specialDelimiter = (c == '{' || c == '}' || c == '[' || c == ']'
+                        || c == '(' || c == ')');
+                boolean isNotValidBreak = !(c == ' ' || cur == 15 || cur == 12 || c == '\r'
+                        || c == 10);
+                if (isNotValidBreak && !specialDelimiter) {
+                    token.add(cur);
+                } else {
+                    parseToken(pos);
+                }
+                if (specialDelimiter) {
+                    token.add(cur);
+                    parseToken(pos);
+                }
+            } else {
+                parseByte(cur, pos);
+            }
+        }
+
+        private void parseToken(int pos) throws UnsupportedEncodingException {
+            byte[] bytesToken = new byte[token.size()];
+            for (int i = 0; i < token.size(); i++) {
+                bytesToken[i] = token.get(i).byteValue();
+            }
+            parseToken(new String(bytesToken, "ASCII"), pos);
+            token.clear();
+        }
+
+        /**
+         * Passes responsibility for processing the byte stream to the PostScript object
+         * @param cur The byte currently being read
+         * @param pos The position of the given byte
+         */
+        public abstract void parseByte(byte cur, int pos);
+
+        /**
+         * Delegates the parse routine to a sub class
+         * @param token The token which to parse
+         */
+        public abstract void parseToken(String token, int curPos);
+
+        protected boolean isInteger(String intValue) {
+            try {
+                Integer.parseInt(intValue);
+                return true;
+            } catch (NumberFormatException ex) {
+                return false;
+            }
+        }
+
+        public LinkedHashMap<String, int[]> getBinaryEntries() {
+            return binaryEntries;
+        }
+
+        /**
+         * Gets the binary entry location of a given index from the array
+         * @param index The index for which to retrieve the binary data location
+         * @return
+         */
+        public int[] getBinaryEntryByIndex(int index) {
+            int count = 0;
+            for (Entry<String, int[]> entry : binaryEntries.entrySet()) {
+                if (count == index) {
+                    return entry.getValue();
+                }
+                count++;
+            }
+            return new int[0];
+        }
+
+        /**
+         * Determines if more data is still to be parsed for the Postscript element.
+         * @return Returns true if more data exists
+         */
+        public boolean hasMore() {
+            return hasMore;
+        }
+
+        /**
+         * Sets a value to be true if an expected entry postscript is found in the element.
+         * An example is where the encoding table may have a series of postscript operators
+         * altering the state of the array. In this case the only option will be to
+         * fully embed the font to avoid incorrect encoding in the resulting subset.
+         * @param foundUnexpected true if unexpected postscript is found.
+         */
+        protected void setFoundUnexpected(boolean foundUnexpected) {
+            this.foundUnexpected = foundUnexpected;
+        }
+
+        /**
+         * Returns whether unexpected postscript has been found in the element
+         * @return true if unexpected postscript is found
+         */
+        public boolean getFoundUnexpected() {
+            return this.foundUnexpected;
+        }
+    }
+
+    /**
+     * An object representing a Postscript array with a fixed number of entries
+     */
+    public class PSFixedArray extends PSElement {
+
+        private String entry = "";
+        private String token = "";
+        private boolean finished = false;
+        protected int binaryLength = 0;
+        /* A list containing each entry and it's contents in the array */
+        private HashMap<Integer, String> entries;
+        private static final String READ_ONLY = "readonly";
+
+        public PSFixedArray(String operator, int startPoint) {
+            super(operator, startPoint);
+            entries = new HashMap<Integer, String>();
+        }
+
+        @Override
+        public void parseToken(String token, int curPos) {
+            if (!checkForEnd(token) || token.equals("def")) {
+                hasMore = false;
+                endPoint = curPos;
+                return;
+            }
+            if (token.equals("dup")) {
+                if (entry.startsWith("dup")) {
+                    addEntry(entry);
+                }
+                entry = "";
+                tokens.clear();
+            }
+            if (!token.equals(READ_ONLY)) {
+                entry += token + " ";
+            }
+            if (!token.trim().equals("")) {
+                tokens.add(token);
+            }
+            if (tokens.size() == 4 && tokens.get(0).equals("dup") && isInteger(tokens.get(2))) {
+                binaryLength = Integer.parseInt(tokens.get(2));
+                readBinary = true;
+            }
+        }
+
+        private boolean checkForEnd(String checkToken) {
+            boolean subFound = false;
+            //Check for a subroutine matching that of an array end definition
+            PSSubroutine sub = subroutines.get("/" + checkToken);
+            if (sub != null && sub.getSubroutine().contains("def")) {
+                subFound = true;
+            }
+            if (!finished && (subFound || checkToken.equals("def"))) {
+                finished = true;
+                addEntry(entry);
+                return false;
+            } else {
+                return !finished;
+            }
+        }
+
+        /**
+         * Gets a map of array entries identified by index
+         * @return Returns the map of array entries
+         */
+        public HashMap<Integer, String> getEntries() {
+            return entries;
+        }
+
+        private void addEntry(String entry) {
+            if (!entry.equals("")) {
+                if (entry.indexOf('/') != -1 && entry.charAt(entry.indexOf('/') - 1) != ' ') {
+                    entry = entry.replace("/", " /");
+                }
+                int entryLen;
+                do {
+                    entryLen = entry.length();
+                    entry = entry.replace("  ", " ");
+                } while (entry.length() != entryLen);
+                Scanner s = new Scanner(entry).useDelimiter(" ");
+                boolean valid = false;
+                do {
+                    s.next();
+                    if (!s.hasNext()) {
+                        break;
+                    }
+                    int id = s.nextInt();
+                    entries.put(id, entry);
+                    valid = true;
+                } while (false);
+                if (!valid) {
+                    setFoundUnexpected(true);
+                }
+            }
+        }
+
+        @Override
+        public void parseByte(byte cur, int pos) {
+            if (binaryLength > 0) {
+                token += (char)cur;
+                binaryLength--;
+            } else {
+                if (readBinary) {
+                    int bLength = Integer.parseInt(tokens.get(2));
+                    int start = pos - bLength;
+                    int end = start + bLength;
+                    binaryEntries.put(tokens.get(1), new int[] {start, end});
+                    token = "";
+                    readBinary = false;
+                } else {
+                    tokens.add(token);
+                    parseToken(token, pos);
+                    token = "";
+                }
+            }
+        }
+    }
+
+    /**
+     * An object representing a Postscript array with a variable number of entries
+     */
+    public class PSVariableArray extends PSElement {
+        private int level = 0;
+        private List<String> arrayItems;
+        private String entry = "";
+
+        public PSVariableArray(String operator, int startPoint) {
+            super(operator, startPoint);
+            arrayItems = new ArrayList<String>();
+        }
+
+        @Override
+        public void parseToken(String token, int curPos) {
+            entry += token + " ";
+            if (level <= 0 && token.length() > 0 && token.charAt(0) == ']') {
+                hasMore = false;
+                endPoint = curPos;
+                return;
+            }
+            /* If the array item is a subroutine, the following keeps track of the current level
+             * of the tokens being parsed so that it can identify the finish */
+            if (token.equals("{")) {
+                level++;
+            } else if (token.equals("}")) {
+                level--;
+                if (!entry.equals("") && level == 0) {
+                    arrayItems.add(entry);
+                    entry = "";
+                }
+            }
+        }
+
+        /**
+         * Gets a list of found array entries within the variable array
+         * @return Returns the found array elements as a list
+         */
+        public List<String> getEntries() {
+            return arrayItems;
+        }
+
+        @Override
+        public void parseByte(byte cur, int pos) {
+            //Not currently used
+        }
+    }
+
+    /**
+     * An object representing a Postscript subroutine element
+     */
+    public class PSSubroutine extends PSElement {
+        private int level = 1;
+        private String entry = "";
+
+        public PSSubroutine(String operator, int startPoint) {
+            super(operator, startPoint);
+        }
+
+        @Override
+        public void parseToken(String token, int curPos) {
+            if (level == 0 && token.length() > 0 && (token.equals("def") || token.equals("ifelse")
+                    || token.charAt(0) == '}')) {
+                hasMore = false;
+                endPoint = curPos;
+                return;
+            }
+            if (token.equals("{")) {
+                level++;
+            } else if (token.equals("}")) {
+                level--;
+            }
+            entry += token + " ";
+        }
+
+        /**
+         * Gets the parsed subroutine element as unmodified string
+         * @return The subroutine as a string
+         */
+        public String getSubroutine() {
+            return entry.trim();
+        }
+
+        @Override
+        public void parseByte(byte cur, int pos) {
+            //Not currently used
+        }
+    }
+
+    /**
+     * An object representing a Postscript dictionary
+     */
+    public class PSDictionary extends PSElement {
+        /* A list of dictionary entries which they themselves could be variables,
+         * subroutines and arrays, This is currently left as parsed Strings as there is
+         * no need to delve deeper for our current purposes. */
+        private HashMap<String, String> entries;
+        private String entry = "";
+        private String token = "";
+        protected int binaryLength = 0;
+
+        public PSDictionary(String operator, int startPoint) {
+            super(operator, startPoint);
+            entries = new HashMap<String, String>();
+        }
+
+        @Override
+        public void parseToken(String token, int curPos) {
+            if (token.equals("end")) {
+                addEntry(entry);
+                hasMore = false;
+                endPoint = curPos;
+                return;
+            }
+            if (token.startsWith("/")) {
+                if (entry.trim().startsWith("/")) {
+                    tokens.clear();
+                    addEntry(entry);
+                }
+                entry = "";
+            }
+            if (tokens.size() >= 1 || token.startsWith("/")) {
+                tokens.add(token);
+            }
+            entry += token + " ";
+            if (tokens.size() == 3 && tokens.get(0).startsWith("/") && !tokens.get(2).equals("def")
+                    && isInteger(tokens.get(1))) {
+                binaryLength = Integer.parseInt(tokens.get(1));
+                readBinary = true;
+            }
+        }
+
+        /**
+         * Gets a map of dictionary entries identified by their name
+         * @return Returns the dictionary entries as a map
+         */
+        public HashMap<String, String> getEntries() {
+            return entries;
+        }
+
+        private void addEntry(String entry) {
+            Scanner s = new Scanner(entry).useDelimiter(" ");
+            String id = s.next();
+            entries.put(id, entry);
+        }
+
+        @Override
+        public void parseByte(byte cur, int pos) {
+            if (binaryLength > 0) {
+                binaryLength--;
+            } else {
+                if (readBinary) {
+                    int start = pos - Integer.parseInt(tokens.get(1));
+                    int end = pos;
+                    binaryEntries.put(tokens.get(0), new int[] {start, end});
+                    readBinary = false;
+                } else {
+                    tokens.add(token);
+                    parseToken(token, pos);
+                }
+            }
+        }
+    }
+
+    /**
+     * An object representing a Postscript variable
+     */
+    public class PSVariable extends PSElement {
+
+        /* The value of the parsed Postscript variable. */
+        private String value = "";
+
+        public PSVariable(String operator, int startPoint) {
+            super(operator, startPoint);
+        }
+
+        @Override
+        public void parseToken(String token, int curPos) {
+            if (token.equals("def")) {
+                hasMore = false;
+                endPoint = curPos;
+                return;
+            }
+        }
+
+        @Override
+        public void parseByte(byte cur, int pos) {
+            //Not currently used
+        }
+
+        /**
+         * Sets the value of the Postscript variable value
+         * @param value The value to set
+         */
+        public void setValue(String value) {
+            this.value = value;
+        }
+
+        /**
+         * Gets the value of the Postscript variable
+         * @return Returns the value as a String
+         */
+        public String getValue() {
+            return value;
+        }
+
+        /**
+         * Sets the end point location of the current Postscript variable.
+         * @param endPoint The end point location as an integer
+         */
+        public void setEndPoint(int endPoint) {
+            this.endPoint = endPoint;
+        }
+
+    }
+}

Modified: xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java?rev=1597112&r1=1597111&r2=1597112&view=diff
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java (original)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java Fri May 23 15:05:19 2014
@@ -34,6 +34,7 @@ import org.apache.commons.logging.LogFac
 
 import org.apache.fop.apps.io.InternalResourceResolver;
 import org.apache.fop.fonts.CodePointMapping;
+import org.apache.fop.fonts.EmbeddingMode;
 import org.apache.fop.fonts.FontLoader;
 import org.apache.fop.fonts.FontType;
 import org.apache.fop.fonts.SingleByteEncoding;
@@ -48,6 +49,8 @@ public class Type1FontLoader extends Fon
 
     private SingleByteFont singleFont;
 
+    private EmbeddingMode embeddingMode;
+
     /**
      * Constructs a new Type 1 font loader.
      * @param fontFileURI the URI to the PFB file of a Type 1 font
@@ -56,9 +59,10 @@ public class Type1FontLoader extends Fon
      * @param resourceResolver the font resolver used to resolve URIs
      * @throws IOException In case of an I/O error
      */
-    public Type1FontLoader(URI fontFileURI, boolean embedded, boolean useKerning,
-            InternalResourceResolver resourceResolver) throws IOException {
+    public Type1FontLoader(URI fontFileURI, boolean embedded, EmbeddingMode embeddingMode,
+            boolean useKerning, InternalResourceResolver resourceResolver) throws IOException {
         super(fontFileURI, embedded, useKerning, true, resourceResolver);
+        this.embeddingMode = embeddingMode;
     }
 
     private String getPFMURI(String pfbURI) {
@@ -137,7 +141,7 @@ public class Type1FontLoader extends Fon
         if (afm == null && pfm == null) {
             throw new IllegalArgumentException("Need at least an AFM or a PFM!");
         }
-        singleFont = new SingleByteFont(resourceResolver);
+        singleFont = new SingleByteFont(resourceResolver, embeddingMode);
         singleFont.setFontType(FontType.TYPE1);
         if (this.embedded) {
             singleFont.setEmbedURI(this.fontFileURI);

Added: xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/Type1SubsetFile.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/Type1SubsetFile.java?rev=1597112&view=auto
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/Type1SubsetFile.java (added)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/fonts/type1/Type1SubsetFile.java Fri May 23 15:05:19 2014
@@ -0,0 +1,772 @@
+/*
+ * 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.fonts.type1;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Scanner;
+import java.util.Set;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.fonts.SingleByteFont;
+import org.apache.fop.fonts.type1.PostscriptParser.PSDictionary;
+import org.apache.fop.fonts.type1.PostscriptParser.PSElement;
+import org.apache.fop.fonts.type1.PostscriptParser.PSFixedArray;
+import org.apache.fop.fonts.type1.PostscriptParser.PSSubroutine;
+import org.apache.fop.fonts.type1.PostscriptParser.PSVariable;
+
+public class Type1SubsetFile {
+
+    protected static final Log LOG = LogFactory.getLog(Type1SubsetFile.class);
+    /* The subset list of char strings */
+    private HashMap<String, byte[]> subsetCharStrings;
+    /* The list of character names in the subset font */
+    private List<String> charNames = null;
+    /* A list of unique subroutines references */
+    private LinkedHashMap<Integer, byte[]> uniqueSubs;
+    private SingleByteFont sbfont = null;
+    /* New line character */
+    private String eol = "\n";
+    /* An option to determine whether the subroutines are subset */
+    private boolean subsetSubroutines = true;
+    private byte[] fullFont;
+    //List of parsed Postscript elements
+    private List<PSElement> headerSection;
+    private List<PSElement> mainSection;
+    //Determines whether the current font uses standard encoding
+    private boolean standardEncoding = false;
+
+    //Type 1 operators
+    private static final int OP_SEAC = 6;
+    private static final int OP_CALLSUBR = 10;
+    private static final int OP_CALLOTHERSUBR = 16;
+
+    public byte[] createSubset(InputStream in, SingleByteFont sbfont,
+            String fontPrefix) throws IOException {
+        fullFont = IOUtils.toByteArray(in);
+        byte[] subsetFont = createSubset(sbfont, fontPrefix, true);
+        //This should never happen but ensure that subset is shorter than original font
+        return (subsetFont.length == 0 || subsetFont.length > fullFont.length)
+                ? fullFont : subsetFont;
+    }
+
+    /**
+     * Creates a new subset from the given type 1 font input stream
+     * @param in The type 1 font to subset
+     * @param sbfont The font object containing information such as the
+     * characters from which to create the subset
+     * @param fontPrefix The prefix used in identifying the subset font
+     * @param allSubroutines This option will force the subset to include all
+     * subroutines.
+     * @return Returns the subset as a byte array
+     * @throws IOException
+     */
+    private byte[] createSubset(SingleByteFont sbfont,
+            String fontPrefix, boolean subsetSubroutines) throws IOException {
+        this.subsetSubroutines = subsetSubroutines;
+        InputStream in = new ByteArrayInputStream(fullFont);
+        //Initialise resources used for the font creation
+        this.sbfont = sbfont;
+        PFBParser pfbParser = new PFBParser();
+        PFBData pfbData = pfbParser.parsePFB(in);
+
+        PostscriptParser psParser = new PostscriptParser();
+        charNames = new ArrayList<String>();
+
+        //Parse the header section of the font
+        if (headerSection == null) {
+            headerSection = psParser.parse(pfbData.getHeaderSegment());
+        }
+
+        //Read the encoding section
+        PSElement encoding = getElement("/Encoding", headerSection);
+        if (encoding.getFoundUnexpected()) {
+            //Fully embed the font as we're unable to interpret postscript on arrays
+            return new byte[0];
+        }
+        List<String> subsetEncodingEntries = readEncoding(encoding);
+
+        //Decode the main section in preparation for parsing
+        byte[] decoded = BinaryCoder.decodeBytes(pfbData.getEncryptedSegment(), 55665, 4);
+
+        //Initialise the resources used to hold the subset data
+        uniqueSubs = new LinkedHashMap<Integer, byte[]>();
+        subsetCharStrings = new HashMap<String, byte[]>();
+
+        //Parse the encoded main font section for elements
+        if (mainSection == null) {
+            mainSection = psParser.parse(decoded);
+        }
+
+        //Process and write the main section
+        PSElement charStrings = getElement("/CharStrings", mainSection);
+        int result = readMainSection(mainSection, decoded, subsetEncodingEntries, charStrings);
+        if (result == 0) {
+            /* This check handles the case where a font uses a postscript method to return a
+             * subroutine index. As there is currently no java postscript interpreter and writing
+             * one would be very difficult it prevents us from handling this eventuality. The way
+             * this issue is being handled is to restart the subset process and include all
+             * subroutines. */
+            uniqueSubs.clear();
+            subsetCharStrings.clear();
+            charNames.clear();
+            return createSubset(sbfont, fontPrefix, false);
+        }
+
+        //Write header section
+        ByteArrayOutputStream boasHeader = writeHeader(pfbData, encoding, subsetEncodingEntries);
+
+        ByteArrayOutputStream boasMain = writeMainSection(decoded, mainSection, charStrings);
+        byte[] mainSectionBytes = boasMain.toByteArray();
+        mainSectionBytes = BinaryCoder.encodeBytes(mainSectionBytes, 55665, 4);
+        boasMain = new ByteArrayOutputStream();
+        boasMain.write(mainSectionBytes);
+
+        ByteArrayOutputStream baosTrailer = new ByteArrayOutputStream();
+        baosTrailer.write(pfbData.getTrailerSegment(), 0, pfbData.getTrailerSegment().length);
+
+        return stitchFont(boasHeader, boasMain, baosTrailer);
+    }
+
+    byte[] stitchFont(ByteArrayOutputStream boasHeader, ByteArrayOutputStream boasMain,
+            ByteArrayOutputStream boasTrailer) throws IOException {
+        int headerLength = boasHeader.size();
+        int mainLength = boasMain.size();
+
+        boasMain.write(128);
+        boasMain.write(1);
+        updateSectionSize(boasTrailer.size()).writeTo(boasMain);
+        boasTrailer.write(128);
+        boasTrailer.write(3);
+
+        boasTrailer.writeTo(boasMain);
+
+        boasHeader.write(128);
+        boasHeader.write(2);
+        //You need to encode the main section first before getting it's size!!!
+        updateSectionSize(mainLength).writeTo(boasHeader);
+        boasMain.writeTo(boasHeader);
+
+        ByteArrayOutputStream fullFont = new ByteArrayOutputStream();
+        fullFont.write(128);
+        fullFont.write(1);
+        updateSectionSize(headerLength).writeTo(fullFont);
+        boasHeader.writeTo(fullFont);
+
+        return fullFont.toByteArray();
+    }
+
+    private List<String> readEncoding(PSElement encoding) {
+        Map<Integer, Integer> usedGlyphs = sbfont.getUsedGlyphs();
+        List<Integer> glyphs = new ArrayList<Integer>(usedGlyphs.keySet());
+        Collections.sort(glyphs);
+        List<String> subsetEncodingEntries = new ArrayList<String>();
+        //Handle custom encoding
+        if (encoding instanceof PSFixedArray) {
+            PSFixedArray encodingArray = (PSFixedArray)encoding;
+            for (int glyph : glyphs) {
+                /* Search for matching entries in the original font encoding table to add
+                 * to the subset. As there may be more than one entry for a character (as
+                 * was the case in a font where some glyphs were duplicated), a name search is
+                 * performed and all matching entries are added. */
+                List<String> matches = searchEntries(encodingArray.getEntries(), glyph);
+                /* If no matches are found, create a new entry for the character so
+                 * that it can be added even if it's not in the current encoding. */
+                if (matches.size() == 0) {
+                    matches = new ArrayList<String>();
+                    if (glyph == 0) {
+                        matches.add("dup 0 /.notdef put");
+                    } else {
+                        matches.add(String.format("dup %d /%s put", glyph,
+                                sbfont.getGlyphName(glyph)));
+                    }
+                }
+                for (String match : matches) {
+                    subsetEncodingEntries.add(match);
+                    addToCharNames(match);
+                }
+            }
+        //Handle fixed encoding
+        } else if (encoding instanceof PSVariable) {
+            if (((PSVariable) encoding).getValue().equals("StandardEncoding")) {
+                standardEncoding = true;
+                sbfont.mapUsedGlyphName(0, "/.notdef");
+                for (int glyph : glyphs) {
+                    //Retrieve the character name and alternates for the given glyph
+                    String name = sbfont.getGlyphName(glyph);
+                    if (glyph != 0 && name != null && !name.trim().equals("")) {
+                        sbfont.mapUsedGlyphName(glyph, "/" + name);
+                    }
+                }
+            } else {
+                LOG.warn("Only Custom or StandardEncoding is supported when creating a Type 1 subset.");
+            }
+        }
+        return subsetEncodingEntries;
+    }
+
+    private List<String> searchEntries(HashMap<Integer, String> encodingEntries, int glyph) {
+        List<String> matches = new ArrayList<String>();
+        for (Entry<Integer, String> entry : encodingEntries.entrySet()) {
+            String tag = getEntryPart(entry.getValue(), 3);
+            String name = sbfont.getGlyphName(sbfont.getUsedGlyphs().get(glyph));
+            if (name.equals(tag)) {
+                matches.add(entry.getValue());
+            }
+        }
+        return matches;
+    }
+
+    private ByteArrayOutputStream writeHeader(PFBData pfbData, PSElement encoding,
+            List<String> subsetEncodingEntries) throws UnsupportedEncodingException,
+            IOException {
+        ByteArrayOutputStream boasHeader = new ByteArrayOutputStream();
+        boasHeader.write(pfbData.getHeaderSegment(), 0, encoding.getStartPoint() - 1);
+
+        if (!standardEncoding) {
+            //Write out the new encoding table for the subset font
+            String encodingArray = eol + String.format("/Encoding %d array", 256) + eol
+                    + "0 1 255 {1 index exch /.notdef put } for" + eol;
+            byte[] encodingDefinition = encodingArray.getBytes("ASCII");
+            boasHeader.write(encodingDefinition, 0, encodingDefinition.length);
+            Set<Entry<Integer, String>> entrySet = sbfont.getUsedGlyphNames().entrySet();
+            for (Entry<Integer, String> entry : entrySet) {
+                String arrayEntry = String.format("dup %d %s put", entry.getKey(),
+                        entry.getValue());
+                writeString(arrayEntry + eol, boasHeader);
+            }
+            writeString("readonly def" + eol, boasHeader);
+        } else {
+            String theEncoding = eol + "/Encoding StandardEncoding def" + eol;
+            boasHeader.write(theEncoding.getBytes("ASCII"));
+        }
+        boasHeader.write(pfbData.getHeaderSegment(), encoding.getEndPoint(),
+                pfbData.getHeaderSegment().length - encoding.getEndPoint());
+
+        return boasHeader;
+    }
+
+    ByteArrayOutputStream updateSectionSize(int size) throws IOException {
+        //Update the size in the header for the previous section
+        ByteArrayOutputStream boas = new ByteArrayOutputStream();
+        byte[] lowOrderSize = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(
+                size).array();
+        boas.write(lowOrderSize);
+        return boas;
+    }
+
+    private int readMainSection(List<PSElement> mainSection, byte[] decoded,
+            List<String> subsetEncodingEntries, PSElement charStrings) {
+        subsetEncodingEntries.add(0, "dup 0 /.notdef put");
+        /* Reads and parses the charStrings section to subset the charString
+         * and it's referenced subroutines found in the main section for each glyph. */
+        PSDictionary charStringsDict = (PSDictionary)charStrings;
+        for (String tag : sbfont.getUsedGlyphNames().values()) {
+            if (!tag.equals("/.notdef")) {
+                charNames.add(tag);
+            }
+
+            int[] location = charStringsDict.getBinaryEntries().get(tag);
+            if (location == null) {
+                continue;
+            }
+            byte[] charStringEntry = getBinaryEntry(location, decoded);
+
+            int skipBytes = 4;
+            PSElement element = getElement("lenIV", mainSection);
+            if (element != null && element instanceof PSVariable) {
+                PSVariable lenIV = (PSVariable)element;
+                try {
+                    skipBytes = Integer.parseInt(lenIV.getValue());
+                } catch (NumberFormatException ex) {
+                    LOG.warn(String.format("Invalid value `%s` for lenIV found in font %s", lenIV.getValue(),
+                            sbfont.getEmbedFileURI().toString()));
+                }
+            }
+
+            charStringEntry = BinaryCoder.decodeBytes(charStringEntry, 4330, skipBytes);
+            PSFixedArray subroutines = (PSFixedArray)getElement("/Subrs", mainSection);
+            if (subsetSubroutines) {
+                /* Recursively scan the charString array for subroutines and if found, copy the
+                 * entry to our subset entries and update any references. */
+                charStringEntry = createSubsetCharStrings(decoded, charStringEntry, subroutines,
+                        subsetEncodingEntries, tag);
+            }
+            if (charStringEntry.length == 0) {
+                return 0;
+            }
+            charStringEntry = BinaryCoder.encodeBytes(charStringEntry, 4330, skipBytes);
+            subsetCharStrings.put(tag, charStringEntry);
+        }
+        return 1;
+    }
+
+    private byte[] createSubsetCharStrings(byte[] decoded, byte[] data, PSFixedArray subroutines,
+            List<String> subsetEncodingEntries, String glyphName) {
+        List<BytesNumber> operands = new ArrayList<BytesNumber>();
+        for (int i = 0; i < data.length; i++) {
+            int cur = data[i] & 0xFF;
+            if (cur <= 31) {
+                int dataLength = data.length;
+                if (cur == OP_CALLSUBR) {
+                    //Found subroutine. Read subroutine and recursively scan and update references
+                    if (operands.size() == 0) {
+                        continue;
+                    }
+                    if (uniqueSubs.get(operands.get(operands.size() - 1).getNumber()) == null) {
+                        uniqueSubs.put(operands.get(operands.size() - 1).getNumber(), new byte[0]);
+                        data = addSubroutine(subroutines, operands, decoded, subsetEncodingEntries,
+                                glyphName, data, i, 1, -1, operands.get(
+                                        operands.size() - 1).getNumber());
+                    } else {
+                        data = addSubroutine(subroutines, operands, decoded, subsetEncodingEntries,
+                                glyphName, data, i, 1, getSubrIndex(operands.get(
+                                        operands.size() - 1).getNumber()), operands.get(
+                                                operands.size() - 1).getNumber());
+                    }
+                } else if (cur == 12) {
+                    int next = data[++i] & 0xFF;
+                    if (next == OP_SEAC) {
+                        /* This charString references two other glyphs which must also be included
+                         * for this character to be displayed properly. */
+                        int first = operands.get(operands.size() - 2).getNumber();
+                        int second = operands.get(operands.size() - 1).getNumber();
+                        String charFirst = AdobeStandardEncoding.getCharFromCodePoint(first);
+                        String charSecond = AdobeStandardEncoding.getCharFromCodePoint(second);
+                        subsetEncodingEntries.add(String.format("dup %d /%s put",
+                                first, charFirst));
+                        subsetEncodingEntries.add(String.format("dup %d /%s put",
+                                second, charSecond));
+                        sbfont.mapUsedGlyphName(first, "/" + charFirst);
+                        sbfont.mapUsedGlyphName(second, "/" + charSecond);
+                    } else if (next == OP_CALLOTHERSUBR) {
+                        /* Search for a specific operator chain which results in a referenced
+                         * subroutine being returned from a postscript method. If it's found then
+                         * return null so the subset process can be restarted and all subroutines
+                         * can be included. */
+                        int[] pattern = {12, 17, 10};
+                        int count = 0;
+                        boolean matchesPattern = true;
+                        if (data.length > i + 4) {
+                            for (int pos = i + 1; pos < i + 4; pos++) {
+                                if (data[pos] != pattern[count++]) {
+                                    matchesPattern = false;
+                                }
+                            }
+                        }
+                        if (matchesPattern) {
+                            return new byte[0];
+                        }
+                        data = addSubroutine(subroutines, operands, decoded, subsetEncodingEntries,
+                                glyphName, data, i, 2, -1, operands.get(0).getNumber());
+                    }
+                }
+                if (data.length == 0) {
+                    return new byte[0];
+                }
+                i -= dataLength - data.length;
+                operands.clear();
+            } else if (cur <= 246) {
+                operands.add(new BytesNumber(cur - 139, 1));
+            } else if (cur <= 250) {
+                operands.add(new BytesNumber((cur - 247) * 256 + (data[i + 1] & 0xFF) + 108, 2));
+                i++;
+            } else if (cur <= 254) {
+                operands.add(new BytesNumber(-(cur - 251) * 256 - (data[i + 1] & 0xFF) - 108, 2));
+                i++;
+            } else if (cur == 255) {
+                int b1 = data[i + 1] & 0xFF;
+                int b2 = data[i + 2] & 0xFF;
+                int b3 = data[i + 3] & 0xFF;
+                int b4 = data[i + 4] & 0xFF;
+                int value = b1 << 24 | b2 << 16 | b3 << 8 | b4;
+                operands.add(new BytesNumber(value, 5));
+                i += 4;
+            }
+        }
+        return data;
+    }
+
+    private int getSubrIndex(int subID) {
+        int count = 0;
+        for (Integer key : uniqueSubs.keySet()) {
+            if (key == subID) {
+                return count;
+            }
+            count++;
+        }
+        return -1;
+    }
+
+    private byte[] addSubroutine(PSFixedArray subroutines, List<BytesNumber> operands, byte[] decoded,
+            List<String> subsetEncodingEntries, String glyphName, byte[] data, int i, int opLength,
+            int existingSubrRef, int subrID) {
+        if (existingSubrRef == -1) {
+            int[] subrData = subroutines.getBinaryEntryByIndex(subrID);
+            byte[] subroutine = getBinaryEntry(subrData, decoded);
+            subroutine = BinaryCoder.decodeBytes(subroutine, 4330, 4);
+            subroutine = createSubsetCharStrings(decoded, subroutine, subroutines,
+                    subsetEncodingEntries, glyphName);
+            if (subroutine.length == 0) {
+                return new byte[0];
+            }
+            //Encode data
+            subroutine = BinaryCoder.encodeBytes(subroutine, 4330, 4);
+            uniqueSubs.put(subrID, subroutine);
+        }
+        int subRef = (existingSubrRef != -1) ? existingSubrRef : uniqueSubs.size() - 1;
+        data = constructNewRefData(i, data, operands, 1, subRef, opLength);
+        return data;
+    }
+
+    private ByteArrayOutputStream writeMainSection(byte[] decoded, List<PSElement> mainSection,
+            PSElement charStrings) throws IOException {
+        ByteArrayOutputStream main = new ByteArrayOutputStream();
+        PSElement subrs = getElement("/Subrs", mainSection);
+
+        //Find the ID of the three most commonly subroutines defined in Type 1 fonts
+        String rd = findVariable(decoded, mainSection, new String[]
+                {"string currentfile exch readstring pop"}, "RD");
+        String nd = findVariable(decoded, mainSection, new String[]
+                {"def", "noaccess def"}, "noaccess def");
+        String np = findVariable(decoded, mainSection, new String[]
+                {"put", "noaccess put"}, "noaccess put");
+
+        main.write(decoded, 0, subrs.getStartPoint());
+        //Write either the subset or full list of subroutines
+        if (subsetSubroutines) {
+            writeString(eol + String.format("/Subrs %d array", uniqueSubs.size()), main);
+            int count = 0;
+            for (Entry<Integer, byte[]> entry : uniqueSubs.entrySet()) {
+                byte[] newSubrBytes = (eol + String.format("dup %d %d %s ", count++,
+                        entry.getValue().length, rd)).getBytes("ASCII");
+                newSubrBytes = concatArray(newSubrBytes, entry.getValue());
+                newSubrBytes = concatArray(newSubrBytes, String.format(" %s", np).getBytes("ASCII"));
+                main.write(newSubrBytes);
+            }
+            writeString(eol + nd, main);
+        } else {
+            int fullSubrsLength = subrs.getEndPoint() - subrs.getStartPoint();
+            main.write(decoded, subrs.getStartPoint(), fullSubrsLength);
+        }
+        main.write(decoded, subrs.getEndPoint(), charStrings.getStartPoint() - subrs.getEndPoint());
+        //Write the subset charString array
+        writeString(eol + String.format("/CharStrings %d dict dup begin",
+                subsetCharStrings.size()), main);
+        for (Entry<String, byte[]> entry : subsetCharStrings.entrySet()) {
+            writeString(eol + String.format("%s %d %s ", entry.getKey(),
+                    entry.getValue().length, rd),
+                    main);
+            main.write(entry.getValue());
+            writeString(" " + nd, main);
+        }
+        writeString(eol + "end", main);
+        main.write(decoded, charStrings.getEndPoint(), decoded.length - charStrings.getEndPoint());
+
+        return main;
+    }
+
+    private String findVariable(byte[] decoded, List<PSElement> elements, String[] matches,
+            String fallback) throws UnsupportedEncodingException {
+        for (PSElement element : elements) {
+            if (element instanceof PSSubroutine) {
+                byte[] var = new byte[element.getEndPoint() - element.getStartPoint()];
+                System.arraycopy(decoded, element.getStartPoint(), var, 0, element.getEndPoint()
+                        - element.getStartPoint());
+                String found = readVariableContents(new String(var, "ASCII")).trim();
+                for (String match : matches) {
+                    if (match.equals(found)) {
+                        return element.getOperator().substring(1, element.getOperator().length());
+                    }
+                }
+            }
+        }
+        return fallback;
+    }
+
+    String readVariableContents(String variable) {
+        int level = 0;
+        String result = "";
+        int start = 0;
+        int end = 0;
+        boolean reading = false;
+        List<Integer> results = new ArrayList<Integer>();
+        for (int i = 0; i < variable.length(); i++) {
+            char curChar = variable.charAt(i);
+            boolean sectionEnd = false;
+            if (curChar == '{') {
+                level++;
+                sectionEnd = true;
+            } else if (curChar == '}') {
+                level--;
+                sectionEnd = true;
+            } else if (level == 1) {
+                if (!reading) {
+                    reading = true;
+                    start = i;
+                }
+                end = i;
+            }
+            if (sectionEnd && reading) {
+                results.add(start);
+                results.add(end);
+                reading = false;
+            }
+        }
+        for (int i = 0; i < results.size(); i += 2) {
+            result = result.concat(variable.substring(results.get(i), results.get(i + 1) + 1));
+        }
+        return result;
+    }
+
+    private void addToCharNames(String encodingEntry) {
+        int spaceCount = 0;
+        int lastSpaceIndex = 0;
+        int charIndex = 0;
+        String charName = "";
+        //Extract the character name from an encoding entry
+        for (int i = 0; i < encodingEntry.length(); i++) {
+            boolean isSpace = encodingEntry.charAt(i) == ' ';
+            if (isSpace) {
+                spaceCount++;
+                switch (spaceCount - 1) {
+                case 1: charIndex = Integer.parseInt(encodingEntry.substring(lastSpaceIndex + 1,
+                        i)); break;
+                case 2: charName = encodingEntry.substring(lastSpaceIndex + 1, i); break;
+                default: break;
+                }
+            }
+            if (isSpace) {
+                lastSpaceIndex = i;
+            }
+        }
+        sbfont.mapUsedGlyphName(charIndex, charName);
+    }
+
+    private void writeString(String entry, ByteArrayOutputStream boas)
+            throws UnsupportedEncodingException, IOException {
+        byte[] byteEntry = entry.getBytes("ASCII");
+        boas.write(byteEntry);
+    }
+
+    /**
+     * A class used to store the last number operand and also it's size in bytes
+     */
+    public static final class BytesNumber {
+        private int number;
+        private int numBytes;
+        private String name = null;
+
+        public BytesNumber(int number, int numBytes) {
+            this.number = number;
+            this.numBytes = numBytes;
+        }
+
+        public int getNumber() {
+            return this.number;
+        }
+
+        public int getNumBytes() {
+            return this.numBytes;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return this.name;
+        }
+    }
+
+    private byte[] constructNewRefData(int curDataPos, byte[] currentData,
+            List<BytesNumber> operands, int opNum, int curSubsetIndexSize, int operatorLength) {
+        //Create the new array with the modified reference
+        byte[] newData;
+        int operandsLenth = getOperandsLength(operands);
+        int startRef = curDataPos - operandsLenth + getOpPosition(opNum, operands)
+                + (1 - operatorLength);
+        byte[] preBytes = new byte[startRef];
+        System.arraycopy(currentData, 0, preBytes, 0, startRef);
+        byte[] newRefBytes = createNewRef(curSubsetIndexSize, -1);
+        newData = concatArray(preBytes, newRefBytes);
+        byte[] postBytes = new byte[currentData.length - (startRef
+                + operands.get(opNum - 1).getNumBytes())];
+        System.arraycopy(currentData, startRef + operands.get(opNum - 1).getNumBytes(), postBytes, 0,
+                currentData.length - (startRef + operands.get(opNum - 1).getNumBytes()));
+        return concatArray(newData, postBytes);
+    }
+
+    int getOpPosition(int opNum, List<BytesNumber> operands) {
+        int byteCount = 0;
+        for (int i = 0; i < opNum - 1; i++) {
+            byteCount += operands.get(i).getNumBytes();
+        }
+        return byteCount;
+    }
+
+    int getOperandsLength(List<BytesNumber> operands) {
+        int length = 0;
+        for (BytesNumber number : operands) {
+            length += number.getNumBytes();
+        }
+        return length;
+    }
+
+    private byte[] createNewRef(int newRef, int forceLength) {
+        byte[] newRefBytes;
+        if ((forceLength == -1 && newRef <= 107) || forceLength == 1) {
+            newRefBytes = new byte[1];
+            newRefBytes[0] = (byte)(newRef + 139);
+        } else if ((forceLength == -1 && newRef <= 1131) || forceLength == 2) {
+            newRefBytes = new byte[2];
+            if (newRef <= 363) {
+                newRefBytes[0] = (byte)247;
+            } else if (newRef <= 619) {
+                newRefBytes[0] = (byte)248;
+            } else if (newRef <= 875) {
+                newRefBytes[0] = (byte)249;
+            } else {
+                newRefBytes[0] = (byte)250;
+            }
+            newRefBytes[1] = (byte)(newRef - 108);
+        } else {
+            newRefBytes = new byte[5];
+            newRefBytes[0] = (byte)255;
+            newRefBytes[1] = (byte)(newRef >> 24);
+            newRefBytes[2] = (byte)(newRef >> 16);
+            newRefBytes[3] = (byte)(newRef >> 8);
+            newRefBytes[4] = (byte)newRef;
+        }
+        return newRefBytes;
+    }
+
+    /**
+     * Concatenate two byte arrays together
+     * @param a The first array
+     * @param b The second array
+     * @return The concatenated array
+     */
+    byte[] concatArray(byte[] a, byte[] b) {
+        int aLen = a.length;
+        int bLen = b.length;
+        byte[] c = new byte[aLen + bLen];
+        System.arraycopy(a, 0, c, 0, aLen);
+        System.arraycopy(b, 0, c, aLen, bLen);
+        return c;
+    }
+
+    /**
+     * Returns a section of a byte array determined by it's start and
+     * end position.
+     * @param position An array containing both the start and end position
+     * of the section to copy.
+     * @param decoded The array from which to copy a section of data
+     * @return Returns the copy of the data section
+     */
+    byte[] getBinaryEntry(int[] position, byte[] decoded) {
+        int start = position[0];
+        int finish = position[1];
+        byte[] line = new byte[finish - start];
+        System.arraycopy(decoded, start, line, 0, finish - start);
+        return line;
+    }
+
+    private String getEntryPart(String entry, int part) {
+        Scanner s = new Scanner(entry).useDelimiter(" ");
+        for (int i = 1; i < part; i++) {
+            s.next();
+        }
+        return s.next();
+    }
+
+    private PSElement getElement(String elementID, List<PSElement> elements) {
+        for (PSElement element : elements) {
+            if (element.getOperator().equals(elementID)) {
+                return element;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets the list of subset character names
+     * @return Returns the subset character names
+     */
+    public List<String> getCharNames() {
+        return charNames;
+    }
+
+    /**
+     * A class to encode and decode sections of a type 1 font file. See Adobe
+     * Type 1 Font Format Section 7.2 for more details.
+     */
+    public static class BinaryCoder {
+        public static byte[] decodeBytes(byte[] in, int inR, int n) {
+            byte[] out = new byte[in.length - n];
+            int r = inR;
+            int c1 = 52845;
+            int c2 = 22719;
+            for (int i = 0; i < in.length; i++) {
+                int cypher = in[i] & 0xFF;
+                int plain = cypher ^ (r >> 8);
+                if (i >= n) {
+                    out[i - n] = (byte)plain;
+                }
+                r = (cypher + r) * c1 + c2 & 0xFFFF;
+            }
+            return out;
+        }
+
+        public static byte[] encodeBytes(byte[] in, int inR, int n) {
+            byte[] buffer = new byte[in.length + n];
+            for (int i = 0; i < n; i++) {
+                buffer[i] = 0;
+            }
+            int r = inR;
+            int c1 = 52845;
+            int c2 = 22719;
+            System.arraycopy(in, 0, buffer, n, buffer.length - n);
+            byte[] out = new byte[buffer.length];
+            for (int i = 0; i < buffer.length; i++) {
+                int plain = buffer[i] & 0xff;
+                int cipher = plain ^ r >> 8;
+                out[i] = (byte) cipher;
+                r = (cipher + r) * c1 + c2 & 0xffff;
+            }
+            return out;
+        }
+    }
+}

Modified: xmlgraphics/fop/trunk/src/java/org/apache/fop/pdf/PDFFactory.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/pdf/PDFFactory.java?rev=1597112&r1=1597111&r2=1597112&view=diff
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/pdf/PDFFactory.java (original)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/pdf/PDFFactory.java Fri May 23 15:05:19 2014
@@ -23,6 +23,7 @@ package org.apache.fop.pdf;
 import java.awt.Color;
 import java.awt.geom.Point2D;
 import java.awt.geom.Rectangle2D;
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.text.DecimalFormat;
@@ -32,6 +33,8 @@ import java.util.BitSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.io.output.ByteArrayOutputStream;
@@ -61,6 +64,7 @@ import org.apache.fop.fonts.truetype.OTF
 import org.apache.fop.fonts.truetype.TTFSubSetFile;
 import org.apache.fop.fonts.type1.PFBData;
 import org.apache.fop.fonts.type1.PFBParser;
+import org.apache.fop.fonts.type1.Type1SubsetFile;
 
 /**
  * This class provides method to create and register PDF objects.
@@ -1372,11 +1376,50 @@ public class PDFFactory {
                 } else {
                     singleByteFont = (SingleByteFont)metrics;
                 }
-                int firstChar = singleByteFont.getFirstChar();
-                int lastChar = singleByteFont.getLastChar();
-                nonBase14.setWidthMetrics(firstChar,
-                lastChar,
-                new PDFArray(null, metrics.getWidths()));
+
+                int firstChar = 0;
+                int lastChar = 0;
+                boolean defaultChars = false;
+                if (singleByteFont.getEmbeddingMode() == EmbeddingMode.SUBSET) {
+                    Map<Integer, Integer> usedGlyphs = singleByteFont.getUsedGlyphs();
+                    if (fonttype == FontType.TYPE1 && usedGlyphs.size() > 0) {
+                        SortedSet<Integer> keys = new TreeSet<Integer>(usedGlyphs.keySet());
+                        keys.remove(0);
+                        if (keys.size() > 0) {
+                            firstChar = keys.first();
+                            lastChar = keys.last();
+                            int[] newWidths = new int[(lastChar - firstChar) + 1];
+                            for (int i = firstChar; i < lastChar + 1; i++) {
+                                if (usedGlyphs.get(i) != null) {
+                                    if (i - singleByteFont.getFirstChar() < metrics.getWidths().length) {
+                                        newWidths[i - firstChar] = metrics.getWidths()[i
+                                        - singleByteFont.getFirstChar()];
+                                    } else {
+                                        defaultChars = true;
+                                        break;
+                                    }
+                                } else {
+                                    newWidths[i - firstChar] = 0;
+                                }
+                            }
+                            nonBase14.setWidthMetrics(firstChar,
+                                    lastChar,
+                                    new PDFArray(null, newWidths));
+                        }
+                    } else {
+                        defaultChars = true;
+                    }
+                } else {
+                    defaultChars = true;
+                }
+
+                if (defaultChars) {
+                    firstChar = singleByteFont.getFirstChar();
+                    lastChar = singleByteFont.getLastChar();
+                    nonBase14.setWidthMetrics(firstChar,
+                            lastChar,
+                            new PDFArray(null, metrics.getWidths()));
+                }
 
                 //Handle encoding
                 SingleByteEncoding mapping = singleByteFont.getEncoding();
@@ -1493,7 +1536,7 @@ public class PDFFactory {
                                             desc.getStemV(), null);
         } else {
             // Create normal FontDescriptor
-            descriptor = new PDFFontDescriptor(desc.getEmbedFontName(),
+            descriptor = new PDFFontDescriptor(fontPrefix + desc.getEmbedFontName(),
                                          desc.getAscender(),
                                          desc.getDescender(),
                                          desc.getCapHeight(),
@@ -1507,7 +1550,6 @@ public class PDFFactory {
         // Check if the font is embeddable
         if (desc.isEmbeddable()) {
             AbstractPDFStream stream = makeFontFile(desc, fontPrefix);
-
             if (stream != null) {
                 descriptor.setFontFile(desc.getFontType(), stream);
                 getDocument().registerObject(stream);
@@ -1586,10 +1628,19 @@ public class PDFFactory {
                     }
                     embeddedFont = getFontStream(font, fontBytes, isCFF);
                 } else if (desc.getFontType() == FontType.TYPE1) {
-                    PFBParser parser = new PFBParser();
-                    PFBData pfb = parser.parsePFB(in);
-                    embeddedFont = new PDFT1Stream();
-                    ((PDFT1Stream) embeddedFont).setData(pfb);
+                    if (font.getEmbeddingMode() != EmbeddingMode.SUBSET) {
+                        embeddedFont = fullyEmbedType1Font(in);
+                    } else {
+                        assert font instanceof SingleByteFont;
+                        SingleByteFont sbfont = (SingleByteFont)font;
+                        Type1SubsetFile pfbFile = new Type1SubsetFile();
+                        byte[] subsetData = pfbFile.createSubset(in, sbfont, fontPrefix);
+                        InputStream subsetStream = new ByteArrayInputStream(subsetData);
+                        PFBParser parser = new PFBParser();
+                        PFBData pfb = parser.parsePFB(subsetStream);
+                        embeddedFont = new PDFT1Stream();
+                        ((PDFT1Stream) embeddedFont).setData(pfb);
+                    }
                 } else {
                     byte[] file = IOUtils.toByteArray(in);
                     embeddedFont = new PDFTTFStream(file.length);
@@ -1614,6 +1665,14 @@ public class PDFFactory {
         }
     }
 
+    private AbstractPDFStream fullyEmbedType1Font(InputStream in) throws IOException {
+        PFBParser parser = new PFBParser();
+        PFBData pfb = parser.parsePFB(in);
+        AbstractPDFStream embeddedFont = new PDFT1Stream();
+        ((PDFT1Stream) embeddedFont).setData(pfb);
+        return embeddedFont;
+    }
+
     private byte[] getFontSubsetBytes(FontFileReader reader, MultiByteFont mbfont, String header,
             String fontPrefix, FontDescriptor desc, boolean isCFF) throws IOException {
         if (isCFF) {

Modified: xmlgraphics/fop/trunk/src/java/org/apache/fop/render/ps/PSFontUtils.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/render/ps/PSFontUtils.java?rev=1597112&r1=1597111&r2=1597112&view=diff
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/render/ps/PSFontUtils.java (original)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/render/ps/PSFontUtils.java Fri May 23 15:05:19 2014
@@ -19,6 +19,7 @@
 
 package org.apache.fop.render.ps;
 
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.HashMap;
@@ -27,6 +28,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 
+import org.apache.commons.io.IOUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.fontbox.cff.CFFStandardString;
@@ -61,6 +63,7 @@ import org.apache.fop.fonts.truetype.Ope
 import org.apache.fop.fonts.truetype.TTFFile;
 import org.apache.fop.fonts.truetype.TTFOutputStream;
 import org.apache.fop.fonts.truetype.TTFSubSetFile;
+import org.apache.fop.fonts.type1.Type1SubsetFile;
 import org.apache.fop.render.ps.fonts.PSTTFOutputStream;
 import org.apache.fop.util.HexEncoder;
 
@@ -272,13 +275,13 @@ public class PSFontUtils extends org.apa
                 }
                 gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, fontRes);
                 if (fontType == FontType.TYPE1) {
-                    embedType1Font(gen, in);
+                    embedType1Font(gen, (SingleByteFont) tf, in);
                     fontResource = PSFontResource.createFontResource(fontRes);
                 } else if (fontType == FontType.TRUETYPE) {
                     embedTrueTypeFont(gen, (SingleByteFont) tf, in);
                     fontResource = PSFontResource.createFontResource(fontRes);
                 } else {
-                    composeType0Font(gen, (MultiByteFont) tf, in);
+                   composeType0Font(gen, (MultiByteFont) tf, in);
                 }
                 gen.writeDSCComment(DSCConstants.END_RESOURCE);
                 gen.getResourceTracker().registerSuppliedResource(fontRes);
@@ -311,6 +314,71 @@ public class PSFontUtils extends org.apa
         }
     }
 
+    private static void embedType1Font(PSGenerator gen, SingleByteFont font,
+            InputStream fontStream) throws IOException {
+        if (font.getEmbeddingMode() == EmbeddingMode.AUTO) {
+            font.setEmbeddingMode(EmbeddingMode.FULL);
+        }
+        byte[] fullFont = IOUtils.toByteArray(fontStream);
+        fontStream = new ByteArrayInputStream(fullFont);
+        boolean embed = true;
+        if (font.getEmbeddingMode() == EmbeddingMode.SUBSET) {
+            Type1SubsetFile subset = new Type1SubsetFile();
+            byte[] byteSubset = subset.createSubset(fontStream, font, "");
+            fontStream = new ByteArrayInputStream(byteSubset);
+        }
+        embedType1Font(gen, fontStream);
+        if (font.getEmbeddingMode() == EmbeddingMode.SUBSET) {
+            writeEncoding(gen, font);
+        }
+    }
+
+    private static void writeEncoding(PSGenerator gen, SingleByteFont font) throws IOException {
+        String psName = font.getEmbedFontName();
+        gen.writeln("/" + psName + ".0.enc [ ");
+        int lengthCount = 0;
+        int charCount = 1;
+        int encodingCount = 0;
+        StringBuilder line = new StringBuilder();
+        int lastGid = 0;
+        Set<Integer> keySet = font.getUsedGlyphNames().keySet();
+        for (int gid : keySet) {
+            for (int i = lastGid; i < gid - 1; i++) {
+                line.append("/.notdef ");
+                lengthCount++;
+                if (lengthCount == 8) {
+                    gen.writeln(line.toString());
+                    line = new StringBuilder();
+                    lengthCount = 0;
+                }
+            }
+            lastGid = gid;
+            line.append(font.getUsedGlyphNames().get(gid) + " ");
+            lengthCount++;
+            charCount++;
+            if (lengthCount == 8) {
+                gen.writeln(line.toString());
+                line = new StringBuilder();
+                lengthCount = 0;
+            }
+            if (charCount > 256) {
+                encodingCount++;
+                charCount = 1;
+                gen.writeln(line.toString());
+                line = new StringBuilder();
+                lengthCount = 0;
+                gen.writeln("] def");
+                gen.writeln(String.format("/%s.%d %s.%d.enc /%s RE", psName,
+                        encodingCount - 1, psName, encodingCount - 1, psName));
+                gen.writeln("/" + psName + "." + encodingCount + ".enc [ ");
+            }
+        }
+        gen.writeln(line.toString());
+        gen.writeln("] def");
+        gen.writeln(String.format("/%s.%d %s.%d.enc /%s RE", psName, encodingCount,
+                psName, encodingCount, psName));
+    }
+
     private static void embedTrueTypeFont(PSGenerator gen,
             SingleByteFont font, InputStream fontStream) throws IOException {
         /* See Adobe Technical Note #5012, "The Type 42 Font Format Specification" */



---------------------------------------------------------------------
To unsubscribe, e-mail: fop-commits-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: fop-commits-help@xmlgraphics.apache.org


Mime
View raw message