/*
 * 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;
    /* A list of unique subroutines references */
    protected LinkedHashMap<Integer, byte[]> uniqueSubs;
    private SingleByteFont sbfont;
    /* 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;

    //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
Params:
  • sbfont – The font object containing information such as the characters from which to create the subset
  • subsetSubroutines – This option will force the subset to include all subroutines.
Throws:
Returns:Returns the subset as a byte array
/** * 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); } public 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
/** * 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; 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
Params:
  • a – The first array
  • b – The second array
Returns:The concatenated array
/** * 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.
Params:
  • position – An array containing both the start and end position of the section to copy.
  • decoded – The array from which to copy a section of data
Returns:Returns the copy of the data section
/** * 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.
/** * 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; } } }