aboutsummaryrefslogtreecommitdiffstats
path: root/src/java/org/apache/fop
diff options
context:
space:
mode:
authorRobert Meyer <rmeyer@apache.org>2014-05-23 15:05:19 +0000
committerRobert Meyer <rmeyer@apache.org>2014-05-23 15:05:19 +0000
commit764eba0b31076d7220082ac167f18cc75c427e27 (patch)
tree3e209ccb9e5e1a376b94e9e5ea29a811b45e3129 /src/java/org/apache/fop
parentc4e5885007f3c61837a12ab330658b131d978247 (diff)
downloadxmlgraphics-fop-764eba0b31076d7220082ac167f18cc75c427e27.tar.gz
xmlgraphics-fop-764eba0b31076d7220082ac167f18cc75c427e27.zip
FOP-2354: Subset support for Type 1 fonts
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1597112 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java/org/apache/fop')
-rw-r--r--src/java/org/apache/fop/fonts/AbstractCodePointMapping.java4
-rw-r--r--src/java/org/apache/fop/fonts/FontLoader.java7
-rw-r--r--src/java/org/apache/fop/fonts/FontReader.java4
-rw-r--r--src/java/org/apache/fop/fonts/SingleByteFont.java97
-rw-r--r--src/java/org/apache/fop/fonts/truetype/OFFontLoader.java2
-rw-r--r--src/java/org/apache/fop/fonts/type1/AdobeStandardEncoding.java11
-rw-r--r--src/java/org/apache/fop/fonts/type1/PFBData.java24
-rw-r--r--src/java/org/apache/fop/fonts/type1/PostscriptParser.java655
-rw-r--r--src/java/org/apache/fop/fonts/type1/Type1FontLoader.java10
-rw-r--r--src/java/org/apache/fop/fonts/type1/Type1SubsetFile.java772
-rw-r--r--src/java/org/apache/fop/pdf/PDFFactory.java81
-rw-r--r--src/java/org/apache/fop/render/ps/PSFontUtils.java72
12 files changed, 1705 insertions, 34 deletions
diff --git a/src/java/org/apache/fop/fonts/AbstractCodePointMapping.java b/src/java/org/apache/fop/fonts/AbstractCodePointMapping.java
index 903fe56ec..20cef2d0b 100644
--- a/src/java/org/apache/fop/fonts/AbstractCodePointMapping.java
+++ b/src/java/org/apache/fop/fonts/AbstractCodePointMapping.java
@@ -179,6 +179,10 @@ public class AbstractCodePointMapping implements SingleByteEncoding {
return -1;
}
+ public String getNameFromCodePoint(int idx) {
+ return getCharNameMap()[idx];
+ }
+
/** {@inheritDoc} */
public String[] getCharNameMap() {
if (this.charNameMap != null) {
diff --git a/src/java/org/apache/fop/fonts/FontLoader.java b/src/java/org/apache/fop/fonts/FontLoader.java
index 09e38260e..a04ee6d5f 100644
--- a/src/java/org/apache/fop/fonts/FontLoader.java
+++ b/src/java/org/apache/fop/fonts/FontLoader.java
@@ -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);
diff --git a/src/java/org/apache/fop/fonts/FontReader.java b/src/java/org/apache/fop/fonts/FontReader.java
index 0448c317e..bd64a7595 100644
--- a/src/java/org/apache/fop/fonts/FontReader.java
+++ b/src/java/org/apache/fop/fonts/FontReader.java
@@ -162,13 +162,13 @@ public class FontReader extends DefaultHandler {
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;
diff --git a/src/java/org/apache/fop/fonts/SingleByteFont.java b/src/java/org/apache/fop/fonts/SingleByteFont.java
index 2a6b04761..7b17c0b5d 100644
--- a/src/java/org/apache/fop/fonts/SingleByteFont.java
+++ b/src/java/org/apache/fop/fonts/SingleByteFont.java
@@ -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.Glyphs;
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 CustomFont {
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 CustomFont {
/** {@inheritDoc} */
public boolean isSubsetEmbedded() {
- return false;
+ return getEmbeddingMode() != EmbeddingMode.FULL;
}
/** {@inheritDoc} */
@@ -182,22 +201,53 @@ public class SingleByteFont extends CustomFont {
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 CustomFont {
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();
+ }
+ }
}
diff --git a/src/java/org/apache/fop/fonts/truetype/OFFontLoader.java b/src/java/org/apache/fop/fonts/truetype/OFFontLoader.java
index 7168389ff..4216cf290 100644
--- a/src/java/org/apache/fop/fonts/truetype/OFFontLoader.java
+++ b/src/java/org/apache/fop/fonts/truetype/OFFontLoader.java
@@ -137,7 +137,7 @@ public class OFFontLoader extends FontLoader {
returnFont = multiFont;
multiFont.setTTCName(ttcFontName);
} else {
- singleFont = new SingleByteFont(resourceResolver);
+ singleFont = new SingleByteFont(resourceResolver, embeddingMode);
returnFont = singleFont;
}
diff --git a/src/java/org/apache/fop/fonts/type1/AdobeStandardEncoding.java b/src/java/org/apache/fop/fonts/type1/AdobeStandardEncoding.java
index 514d03185..d3d5a969d 100644
--- a/src/java/org/apache/fop/fonts/type1/AdobeStandardEncoding.java
+++ b/src/java/org/apache/fop/fonts/type1/AdobeStandardEncoding.java
@@ -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 "";
+ }
}
diff --git a/src/java/org/apache/fop/fonts/type1/PFBData.java b/src/java/org/apache/fop/fonts/type1/PFBData.java
index e75c60117..05f0ec78f 100644
--- a/src/java/org/apache/fop/fonts/type1/PFBData.java
+++ b/src/java/org/apache/fop/fonts/type1/PFBData.java
@@ -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
*/
diff --git a/src/java/org/apache/fop/fonts/type1/PostscriptParser.java b/src/java/org/apache/fop/fonts/type1/PostscriptParser.java
new file mode 100644
index 000000000..05c3c6453
--- /dev/null
+++ b/src/java/org/apache/fop/fonts/type1/PostscriptParser.java
@@ -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;
+ }
+
+ }
+}
diff --git a/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java b/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java
index 260ef209f..802c3c033 100644
--- a/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java
+++ b/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java
@@ -34,6 +34,7 @@ import org.apache.commons.logging.LogFactory;
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 FontLoader {
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 FontLoader {
* @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 FontLoader {
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);
diff --git a/src/java/org/apache/fop/fonts/type1/Type1SubsetFile.java b/src/java/org/apache/fop/fonts/type1/Type1SubsetFile.java
new file mode 100644
index 000000000..6e6a7f26b
--- /dev/null
+++ b/src/java/org/apache/fop/fonts/type1/Type1SubsetFile.java
@@ -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;
+ }
+ }
+}
diff --git a/src/java/org/apache/fop/pdf/PDFFactory.java b/src/java/org/apache/fop/pdf/PDFFactory.java
index 070630274..eb4f61077 100644
--- a/src/java/org/apache/fop/pdf/PDFFactory.java
+++ b/src/java/org/apache/fop/pdf/PDFFactory.java
@@ -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.OTFSubSetFile;
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) {
diff --git a/src/java/org/apache/fop/render/ps/PSFontUtils.java b/src/java/org/apache/fop/render/ps/PSFontUtils.java
index 06191f84d..5bb723eea 100644
--- a/src/java/org/apache/fop/render/ps/PSFontUtils.java
+++ b/src/java/org/apache/fop/render/ps/PSFontUtils.java
@@ -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.OpenFont.PostScriptVersion;
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.apache.xmlgraphics.ps.PSFontUtils {
}
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.apache.xmlgraphics.ps.PSFontUtils {
}
}
+ 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" */