git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1597112 13f79535-47bb-0310-9956-ffa450edef68tags/fop-2_0
@@ -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) { |
@@ -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); |
@@ -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; |
@@ -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(); | |||
} | |||
} | |||
} | |||
@@ -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; | |||
} | |||
@@ -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 ""; | |||
} | |||
} |
@@ -84,6 +84,14 @@ public class PFBData { | |||
this.headerSegment = headerSeg; | |||
} | |||
/** | |||
* 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 | |||
@@ -92,6 +100,14 @@ public class PFBData { | |||
this.encryptedSegment = encryptedSeg; | |||
} | |||
/** | |||
* 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 | |||
@@ -100,6 +116,14 @@ public class PFBData { | |||
this.trailerSegment = trailerSeg; | |||
} | |||
/** | |||
* 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 |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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); |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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) { |
@@ -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" */ |
@@ -0,0 +1,93 @@ | |||
/* | |||
* 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.util.List; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertTrue; | |||
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; | |||
import org.apache.fop.fonts.type1.PostscriptParser.PSVariableArray; | |||
public class PostscriptParserTestCase { | |||
private PostscriptParser parser; | |||
private String eol = new String(new byte[] {13}); | |||
private String postscriptElements = | |||
"/myVariable 100 def" + eol | |||
+ "/-| {def} executeonly def" + eol | |||
+ "/myFixedArray 6 array" + eol | |||
+ "0 1 5 {1 index exch /.notdef put } for" + eol | |||
+ "dup 1 /a put" + eol | |||
+ "dup 2 /b put" + eol | |||
+ "dup 3 /c put" + eol | |||
+ "dup 4 /d put" + eol | |||
+ "readonly def" + eol | |||
+ "/myVariableArray [ { this } { is } { a } { test } ] no access def" + eol | |||
+ "/refVarSubr myValue -|"; | |||
@Before | |||
public void setUp() { | |||
parser = new PostscriptParser(); | |||
} | |||
/** | |||
* Tests parsing an example Postscript document and verifying what | |||
* has been read. | |||
* @throws IOException | |||
*/ | |||
@Test | |||
public void testPostscriptParsing() throws IOException { | |||
List<PSElement> elements = parser.parse(postscriptElements.getBytes()); | |||
assertEquals(elements.size(), 5); | |||
assertTrue(elements.get(0) instanceof PSVariable); | |||
assertTrue(elements.get(2) instanceof PSFixedArray); | |||
assertTrue(elements.get(3) instanceof PSVariableArray); | |||
PSFixedArray fixedArray = (PSFixedArray)elements.get(2); | |||
assertEquals(fixedArray.getEntries().size(), 4); | |||
assertEquals(fixedArray.getEntries().get(2), "dup 2 /b put "); | |||
PSVariableArray variableArray = (PSVariableArray)elements.get(3); | |||
assertEquals(variableArray.getEntries().size(), 4); | |||
/* Currently only variable arrays containing subroutines are supported, though | |||
* this can be modified to support single values and also strip out unnecessary | |||
* characters like the { } below. */ | |||
assertEquals(variableArray.getEntries().get(0).trim(), "{ this }"); | |||
} | |||
/** | |||
* Tests that the correct element is returned given the operator and element ID provided | |||
*/ | |||
@Test | |||
public void testCreateElement() { | |||
assertTrue(parser.createElement("/custDictionary", "dict", -1) instanceof PSDictionary); | |||
assertEquals(parser.createElement("/Private", "dict", -1), null); | |||
assertTrue(parser.createElement("/aFixedArray", "array", -1) instanceof PSFixedArray); | |||
assertTrue(parser.createElement("/aVariableArray", "[", -1) instanceof PSVariableArray); | |||
assertTrue(parser.createElement("/aSubroutine", "{", -1) instanceof PSSubroutine); | |||
} | |||
} |
@@ -0,0 +1,348 @@ | |||
/* | |||
* 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.FileInputStream; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.net.URI; | |||
import java.util.ArrayList; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import org.junit.Test; | |||
import static org.junit.Assert.assertArrayEquals; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
import org.apache.xmlgraphics.fonts.Glyphs; | |||
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.Type1SubsetFile.BinaryCoder; | |||
import org.apache.fop.fonts.type1.Type1SubsetFile.BytesNumber; | |||
public class Type1SubsetFileTestCase { | |||
private List<byte[]> decodedSections; | |||
private static final String TEST_FONT_A = "./test/resources/fonts/type1/c0419bt_.pfb"; | |||
@Test | |||
public void test() throws IOException { | |||
InputStream in = new FileInputStream(TEST_FONT_A); | |||
compareCharStringData(in, TEST_FONT_A, createFontASubset(in, TEST_FONT_A)); | |||
} | |||
@Test | |||
public void testStitchFont() throws IOException { | |||
ByteArrayOutputStream baosHeader = new ByteArrayOutputStream(); | |||
ByteArrayOutputStream baosMain = new ByteArrayOutputStream(); | |||
ByteArrayOutputStream baosTrailer = new ByteArrayOutputStream(); | |||
//Header | |||
for (int i = 0; i < 10; i++) { | |||
baosHeader.write(123); | |||
baosMain.write(123); | |||
} | |||
for (int i = 0; i < 10; i++) { | |||
baosTrailer.write(0); | |||
} | |||
Type1SubsetFile subset = new Type1SubsetFile(); | |||
byte[] result = subset.stitchFont(baosHeader, baosMain, baosTrailer); | |||
ByteArrayInputStream bais = new ByteArrayInputStream(result); | |||
assertEquals(result.length, 50); | |||
PFBParser parser = new PFBParser(); | |||
parser.parsePFB(bais); | |||
} | |||
@Test | |||
public void testUpdateSectionSize() throws IOException { | |||
Type1SubsetFile subset = new Type1SubsetFile(); | |||
ByteArrayOutputStream baos = subset.updateSectionSize(456); | |||
byte[] lowOrderSize = baos.toByteArray(); | |||
assertEquals(lowOrderSize[0], -56); | |||
assertEquals(lowOrderSize[1], 1); | |||
} | |||
@Test | |||
public void testVariableContents() { | |||
Type1SubsetFile subset = new Type1SubsetFile(); | |||
String result = subset.readVariableContents("/myvariable {some variable contents}"); | |||
assertEquals(result, "some variable contents"); | |||
result = subset.readVariableContents("/myvariable {hello {some more text {test} and some more}test}"); | |||
//Should only reads one level deep | |||
assertEquals(result, "hello test"); | |||
} | |||
@Test | |||
public void getOpPositionAndLength() { | |||
Type1SubsetFile subset = new Type1SubsetFile(); | |||
ArrayList<BytesNumber> ops = new ArrayList<BytesNumber>(); | |||
ops.add(new BytesNumber(10, 1)); | |||
ops.add(new BytesNumber(255, 2)); | |||
ops.add(new BytesNumber(100, 1)); | |||
ops.add(new BytesNumber(97, 1)); | |||
ops.add(new BytesNumber(856, 2)); | |||
assertEquals(subset.getOpPosition(4, ops), 4); | |||
assertEquals(subset.getOperandsLength(ops), 7); | |||
} | |||
@Test | |||
public void testConcatArrays() { | |||
byte[] arrayA = {(byte)1, (byte)2, (byte)3, (byte)4, (byte)5}; | |||
byte[] arrayB = {(byte)6, (byte)7, (byte)8, (byte)9, (byte)10}; | |||
Type1SubsetFile subset = new Type1SubsetFile(); | |||
byte[] concatArray = subset.concatArray(arrayA, arrayB); | |||
assertEquals(concatArray.length, 10); | |||
assertEquals(concatArray[5], 6); | |||
assertEquals(concatArray[3], 4); | |||
} | |||
@Test | |||
public void testGetBinaryEntry() { | |||
byte[] decoded = {(byte)34, (byte)23, (byte)78, (byte)55, (byte)12, | |||
(byte)2, (byte)65, (byte)49, (byte)90, (byte)10}; | |||
int[] section = {3, 7}; | |||
Type1SubsetFile subset = new Type1SubsetFile(); | |||
byte[] segment = subset.getBinaryEntry(section, decoded); | |||
assertEquals(segment.length, 4); | |||
assertEquals(segment[0], 55); | |||
assertEquals(segment[3], 65); | |||
} | |||
private void compareCharStringData(InputStream in, String font, byte[] subsetFont) | |||
throws IOException { | |||
decodedSections = new ArrayList<byte[]>(); | |||
//Reinitialise the input stream as reset only supports 1000 bytes. | |||
in = new FileInputStream(font); | |||
List<PSElement> origElements = parseElements(in); | |||
List<PSElement> subsetElements = parseElements(new ByteArrayInputStream(subsetFont)); | |||
PSFixedArray origSubs = (PSFixedArray)findElement(origElements, "/Subrs"); | |||
PSFixedArray subsetSubs = (PSFixedArray)findElement(subsetElements, "/Subrs"); | |||
PSDictionary origCharStrings = (PSDictionary)findElement(origElements, "/CharStrings"); | |||
PSDictionary subsetCharStrings = (PSDictionary)findElement(subsetElements, "/CharStrings"); | |||
for (String element : subsetCharStrings.getEntries().keySet()) { | |||
if (element.equals("/.notdef")) { | |||
continue; | |||
} | |||
int[] origBinaryCharLocation = origCharStrings.getBinaryEntries().get(element); | |||
int[] subsetBinaryCharLocation = subsetCharStrings.getBinaryEntries().get(element); | |||
int origLength = origBinaryCharLocation[1] - origBinaryCharLocation[0]; | |||
int subsetLength = subsetBinaryCharLocation[1] - subsetBinaryCharLocation[0]; | |||
byte[] origCharData = new byte[origLength]; | |||
byte[] subsetCharData = new byte[subsetLength]; | |||
System.arraycopy(decodedSections.get(0), origBinaryCharLocation[0], origCharData, 0, origLength); | |||
System.arraycopy(decodedSections.get(1), subsetBinaryCharLocation[0], subsetCharData, 0, subsetLength); | |||
origCharData = BinaryCoder.decodeBytes(origCharData, 4330, 4); | |||
subsetCharData = BinaryCoder.decodeBytes(subsetCharData, 4330, 4); | |||
byte[] origFullCharData = readFullCharString(decodedSections.get(0), origCharData, origSubs); | |||
byte[] subsetFullCharData = readFullCharString(decodedSections.get(1), subsetCharData, subsetSubs); | |||
assertArrayEquals(origFullCharData, subsetFullCharData); | |||
} | |||
} | |||
private byte[] createFontASubset(InputStream in, String font) throws IOException { | |||
SingleByteFont sbfont = mock(SingleByteFont.class); | |||
//Glyph index & selector | |||
Map<Integer, Integer> glyphs = new HashMap<Integer, Integer>(); | |||
//Selector & unicode | |||
Map<Integer, Character> usedCharsIndex = new HashMap<Integer, Character>(); | |||
Map<Integer, String> usedCharNames = new HashMap<Integer, String>(); | |||
int count = 0; | |||
for (int i = 32; i < 127; i++) { | |||
glyphs.put(i, count++); | |||
when(sbfont.getUnicodeFromSelector(count)).thenReturn((char)i); | |||
usedCharNames.put(i, String.format("/%s", Glyphs.charToGlyphName((char)i))); | |||
when(sbfont.getGlyphName(i)).thenReturn(AdobeStandardEncoding.getCharFromCodePoint(i)); | |||
} | |||
for (int i = 161; i < 204; i++) { | |||
glyphs.put(i, count++); | |||
usedCharsIndex.put(count, (char)i); | |||
when(sbfont.getUnicodeFromSelector(count)).thenReturn((char)i); | |||
usedCharNames.put(i, String.format("/%s", Glyphs.charToGlyphName((char)i))); | |||
when(sbfont.getGlyphName(i)).thenReturn(AdobeStandardEncoding.getCharFromCodePoint(i)); | |||
} | |||
int[] randomGlyphs = {205, 206, 207, 208, 225, 227, 232, 233, 234, 235, 241, 245, | |||
248, 249, 250, 251 | |||
}; | |||
for (int i = 0; i < randomGlyphs.length; i++) { | |||
glyphs.put(randomGlyphs[i], count++); | |||
usedCharsIndex.put(count, (char)randomGlyphs[i]); | |||
when(sbfont.getUnicodeFromSelector(count)).thenReturn((char)randomGlyphs[i]); | |||
usedCharNames.put(i, String.format("/%s", Glyphs.charToGlyphName((char)i))); | |||
when(sbfont.getGlyphName(i)).thenReturn(AdobeStandardEncoding.getCharFromCodePoint(i)); | |||
} | |||
for (int i = 256; i < 335; i++) { | |||
glyphs.put(i, count++); | |||
usedCharsIndex.put(count, (char)i); | |||
when(sbfont.getUnicodeFromSelector(count)).thenReturn((char)i); | |||
usedCharNames.put(i, String.format("/%s", Glyphs.charToGlyphName((char)i))); | |||
when(sbfont.getGlyphName(i)).thenReturn(AdobeStandardEncoding.getCharFromCodePoint(i)); | |||
} | |||
when(sbfont.getUsedGlyphNames()).thenReturn(usedCharNames); | |||
when(sbfont.getUsedGlyphs()).thenReturn(glyphs); | |||
when(sbfont.getEmbedFileURI()).thenReturn(URI.create(font)); | |||
Type1SubsetFile subset = new Type1SubsetFile(); | |||
return subset.createSubset(in, sbfont, "AAAAAA"); | |||
} | |||
private List<PSElement> parseElements(InputStream in) | |||
throws IOException { | |||
PFBParser pfbParser = new PFBParser(); | |||
PFBData origData = pfbParser.parsePFB(in); | |||
PostscriptParser parser = new PostscriptParser(); | |||
byte[] decoded = BinaryCoder.decodeBytes(origData.getEncryptedSegment(), 55665, 4); | |||
decodedSections.add(decoded); | |||
return parser.parse(decoded); | |||
} | |||
private PSElement findElement(List<PSElement> elements, String operator) { | |||
for (PSElement element : elements) { | |||
if (element.getOperator().equals(operator)) { | |||
return element; | |||
} | |||
} | |||
return null; | |||
} | |||
private byte[] readFullCharString(byte[] decoded, byte[] data, PSFixedArray subroutines) { | |||
List<BytesNumber> operands = new ArrayList<BytesNumber>(); | |||
List<BytesNumber> fullList = new ArrayList<BytesNumber>(); | |||
for (int i = 0; i < data.length; i++) { | |||
int cur = data[i] & 0xFF; | |||
if (cur >= 0 && cur <= 31) { | |||
//Found subroutine. Read subroutine, recursively scan and update references | |||
if (cur == 10) { | |||
if (operands.size() == 0) { | |||
continue; | |||
} | |||
int[] subrData = subroutines.getBinaryEntryByIndex(operands.get(0).getNumber()); | |||
byte[] subroutine = getBinaryEntry(subrData, decoded); | |||
subroutine = BinaryCoder.decodeBytes(subroutine, 4330, 4); | |||
subroutine = readFullCharString(decoded, subroutine, subroutines); | |||
data = replaceReference(data, subroutine, i - 1 + operands.get(0).getNumBytes(), i); | |||
} else { | |||
int next = -1; | |||
if (cur == 12) { | |||
next = data[++i] & 0xFF; | |||
} | |||
BytesNumber operand = new BytesNumber(cur, i); | |||
operand.setName(getName(cur, next)); | |||
fullList.add(operand); | |||
} | |||
operands.clear(); | |||
} | |||
if (cur >= 32 && cur <= 246) { | |||
operands.add(new BytesNumber(cur - 139, 1)); | |||
fullList.add(operands.get(operands.size() - 1)); | |||
} else if (cur >= 247 && cur <= 250) { | |||
operands.add(new BytesNumber((cur - 247) * 256 + (data[i + 1] & 0xFF) + 108, 2)); | |||
fullList.add(operands.get(operands.size() - 1)); | |||
i++; | |||
} else if (cur >= 251 && cur <= 254) { | |||
operands.add(new BytesNumber(-(cur - 251) * 256 - (data[i + 1] & 0xFF) - 108, 2)); | |||
fullList.add(operands.get(operands.size() - 1)); | |||
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)); | |||
fullList.add(operands.get(operands.size() - 1)); | |||
i += 4; | |||
} | |||
} | |||
return data; | |||
} | |||
private String getName(int operator, int next) { | |||
switch (operator) { | |||
case 14: return "endchar"; | |||
case 13: return "hsbw"; | |||
case 12: | |||
switch (next) { | |||
case 0: return "dotsection"; | |||
case 1: return "vstem3"; | |||
case 2: return "hstem3"; | |||
case 6: return "seac"; | |||
case 7: return "sbw"; | |||
case 16: return "callothersubr"; | |||
case 17: return "pop"; | |||
case 33: return "setcurrentpoint"; | |||
default: return "unknown"; | |||
} | |||
case 9: return "closepath"; | |||
case 6: return "hlineto"; | |||
case 22: return "hmoveto"; | |||
case 31: return "hvcurveto"; | |||
case 5: return "rlineto"; | |||
case 21: return "rmoveto"; | |||
case 8: return "rrcurveto"; | |||
case 30: return "vhcurveto"; | |||
case 7: return "vlineto"; | |||
case 4: return "vmoveto"; | |||
case 1: return "hstem"; | |||
case 3: return "vstem"; | |||
case 10: return "callsubr"; | |||
case 11: return "return"; | |||
default: return "unknown"; | |||
} | |||
} | |||
private byte[] replaceReference(byte[] data, byte[] subroutine, int startRef, int endRef) { | |||
byte[] preBytes = new byte[startRef - 1]; | |||
System.arraycopy(data, 0, preBytes, 0, startRef - 1); | |||
byte[] postBytes = new byte[data.length - endRef - 1]; | |||
System.arraycopy(data, endRef + 1, postBytes, 0, data.length - endRef - 1); | |||
data = concatArray(preBytes, subroutine, 1); | |||
data = concatArray(data, postBytes, 0); | |||
return data; | |||
} | |||
private 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 byte[] concatArray(byte[] a, byte[] b, int subtract) { | |||
int aLen = a.length; | |||
int bLen = b.length - subtract; | |||
byte[] c = new byte[aLen + bLen]; | |||
System.arraycopy(a, 0, c, 0, aLen); | |||
System.arraycopy(b, 0, c, aLen, bLen); | |||
return c; | |||
} | |||
} |
@@ -0,0 +1,264 @@ | |||
StartFontMetrics 2.0 | |||
Comment Bitstream AFM Data | |||
Comment Copyright 1987-1990 as an unpublished work by Bitstream Inc., Cambridge, MA. | |||
Comment All rights reserved | |||
Comment Confidential and proprietary to Bitstream Inc. | |||
Comment Bitstream is a registered trademark of Bitstream Inc. | |||
Comment bitsClassification Fixed Pitch 810 | |||
Comment bitsFontID 0419 | |||
Comment bitsManufacturingDate Mon Nov 5 16:16:55 1990 | |||
Comment bitsLayoutName clayout.adobe.text228.new | |||
Comment UniqueID 15530419 | |||
FontName Courier10PitchBT-Roman | |||
FullName Courier 10 Pitch | |||
FamilyName Courier 10 Pitch | |||
Weight Normal | |||
ItalicAngle 0.00 | |||
IsFixedPitch true | |||
FontBBox -44 -299 664 858 | |||
UnderlinePosition -97 | |||
UnderlineThickness 82 | |||
Version 1.0 [UFO] | |||
Notice Copyright 1987-1990 as an unpublished work by Bitstream Inc. All rights reserved. Confidential. | |||
EncodingScheme AdobeStandardEncoding | |||
CapHeight 579 | |||
XHeight 452 | |||
Ascender 639 | |||
Descender -195 | |||
StartCharMetrics 228 | |||
C 32 ; WX 602 ; N space ; B 0 0 0 0 ; | |||
C 33 ; WX 602 ; N exclam ; B 210 -11 392 613 ; | |||
C 34 ; WX 602 ; N quotedbl ; B 154 337 448 579 ; | |||
C 35 ; WX 602 ; N numbersign ; B 105 -58 497 674 ; | |||
C 36 ; WX 602 ; N dollar ; B 98 -122 498 666 ; | |||
C 37 ; WX 602 ; N percent ; B 101 -2 501 618 ; | |||
C 38 ; WX 602 ; N ampersand ; B 88 -11 501 547 ; | |||
C 39 ; WX 602 ; N quoteright ; B 215 317 396 579 ; | |||
C 40 ; WX 602 ; N parenleft ; B 201 -196 401 597 ; | |||
C 41 ; WX 602 ; N parenright ; B 201 -196 400 597 ; | |||
C 42 ; WX 602 ; N asterisk ; B 103 201 499 579 ; | |||
C 43 ; WX 602 ; N plus ; B 48 49 553 566 ; | |||
C 44 ; WX 602 ; N comma ; B 178 -157 399 163 ; | |||
C 45 ; WX 602 ; N hyphen ; B 76 178 526 273 ; | |||
C 46 ; WX 602 ; N period ; B 204 -7 397 167 ; | |||
C 47 ; WX 602 ; N slash ; B 114 -55 503 699 ; | |||
C 48 ; WX 602 ; N zero ; B 101 -17 501 632 ; | |||
C 49 ; WX 602 ; N one ; B 126 0 499 630 ; | |||
C 50 ; WX 602 ; N two ; B 80 0 487 633 ; | |||
C 51 ; WX 602 ; N three ; B 90 -16 497 633 ; | |||
C 52 ; WX 602 ; N four ; B 88 0 493 631 ; | |||
C 53 ; WX 602 ; N five ; B 84 -16 503 616 ; | |||
C 54 ; WX 602 ; N six ; B 100 -17 499 633 ; | |||
C 55 ; WX 602 ; N seven ; B 101 -10 491 616 ; | |||
C 56 ; WX 602 ; N eight ; B 102 -17 500 633 ; | |||
C 57 ; WX 602 ; N nine ; B 98 -17 497 633 ; | |||
C 58 ; WX 602 ; N colon ; B 204 -7 397 438 ; | |||
C 59 ; WX 602 ; N semicolon ; B 178 -157 395 438 ; | |||
C 60 ; WX 602 ; N less ; B 34 68 543 547 ; | |||
C 61 ; WX 602 ; N equal ; B 51 189 551 426 ; | |||
C 62 ; WX 602 ; N greater ; B 59 68 569 547 ; | |||
C 63 ; WX 602 ; N question ; B 122 -8 488 613 ; | |||
C 64 ; WX 602 ; N at ; B 109 -53 476 668 ; | |||
C 65 ; WX 602 ; N A ; B 0 0 607 579 ; | |||
C 66 ; WX 602 ; N B ; B 42 0 558 579 ; | |||
C 67 ; WX 602 ; N C ; B 39 -14 541 595 ; | |||
C 68 ; WX 602 ; N D ; B 49 -2 558 579 ; | |||
C 69 ; WX 602 ; N E ; B 33 0 530 579 ; | |||
C 70 ; WX 602 ; N F ; B 45 0 547 579 ; | |||
C 71 ; WX 602 ; N G ; B 34 -16 574 596 ; | |||
C 72 ; WX 602 ; N H ; B 49 0 553 579 ; | |||
C 73 ; WX 602 ; N I ; B 94 0 508 579 ; | |||
C 74 ; WX 602 ; N J ; B 54 -14 574 579 ; | |||
C 75 ; WX 602 ; N K ; B 35 0 573 579 ; | |||
C 76 ; WX 602 ; N L ; B 28 0 555 579 ; | |||
C 77 ; WX 602 ; N M ; B 7 0 595 579 ; | |||
C 78 ; WX 602 ; N N ; B 16 -8 569 579 ; | |||
C 79 ; WX 602 ; N O ; B 36 -16 566 595 ; | |||
C 80 ; WX 602 ; N P ; B 43 0 547 579 ; | |||
C 81 ; WX 602 ; N Q ; B 36 -128 566 595 ; | |||
C 82 ; WX 602 ; N R ; B 29 0 588 579 ; | |||
C 83 ; WX 602 ; N S ; B 70 -23 527 596 ; | |||
C 84 ; WX 602 ; N T ; B 45 0 557 579 ; | |||
C 85 ; WX 602 ; N U ; B 31 -16 570 579 ; | |||
C 86 ; WX 602 ; N V ; B 8 -7 594 579 ; | |||
C 87 ; WX 602 ; N W ; B 2 0 597 579 ; | |||
C 88 ; WX 602 ; N X ; B 42 0 560 579 ; | |||
C 89 ; WX 602 ; N Y ; B 38 0 562 579 ; | |||
C 90 ; WX 602 ; N Z ; B 89 0 492 579 ; | |||
C 91 ; WX 602 ; N bracketleft ; B 202 -181 400 579 ; | |||
C 92 ; WX 602 ; N backslash ; B 114 -55 503 698 ; | |||
C 93 ; WX 602 ; N bracketright ; B 202 -181 400 579 ; | |||
C 94 ; WX 602 ; N asciicircum ; B 126 448 476 676 ; | |||
C 95 ; WX 602 ; N underscore ; B -22 -299 626 -217 ; | |||
C 96 ; WX 602 ; N quoteleft ; B 210 334 391 596 ; | |||
C 97 ; WX 602 ; N a ; B 66 -12 568 464 ; | |||
C 98 ; WX 602 ; N b ; B 33 -10 566 639 ; | |||
C 99 ; WX 602 ; N c ; B 48 -12 523 463 ; | |||
C 100 ; WX 602 ; N d ; B 44 -12 577 639 ; | |||
C 101 ; WX 602 ; N e ; B 51 -12 546 465 ; | |||
C 102 ; WX 602 ; N f ; B 93 0 530 640 ; | |||
C 103 ; WX 602 ; N g ; B 66 -196 567 452 ; | |||
C 104 ; WX 602 ; N h ; B 49 0 561 639 ; | |||
C 105 ; WX 602 ; N i ; B 82 0 540 672 ; | |||
C 106 ; WX 602 ; N j ; B 115 -193 452 672 ; | |||
C 107 ; WX 602 ; N k ; B 51 0 575 639 ; | |||
C 108 ; WX 602 ; N l ; B 91 0 531 639 ; | |||
C 109 ; WX 602 ; N m ; B 0 0 614 464 ; | |||
C 110 ; WX 602 ; N n ; B 51 0 563 464 ; | |||
C 111 ; WX 602 ; N o ; B 45 -12 557 463 ; | |||
C 112 ; WX 602 ; N p ; B 33 -195 566 452 ; | |||
C 113 ; WX 602 ; N q ; B 39 -195 572 452 ; | |||
C 114 ; WX 602 ; N r ; B 53 0 556 464 ; | |||
C 115 ; WX 602 ; N s ; B 87 -12 512 464 ; | |||
C 116 ; WX 602 ; N t ; B 43 -10 550 591 ; | |||
C 117 ; WX 602 ; N u ; B 35 -10 558 452 ; | |||
C 118 ; WX 602 ; N v ; B 21 -18 580 452 ; | |||
C 119 ; WX 602 ; N w ; B -10 -18 603 452 ; | |||
C 120 ; WX 602 ; N x ; B 40 0 568 452 ; | |||
C 121 ; WX 602 ; N y ; B 28 -195 570 452 ; | |||
C 122 ; WX 602 ; N z ; B 98 0 508 452 ; | |||
C 123 ; WX 602 ; N braceleft ; B 143 -183 468 581 ; | |||
C 124 ; WX 602 ; N bar ; B 259 -261 342 789 ; | |||
C 125 ; WX 602 ; N braceright ; B 142 -183 467 581 ; | |||
C 126 ; WX 602 ; N asciitilde ; B 64 249 538 366 ; | |||
C 161 ; WX 602 ; N exclamdown ; B 210 -29 392 595 ; | |||
C 162 ; WX 602 ; N cent ; B 93 -33 493 646 ; | |||
C 163 ; WX 602 ; N sterling ; B 127 -4 501 625 ; | |||
C 164 ; WX 602 ; N fraction ; B 114 -55 503 699 ; | |||
C 165 ; WX 602 ; N yen ; B 38 0 562 579 ; | |||
C 166 ; WX 602 ; N florin ; B 17 -139 541 632 ; | |||
C 167 ; WX 602 ; N section ; B 75 -105 526 579 ; | |||
C 168 ; WX 602 ; N currency ; B 60 188 548 675 ; | |||
C 169 ; WX 602 ; N quotesingle ; B 243 330 358 579 ; | |||
C 170 ; WX 602 ; N quotedblleft ; B 107 341 475 596 ; | |||
C 171 ; WX 602 ; N guillemotleft ; B 149 44 453 407 ; | |||
C 172 ; WX 602 ; N guilsinglleft ; B 232 44 370 407 ; | |||
C 173 ; WX 602 ; N guilsinglright ; B 231 44 370 407 ; | |||
C 174 ; WX 602 ; N fi ; B -1 0 602 672 ; | |||
C 175 ; WX 602 ; N fl ; B -1 0 602 642 ; | |||
C 177 ; WX 602 ; N endash ; B -22 184 626 266 ; | |||
C 178 ; WX 602 ; N dagger ; B 121 -15 481 595 ; | |||
C 179 ; WX 602 ; N daggerdbl ; B 121 -15 481 595 ; | |||
C 180 ; WX 602 ; N periodcentered ; B 207 214 395 402 ; | |||
C 182 ; WX 602 ; N paragraph ; B 57 -77 551 616 ; | |||
C 183 ; WX 602 ; N bullet ; B 222 256 379 413 ; | |||
C 184 ; WX 602 ; N quotesinglbase ; B 199 -147 380 116 ; | |||
C 185 ; WX 602 ; N quotedblbase ; B 114 -139 481 116 ; | |||
C 186 ; WX 602 ; N quotedblright ; B 122 325 489 579 ; | |||
C 187 ; WX 602 ; N guillemotright ; B 148 44 453 407 ; | |||
C 188 ; WX 602 ; N ellipsis ; B 31 -15 570 122 ; | |||
C 189 ; WX 602 ; N perthousand ; B -44 -2 664 618 ; | |||
C 191 ; WX 602 ; N questiondown ; B 118 -25 485 595 ; | |||
C 193 ; WX 602 ; N grave ; B 124 504 407 670 ; | |||
C 194 ; WX 602 ; N acute ; B 195 504 478 670 ; | |||
C 195 ; WX 602 ; N circumflex ; B 132 509 470 657 ; | |||
C 196 ; WX 602 ; N tilde ; B 123 537 478 636 ; | |||
C 197 ; WX 602 ; N macron ; B 130 551 472 611 ; | |||
C 198 ; WX 602 ; N breve ; B 113 528 488 661 ; | |||
C 199 ; WX 602 ; N dotaccent ; B 239 527 363 652 ; | |||
C 200 ; WX 602 ; N dieresis ; B 117 522 484 641 ; | |||
C 202 ; WX 602 ; N ring ; B 177 499 425 747 ; | |||
C 203 ; WX 602 ; N cedilla ; B 178 -233 431 -9 ; | |||
C 205 ; WX 602 ; N hungarumlaut ; B 168 504 535 661 ; | |||
C 206 ; WX 602 ; N ogonek ; B 248 -224 435 15 ; | |||
C 207 ; WX 602 ; N caron ; B 132 511 469 659 ; | |||
C 208 ; WX 602 ; N emdash ; B -22 184 626 266 ; | |||
C 225 ; WX 602 ; N AE ; B 0 0 578 579 ; | |||
C 227 ; WX 602 ; N ordfeminine ; B 117 110 518 596 ; | |||
C 232 ; WX 602 ; N Lslash ; B 28 0 555 579 ; | |||
C 233 ; WX 602 ; N Oslash ; B 36 -55 566 631 ; | |||
C 234 ; WX 602 ; N OE ; B 34 0 578 579 ; | |||
C 235 ; WX 602 ; N ordmasculine ; B 95 110 508 596 ; | |||
C 241 ; WX 602 ; N ae ; B 18 -13 579 465 ; | |||
C 245 ; WX 602 ; N dotlessi ; B 82 0 540 452 ; | |||
C 248 ; WX 602 ; N lslash ; B 91 0 531 639 ; | |||
C 249 ; WX 602 ; N oslash ; B 47 -22 557 475 ; | |||
C 250 ; WX 602 ; N oe ; B 35 -13 579 466 ; | |||
C 251 ; WX 602 ; N germandbls ; B 21 -11 550 640 ; | |||
C -1 ; WX 602 ; N Aacute ; B 0 0 607 802 ; | |||
C -1 ; WX 602 ; N Acircumflex ; B 0 0 607 789 ; | |||
C -1 ; WX 602 ; N Adieresis ; B 0 0 607 773 ; | |||
C -1 ; WX 602 ; N Agrave ; B 0 0 607 802 ; | |||
C -1 ; WX 602 ; N Aring ; B 0 0 607 858 ; | |||
C -1 ; WX 602 ; N Atilde ; B 0 0 607 768 ; | |||
C -1 ; WX 602 ; N Ccedilla ; B 39 -233 541 595 ; | |||
C -1 ; WX 602 ; N Eacute ; B 33 0 530 802 ; | |||
C -1 ; WX 602 ; N Ecircumflex ; B 33 0 530 789 ; | |||
C -1 ; WX 602 ; N Edieresis ; B 33 0 530 773 ; | |||
C -1 ; WX 602 ; N Egrave ; B 33 0 530 802 ; | |||
C -1 ; WX 602 ; N Iacute ; B 94 0 508 802 ; | |||
C -1 ; WX 602 ; N Icircumflex ; B 94 0 508 789 ; | |||
C -1 ; WX 602 ; N Idieresis ; B 94 0 508 773 ; | |||
C -1 ; WX 602 ; N Igrave ; B 94 0 508 802 ; | |||
C -1 ; WX 602 ; N Ntilde ; B 16 -8 569 768 ; | |||
C -1 ; WX 602 ; N Oacute ; B 36 -16 566 802 ; | |||
C -1 ; WX 602 ; N Ocircumflex ; B 36 -16 566 789 ; | |||
C -1 ; WX 602 ; N Odieresis ; B 36 -16 566 773 ; | |||
C -1 ; WX 602 ; N Ograve ; B 36 -16 566 802 ; | |||
C -1 ; WX 602 ; N Otilde ; B 36 -16 566 768 ; | |||
C -1 ; WX 602 ; N Scaron ; B 70 -23 527 791 ; | |||
C -1 ; WX 602 ; N Uacute ; B 31 -16 570 802 ; | |||
C -1 ; WX 602 ; N Ucircumflex ; B 31 -16 570 789 ; | |||
C -1 ; WX 602 ; N Udieresis ; B 31 -16 570 773 ; | |||
C -1 ; WX 602 ; N Ugrave ; B 31 -16 570 802 ; | |||
C -1 ; WX 602 ; N Ydieresis ; B 38 0 562 773 ; | |||
C -1 ; WX 602 ; N Zcaron ; B 89 0 492 791 ; | |||
C -1 ; WX 602 ; N aacute ; B 66 -12 568 670 ; | |||
C -1 ; WX 602 ; N acircumflex ; B 66 -12 568 657 ; | |||
C -1 ; WX 602 ; N adieresis ; B 66 -12 568 641 ; | |||
C -1 ; WX 602 ; N agrave ; B 66 -12 568 670 ; | |||
C -1 ; WX 602 ; N aring ; B 66 -12 568 743 ; | |||
C -1 ; WX 602 ; N atilde ; B 66 -12 568 636 ; | |||
C -1 ; WX 602 ; N ccedilla ; B 48 -233 523 463 ; | |||
C -1 ; WX 602 ; N eacute ; B 51 -12 546 670 ; | |||
C -1 ; WX 602 ; N ecircumflex ; B 51 -12 546 657 ; | |||
C -1 ; WX 602 ; N edieresis ; B 51 -12 546 641 ; | |||
C -1 ; WX 602 ; N egrave ; B 51 -12 546 670 ; | |||
C -1 ; WX 602 ; N iacute ; B 82 0 540 670 ; | |||
C -1 ; WX 602 ; N icircumflex ; B 82 0 540 657 ; | |||
C -1 ; WX 602 ; N idieresis ; B 82 0 540 641 ; | |||
C -1 ; WX 602 ; N igrave ; B 82 0 540 670 ; | |||
C -1 ; WX 602 ; N ntilde ; B 51 0 563 636 ; | |||
C -1 ; WX 602 ; N oacute ; B 45 -12 557 670 ; | |||
C -1 ; WX 602 ; N ocircumflex ; B 45 -12 557 657 ; | |||
C -1 ; WX 602 ; N odieresis ; B 45 -12 557 641 ; | |||
C -1 ; WX 602 ; N ograve ; B 45 -12 557 670 ; | |||
C -1 ; WX 602 ; N otilde ; B 45 -12 557 636 ; | |||
C -1 ; WX 602 ; N scaron ; B 87 -12 512 659 ; | |||
C -1 ; WX 602 ; N uacute ; B 35 -10 558 670 ; | |||
C -1 ; WX 602 ; N ucircumflex ; B 35 -10 558 657 ; | |||
C -1 ; WX 602 ; N udieresis ; B 35 -10 558 641 ; | |||
C -1 ; WX 602 ; N ugrave ; B 35 -10 558 670 ; | |||
C -1 ; WX 602 ; N ydieresis ; B 28 -195 570 641 ; | |||
C -1 ; WX 602 ; N zcaron ; B 98 0 508 659 ; | |||
C -1 ; WX 602 ; N trademark ; B 56 337 547 616 ; | |||
C -1 ; WX 602 ; N copyright ; B 11 45 591 625 ; | |||
C -1 ; WX 602 ; N logicalnot ; B 48 170 553 445 ; | |||
C -1 ; WX 602 ; N registered ; B 11 45 591 625 ; | |||
C -1 ; WX 602 ; N minus ; B 76 262 526 354 ; | |||
C -1 ; WX 602 ; N Eth ; B 20 -2 558 579 ; | |||
C -1 ; WX 602 ; N Thorn ; B 55 0 518 579 ; | |||
C -1 ; WX 602 ; N Yacute ; B 38 0 562 802 ; | |||
C -1 ; WX 602 ; N brokenbar ; B 271 -172 331 699 ; | |||
C -1 ; WX 602 ; N degree ; B 143 349 459 665 ; | |||
C -1 ; WX 602 ; N divide ; B 51 99 551 516 ; | |||
C -1 ; WX 602 ; N eth ; B 45 -12 557 640 ; | |||
C -1 ; WX 602 ; N mu ; B 32 -201 534 452 ; | |||
C -1 ; WX 602 ; N multiply ; B 105 125 493 511 ; | |||
C -1 ; WX 602 ; N onehalf ; B 59 -89 539 680 ; | |||
C -1 ; WX 602 ; N onequarter ; B 59 -94 539 680 ; | |||
C -1 ; WX 602 ; N onesuperior ; B 164 240 462 633 ; | |||
C -1 ; WX 602 ; N plusminus ; B 105 44 497 619 ; | |||
C -1 ; WX 602 ; N thorn ; B 33 -195 566 639 ; | |||
C -1 ; WX 602 ; N threequarters ; B 59 -94 539 680 ; | |||
C -1 ; WX 602 ; N threesuperior ; B 140 222 461 633 ; | |||
C -1 ; WX 602 ; N twosuperior ; B 145 240 451 632 ; | |||
C -1 ; WX 602 ; N yacute ; B 28 -195 570 670 ; | |||
EndCharMetrics | |||
StartKernData | |||
StartKernPairs 0 | |||
EndKernPairs | |||
StartTrackKern 0 | |||
EndTrackKern | |||
EndKernData | |||
EndFontMetrics |