blob: 7b05e567d9c9f90f8406f9360a582b7cf7272db1 [file] [log] [blame]
/*
* 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 */
protected HashMap<String, byte[]> subsetCharStrings;
/* The list of character names in the subset font */
protected List<String> charNames = null;
/* A list of unique subroutines references */
protected LinkedHashMap<Integer, byte[]> uniqueSubs;
private SingleByteFont sbfont = null;
/* New line character */
protected String eol = "\n";
/* An option to determine whether the subroutines are subset */
protected boolean subsetSubroutines = true;
private byte[] fullFont;
//List of parsed Postscript elements
protected List<PSElement> headerSection;
protected List<PSElement> mainSection;
//Determines whether the current font uses standard encoding
protected 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) throws IOException {
fullFont = IOUtils.toByteArray(in);
byte[] subsetFont = createSubset(sbfont, 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 sbfont The font object containing information such as the
* characters from which to create the subset
* @param subsetSubroutines 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, 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);
boolean result = readMainSection(mainSection, decoded, subsetEncodingEntries, charStrings);
if (!result) {
/* 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, false);
}
//Write header section
ByteArrayOutputStream boasHeader = writeHeader(pfbData, encoding);
ByteArrayOutputStream boasMain = writeMainSection(decoded, mainSection, charStrings);
byte[] mainSectionBytes = boasMain.toByteArray();
mainSectionBytes = BinaryCoder.encodeBytes(mainSectionBytes, 55665, 4);
boasMain.reset();
boasMain.write(mainSectionBytes);
ByteArrayOutputStream baosTrailer = new ByteArrayOutputStream();
baosTrailer.write(pfbData.getTrailerSegment(), 0, pfbData.getTrailerSegment().length);
return stitchFont(boasHeader, boasMain, baosTrailer);
}
protected 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.clear();
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;
}
protected 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;
}
protected ByteArrayOutputStream writeHeader(PFBData pfbData, PSElement encoding) throws 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 + "/Encoding 256 array" + 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 boolean 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);
}
if (charStringEntry.length == 0) {
return false;
}
charStringEntry = BinaryCoder.encodeBytes(charStringEntry, 4330, skipBytes);
subsetCharStrings.put(tag, charStringEntry);
}
return true;
}
private byte[] createSubsetCharStrings(byte[] decoded, byte[] data, PSFixedArray subroutines,
List<String> subsetEncodingEntries) {
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,
data, i, 1, -1, operands.get(
operands.size() - 1).getNumber());
} else {
data = addSubroutine(subroutines, operands, decoded, subsetEncodingEntries,
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,
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, 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);
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;
}
protected 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()) {
writeString(eol + String.format("dup %d %d %s ", count++, entry.getValue().length, rd), main);
main.write(entry.getValue());
writeString(" " + np, main);
}
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;
}
protected 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);
}
protected void writeString(String entry, ByteArrayOutputStream boas)
throws 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
*/
protected 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;
}
protected String getEntryPart(String entry, int part) {
Scanner s = new Scanner(entry).useDelimiter(" ");
for (int i = 1; i < part; i++) {
s.next();
}
return s.next();
}
protected PSElement getElement(String elementID, List<PSElement> elements) {
for (PSElement element : elements) {
if (element.getOperator().equals(elementID)) {
return element;
}
}
return null;
}
/**
* 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;
}
}
}