/*
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.sun.pdfview.font;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import com.sun.pdfview.PDFFile;
import com.sun.pdfview.PDFObject;
/**
* A representation, with parser, of an Adobe Type 1 font.
* @author Mike Wessler
*/
public class Type1Font extends OutlineFont {
String chr2name[];
int password;
byte[] subrs[];
int lenIV;
Map<String,Object> name2outline;
Map<String,FlPoint> name2width;
AffineTransform at;
/** the Type1 stack of command values */
float stack[] = new float[100];
/** the current position in the Type1 stack */
int sloc = 0;
/** the stack of postscript commands (used by callothersubr) */
float psStack[] = new float[3];
/** the current position in the postscript stack */
int psLoc = 0;
/**
* create a new Type1Font based on a font data stream and an encoding.
* @param baseName the postscript name of this font
* @param src the Font object as a stream with a dictionary
* @param descriptor the descriptor for this font
*/
public Type1Font(String baseName, PDFObject src,
PDFFontDescriptor descriptor) throws IOException {
super(baseName, src, descriptor);
if (descriptor != null && descriptor.getFontFile() != null) {
// parse that file, filling name2outline and chr2name
int start = descriptor.getFontFile().getDictRef("Length1").getIntValue();
int len = descriptor.getFontFile().getDictRef("Length2").getIntValue();
byte font[] = descriptor.getFontFile().getStream();
parseFont(font, start, len);
}
}
/** Read a font from it's data, start position and length */
protected void parseFont(byte[] font, int start, int len) {
this.name2width = new HashMap<String,FlPoint>();
byte data[] = null;
if (isASCII(font, start)) {
byte[] bData = readASCII(font, start, start + len);
data = decrypt(bData, 0, bData.length, 55665, 4);
} else {
data = decrypt(font, start, start + len, 55665, 4);
}
// encoding is in cleartext area
this.chr2name = readEncoding(font);
int lenIVLoc = findSlashName(data, "lenIV");
PSParser psp = new PSParser(data, 0);
if (lenIVLoc < 0) {
this.lenIV = 4;
} else {
psp.setLoc(lenIVLoc + 6);
this.lenIV = Integer.parseInt(psp.readThing());
}
this.password = 4330;
int matrixloc = findSlashName(font, "FontMatrix");
if (matrixloc < 0) {
System.out.println("No FontMatrix!");
this.at = new AffineTransform(0.001f, 0, 0, 0.001f, 0, 0);
} else {
PSParser psp2 = new PSParser(font, matrixloc + 11);
// read [num num num num num num]
float xf[] = psp2.readArray(6);
// System.out.println("FONT MATRIX: "+xf);
this.at = new AffineTransform(xf);
}
this.subrs = readSubrs(data);
this.name2outline = new TreeMap<String,Object>(readChars(data));
// at this point, name2outline holds name -> byte[].
}
/**
* parse the encoding portion of the font definition
* @param d the font definition stream
* @return an array of the glyphs corresponding to each byte
*/
private String[] readEncoding(byte[] d) {
byte[][] ary = readArray(d, "Encoding", "def");
String res[] = new String[256];
for (int i = 0; i < ary.length; i++) {
if (ary[i] != null) {
if (ary[i][0] == '/') {
res[i] = new String(ary[i]).substring(1);
} else {
res[i] = new String(ary[i]);
}
} else {
res[i] = null;
}
}
return res;
}
/**
* read the subroutines out of the font definition
* @param d the font definition stream
* @return an array of the subroutines, each as a byte array.
*/
private byte[][] readSubrs(byte[] d) {
return readArray(d, "Subrs", "index");
}
/**
* read a named array out of the font definition.
* <p>
* this function attempts to parse an array out of a postscript
* definition without doing any postscript. It's actually looking
* for things that look like "dup <i>id</i> <i>elt</i> put", and
* placing the <i>elt</i> at the <i>i</i>th position in the array.
* @param d the font definition stream
* @param key the name of the array
* @param end a string that appears at the end of the array
* @return an array consisting of a byte array for each entry
*/
private byte[][] readArray(byte[] d, String key, String end) {
int i = findSlashName(d, key);
if (i < 0) {
// not found.
return new byte[0][];
}
// now find things that look like "dup id elt put"
// end at "def"
PSParser psp = new PSParser(d, i);
String type = psp.readThing(); // read the key (i is the start of the key)
double val;
type = psp.readThing();
if (type.equals("StandardEncoding")) {
byte[] stdenc[] = new byte[FontSupport.standardEncoding.length][];
for (i = 0; i < stdenc.length; i++) {
stdenc[i] = FontSupport.getName(FontSupport.standardEncoding[i]).getBytes();
}
return stdenc;
}
int len = Integer.parseInt(type);
byte[] out[] = new byte[len][];
byte[] line;
while (true) {
String s = psp.readThing();
if (s.equals("dup")) {
String thing = psp.readThing();
int id = 0;
try {
id = Integer.parseInt(thing);
} catch (Exception e) {
break;
}
String elt = psp.readThing();
line = elt.getBytes();
if (Character.isDigit(elt.charAt(0))) {
int hold = Integer.parseInt(elt);
String special = psp.readThing();
if (special.equals("-|") || special.equals("RD")) {
psp.setLoc(psp.getLoc() + 1);
line = psp.getNEncodedBytes(hold, this.password, this.lenIV);
}
}
out[id] = line;
} else if (s.equals(end)) {
break;
}
}
return out;
}
/**
* decrypt an array using the Adobe Type 1 Font decryption algorithm.
* @param d the input array of bytes
* @param start where in the array to start decoding
* @param end where in the array to stop decoding
* @param key the decryption key
* @param skip how many bytes to skip initially
* @return the decrypted bytes. The length of this array will be
* (start-end-skip) bytes long
*/
private byte[] decrypt(byte[] d, int start, int end, int key, int skip) {
if (end - start - skip < 0) {
skip = 0;
}
byte[] o = new byte[end - start - skip];
int r = key;
int ipos;
int c1 = 52845;
int c2 = 22719;
for (ipos = start; ipos < end; ipos++) {
int c = d[ipos] & 0xff;
int p = (c ^ (r >> 8)) & 0xff;
r = ((c + r) * c1 + c2) & 0xffff;
if (ipos - start - skip >= 0) {
o[ipos - start - skip] = (byte) p;
}
}
return o;
}
/**
* Read data formatted as ASCII strings as binary data
*
* @param data the data, formatted as ASCII strings
* @param start where in the array to start decrypting
* @param end where in the array to stop decrypting
*/
private byte[] readASCII(byte[] data, int start, int end) {
// each byte of output is derived from one character (two bytes) of
// input
byte[] o = new byte[(end - start) / 2];
int count = 0;
int bit = 0;
for (int loc = start; loc < end; loc++) {
char c = (char) (data[loc] & 0xff);
byte b = (byte) 0;
if (c >= '0' && c <= '9') {
b = (byte) (c - '0');
} else if (c >= 'a' && c <= 'f') {
b = (byte) (10 + (c - 'a'));
} else if (c >= 'A' && c <= 'F') {
b = (byte) (10 + (c - 'A'));
} else {
// linefeed or something. Skip.
continue;
}
// which half of the byte are we?
if ((bit++ % 2) == 0) {
o[count] = (byte) (b << 4);
} else {
o[count++] |= b;
}
}
return o;
}
/**
* Determine if data is in ASCII or binary format. According to the spec,
* if any of the first 4 bytes are not character codes ('0' - '9' or
* 'A' - 'F' or 'a' - 'f'), then the data is binary. Otherwise it is
* ASCII
*/
private boolean isASCII(byte[] data, int start) {
// look at the first 4 bytes
for (int i = start; i < start + 4; i++) {
// get the byte as a character
char c = (char) (data[i] & 0xff);
if (c >= '0' && c <= '9') {
continue;
} else if (c >= 'a' && c <= 'f') {
continue;
} else if (c >= 'A' && c <= 'F') {
continue;
} else {
// out of range
return false;
}
}
// all were in range, so it is ASCII
return true;
}
/**
* PostScript reader (not a parser, as the name would seem to indicate).
*/
class PSParser {
byte[] data;
int loc;
/**
* create a PostScript reader given some data and an initial offset
* into that data.
* @param data the bytes of the postscript information
* @param start an initial offset into the data
*/
public PSParser(byte[] data, int start) {
this.data = data;
this.loc = start;
// System.out.println("PSParser.constructor: start: " + start +
// ", Length: " + data.length);
// System.out.print (new String(data, loc, data.length - loc));
// System.out.println(" - end -\n");
}
/**
* get the next postscript "word". This is basically the next
* non-whitespace block between two whitespace delimiters.
* This means that something like " [2 4 53]" will produce
* three items, while " [2 4 56 ]" will produce four.
*/
public String readThing() {
// skip whitespace
// System.out.println("PAParser: whitespace: \"");
while (PDFFile.isWhiteSpace(this.data[this.loc])) {
// System.out.print (new String(data, loc, 1));
this.loc++;
}
// System.out.print("\": thing: ");
// read thing
int start = this.loc;
while (!PDFFile.isWhiteSpace(this.data[this.loc])) {
this.loc++;
if (!PDFFile.isRegularCharacter(this.data[this.loc])) {
break; // leave with the delimiter included
}
}
String s = new String(this.data, start, this.loc - start);
// System.out.println(": Read: "+s);
return s;
}
/**
* read a set of numbers from the input. This method doesn't
* pay any attention to "[" or "]" delimiters, and reads any
* non-numeric items as the number 0.
* @param count the number of items to read
* @return an array of count floats
*/
public float[] readArray(int count) {
float[] ary = new float[count];
int idx = 0;
while (idx < count) {
String thing = readThing();
if (thing.charAt(0) == '[') {
thing = thing.substring(1);
}
if (thing.endsWith("]")) {
thing = thing.substring(0, thing.length() - 1);
}
if (thing.length() > 0) {
ary[idx++] = Float.parseFloat(thing);
}
}
return ary;
}
/**
* get the current location within the input stream
*/
public int getLoc() {
return this.loc;
}
/**
* set the current location within the input stream
*/
public void setLoc(int loc) {
this.loc = loc;
}
/**
* treat the next n bytes of the input stream as encoded
* information to be decrypted.
* @param n the number of bytes to decrypt
* @param key the decryption key
* @param skip the number of bytes to skip at the beginning of the
* decryption
* @return an array of decrypted bytes. The length of the array
* will be n-skip.
*/
public byte[] getNEncodedBytes(int n, int key, int skip) {
byte[] result = decrypt(this.data, this.loc, this.loc + n, key, skip);
this.loc += n;
return result;
}
}
/**
* get the index into the byte array of a slashed name, like "/name".
* @param d the search array
* @param name the name to look for, without the initial /
* @return the index of the first occurance of /name in the array.
*/
private int findSlashName(byte[] d, String name) {
int i;
for (i = 0; i < d.length; i++) {
if (d[i] == '/') {
// check for key
boolean found = true;
for (int j = 0; j < name.length(); j++) {
if (d[i + j + 1] != name.charAt(j)) {
found = false;
break;
}
}
if (found) {
return i;
}
}
}
return -1;
}
/**
* get the character definitions of the font.
* @param d the font data
* @return a HashMap that maps string glyph names to byte arrays of
* decoded font data.
*/
private HashMap<String,byte[]> readChars(byte[] d) {
// skip thru data until we find "/"+key
HashMap<String,byte[]> hm = new HashMap<String,byte[]>();
int i = findSlashName(d, "CharStrings");
if (i < 0) {
// not found
return hm;
}
PSParser psp = new PSParser(d, i);
// read /name len -| [len bytes] |-
// until "end"
while (true) {
String s = psp.readThing();
char c = s.charAt(0);
if (c == '/') {
int len = Integer.parseInt(psp.readThing());
String go = psp.readThing(); // it's -| or RD
if (go.equals("-|") || go.equals("RD")) {
psp.setLoc(psp.getLoc() + 1);
byte[] line = psp.getNEncodedBytes(len, this.password, this.lenIV);
hm.put(s.substring(1), line);
}
} else if (s.equals("end")) {
break;
}
}
return hm;
}
/**
* pop the next item off the stack
*/
private float pop() {
float val = 0;
if (this.sloc > 0) {
val = this.stack[--this.sloc];
}
return val;
}
int callcount = 0;
/**
* parse glyph data into a GeneralPath, and return the advance width.
* The working point is passed in as a parameter in order to allow
* recursion.
* @param cs the decrypted glyph data
* @param gp a GeneralPath into which the glyph shape will be stored
* @param pt a FlPoint object that will be used to generate the path
* @param wid a FlPoint into which the advance width will be placed.
*/
private void parse(byte[] cs, GeneralPath gp, FlPoint pt, FlPoint wid) {
// System.out.println("--- cmd length is "+cs.length);
int loc = 0;
float x1, x2, x3, y1, y2, y3;
boolean flexMode = false;
float[] flexArray = new float[16];
int flexPt = 0;
while (loc < cs.length) {
int v = (cs[loc++]) & 0xff;
if (v == 255) {
this.stack[this.sloc++] = (((cs[loc]) & 0xff) << 24) +
(((cs[loc + 1]) & 0xff) << 16) +
(((cs[loc + 2]) & 0xff) << 8) +
(((cs[loc + 3]) & 0xff));
loc += 4;
// System.out.println("Pushed long "+stack[sloc-1]);
} else if (v >= 251) {
this.stack[this.sloc++] = -((v - 251) << 8) - ((cs[loc]) & 0xff) - 108;
loc++;
// System.out.println("Pushed lo "+stack[sloc-1]);
} else if (v >= 247) {
this.stack[this.sloc++] = ((v - 247) << 8) + ((cs[loc]) & 0xff) + 108;
loc++;
// System.out.println("Pushed hi "+stack[sloc-1]);
} else if (v >= 32) {
this.stack[this.sloc++] = v - 139;
// System.out.println("Pushed "+stack[sloc-1]);
} else {
// System.out.println("CMD: "+v+" (stack is size "+sloc+")");
switch (v) {
case 0: // x
throw new RuntimeException("Bad command (" + v + ")");
case 1: // hstem
this.sloc = 0;
break;
case 2: // x
throw new RuntimeException("Bad command (" + v + ")");
case 3: // vstem
this.sloc = 0;
break;
case 4: // y vmoveto
pt.y += pop();
if (flexMode) {
flexArray[flexPt++] = pt.x;
flexArray[flexPt++] = pt.y;
}
else{
gp.moveTo(pt.x, pt.y);
}
this.sloc = 0;
break;
case 5: // x y rlineto
pt.y += pop();
pt.x += pop();
gp.lineTo(pt.x, pt.y);
this.sloc = 0;
break;
case 6: // x hlineto
pt.x += pop();
gp.lineTo(pt.x, pt.y);
this.sloc = 0;
break;
case 7: // y vlineto
pt.y += pop();
gp.lineTo(pt.x, pt.y);
this.sloc = 0;
break;
case 8: // x1 y1 x2 y2 x3 y3 rcurveto
y3 = pop();
x3 = pop();
y2 = pop();
x2 = pop();
y1 = pop();
x1 = pop();
gp.curveTo(pt.x + x1, pt.y + y1,
pt.x + x1 + x2, pt.y + y1 + y2,
pt.x + x1 + x2 + x3, pt.y + y1 + y2 + y3);
pt.x += x1 + x2 + x3;
pt.y += y1 + y2 + y3;
this.sloc = 0;
break;
case 9: // closepath
gp.closePath();
this.sloc = 0;
break;
case 10: // n callsubr
int n = (int) pop();
if (n == 1) {
flexMode = true;
flexPt = 0;
this.sloc = 0;
break;
}
if (n == 0) {
if (flexPt != 14) {
System.out.println("There must be 14 flex entries!");
}
else {
gp.curveTo(flexArray[2], flexArray[3], flexArray[4],
flexArray[5],
flexArray[6], flexArray[7]);
gp.curveTo(flexArray[8], flexArray[9], flexArray[10],
flexArray[11],
flexArray[12], flexArray[13]);
flexMode = false;
this.sloc = 0;
//System.out.println("End Flex " + flexPt);
break;
}
}
if (n == 2) {
if (flexMode == false) {
System.out.println("Flex mode assumed");
}
else {
this.sloc = 0;
break;
}
}
if (this.subrs[n] == null) {
System.out.println("No subroutine #" + n);
} else {
this.callcount++;
if (this.callcount > 10) {
System.out.println("Call stack too large");
// throw new RuntimeException("Call stack too large");
} else {
parse(this.subrs[n], gp, pt, wid);
}
this.callcount--;
}
break;
case 11: // return
return;
case 12: // ext...
v = (cs[loc++]) & 0xff;
if (v == 6) { // s x y a b seac
char b = (char) pop();
char a = (char) pop();
float y = pop();
float x = pop();
buildAccentChar(x, y, a, b, gp);
this.sloc = 0;
} else if (v == 7) { // x y w h sbw
wid.y = pop();
wid.x = pop();
pt.y = pop();
pt.x = pop();
this.sloc = 0;
} else if (v == 12) { // a b div -> a/b
float b = pop();
float a = pop();
this.stack[this.sloc++] = a / b;
} else if (v == 33) { // a b setcurrentpoint
pt.y = pop();
pt.x = pop();
gp.moveTo(pt.x, pt.y);
this.sloc = 0;
} else if (v == 0) { // dotsection
this.sloc = 0;
} else if (v == 1) { // vstem3
this.sloc = 0;
} else if (v == 2) { // hstem3
this.sloc = 0;
} else if (v == 16) { // n callothersubr
int cn = (int) pop();
int countargs = (int) pop();
// System.out.println("Called othersubr with index "+cn);
switch (cn) {
case 0:
// push args2 and args3 onto stack
this.psStack[this.psLoc++] = pop();
this.psStack[this.psLoc++] = pop();
pop();
break;
case 3:
// push 3 onto the postscript stack
this.psStack[this.psLoc++] = 3;
break;
default:
// push arguments onto the postscript stack
for (int i = 0; i > countargs; i--) {
this.psStack[this.psLoc++] = pop();
}
break;
}
} else if (v == 17) { // pop
// pop from the postscript stack onto the type1 stack
this.stack[this.sloc++] = this.psStack[this.psLoc - 1];
this.psLoc--;
} else {
throw new RuntimeException("Bad command (" + v + ")");
}
break;
case 13: // s w hsbw
wid.x = pop();
wid.y = 0;
pt.x = pop();
pt.y = 0;
// gp.moveTo(pt.x, pt.y);
this.sloc = 0;
break;
case 14: // endchar
// return;
break;
case 15: // x
case 16: // x
case 17: // x
case 18: // x
case 19: // x
case 20: // x
throw new RuntimeException("Bad command (" + v + ")");
case 21: // x y rmoveto
pt.y += pop();
pt.x += pop();
if (flexMode) {
flexArray[flexPt++] = pt.x;
flexArray[flexPt++] = pt.y;
}
else{
gp.moveTo(pt.x, pt.y);
}
this.sloc = 0;
break;
case 22: // x hmoveto
pt.x += pop();
if (flexMode) {
flexArray[flexPt++] = pt.x;
flexArray[flexPt++] = pt.y;
}
else {
gp.moveTo(pt.x, pt.y);
}
this.sloc = 0;
break;
case 23: // x
case 24: // x
case 25: // x
case 26: // x
case 27: // x
case 28: // x
case 29: // x
throw new RuntimeException("Bad command (" + v + ")");
case 30: // y1 x2 y2 x3 vhcurveto
x3 = pop();
y2 = pop();
x2 = pop();
y1 = pop();
x1 = y3 = 0;
gp.curveTo(pt.x, pt.y + y1,
pt.x + x2, pt.y + y1 + y2,
pt.x + x2 + x3, pt.y + y1 + y2);
pt.x += x2 + x3;
pt.y += y1 + y2;
this.sloc = 0;
break;
case 31: // x1 x2 y2 y3 hvcurveto
y3 = pop();
y2 = pop();
x2 = pop();
x1 = pop();
y1 = x3 = 0;
gp.curveTo(pt.x + x1, pt.y,
pt.x + x1 + x2, pt.y + y2,
pt.x + x1 + x2, pt.y + y2 + y3);
pt.x += x1 + x2;
pt.y += y2 + y3;
this.sloc = 0;
break;
}
}
}
}
/**
* build an accented character out of two pre-defined glyphs.
* @param x the x offset of the accent
* @param y the y offset of the accent
* @param a the index of the accent glyph
* @param b the index of the base glyph
* @param gp the GeneralPath into which the combined glyph will be
* written.
*/
private void buildAccentChar(float x, float y, char a, char b,
GeneralPath gp) {
// get the outline of the accent
GeneralPath pathA = getOutline(a, getWidth(a, null));
try {
// undo the effect of the transform applied in read
AffineTransform xformA = this.at.createInverse();
xformA.translate(x, y);
pathA.transform(xformA);
} catch (NoninvertibleTransformException nte) {
pathA.transform(AffineTransform.getTranslateInstance(x, y));
}
GeneralPath pathB = getOutline(b, getWidth(b, null));
try {
AffineTransform xformB = this.at.createInverse();
pathB.transform(xformB);
} catch (NoninvertibleTransformException nte) {
// ignore
}
gp.append(pathB, false);
gp.append(pathA, false);
}
/**
* Get the width of a given character
*
* This method is overridden to work if the width array hasn't been
* populated (as for one of the 14 base fonts)
*/
@Override
public float getWidth(char code, String name) {
// we don't have first and last chars, so therefore no width array
if (getFirstChar() == -1 || getLastChar() == -1) {
String key = this.chr2name[code & 0xff];
// use a name if one is provided
if (name != null) {
key = name;
}
if (key != null && this.name2outline.containsKey(key)) {
if (!this.name2width.containsKey(key)) {
// glyph has not yet been parsed
// getting the outline will force it to get read
getOutline(key, 0);
}
FlPoint width = this.name2width.get(key);
if (width != null) {
return width.x / getDefaultWidth();
}
}
return 0;
}
// return the width that has been specified
return super.getWidth(code, name);
}
/**
* Decrypt a glyph stored in byte form
*/
private synchronized GeneralPath parseGlyph(byte[] cs, FlPoint advance,
AffineTransform at) {
GeneralPath gp = new GeneralPath();
FlPoint curpoint = new FlPoint();
this.sloc = 0;
parse(cs, gp, curpoint, advance);
gp.transform(at);
return gp;
}
/**
* Get a glyph outline by name
*
* @param name the name of the desired glyph
* @return the glyph outline, or null if unavailable
*/
@Override
protected GeneralPath getOutline(String name, float width) {
// make sure we have a valid name
if (name == null || !this.name2outline.containsKey(name)) {
name = ".notdef";
}
// get whatever is stored in name. Could be a GeneralPath, could be byte[]
Object obj = this.name2outline.get(name);
// if it's a byte array, it needs to be parsed
// otherwise, just return the path
if (obj instanceof GeneralPath) {
return (GeneralPath) obj;
} else {
byte[] cs = (byte[]) obj;
FlPoint advance = new FlPoint();
GeneralPath gp = parseGlyph(cs, advance, this.at);
if (width != 0 && advance.x != 0) {
// scale the glyph to fit in the width
Point2D p = new Point2D.Float(advance.x, advance.y);
this.at.transform(p, p);
double scale = width / p.getX();
AffineTransform xform = AffineTransform.getScaleInstance(scale, 1.0);
gp.transform(xform);
}
// put the parsed object in the cache
this.name2outline.put(name, gp);
this.name2width.put(name, advance);
return gp;
}
}
/**
* Get a glyph outline by character code
*
* Note this method must always return an outline
*
* @param src the character code of the desired glyph
* @return the glyph outline
*/
@Override
protected GeneralPath getOutline(char src, float width) {
return getOutline(this.chr2name[src & 0xff], width);
}
}