| /* |
| * 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; |
| } |
| } |
| } |