/*
* 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.io.IOException;
import com.sun.pdfview.PDFDebugger;
import com.sun.pdfview.PDFObject;
/**
* A representation, with parser, of an Adobe Type 1C font.
* You can find information about CFF and Type 1C font encoding
* in
* http://partners.adobe.com/public/developer/en/font/5176.CFF.pdf
* and
* http://partners.adobe.com/public/developer/en/font/5177.Type2.pdf
*
* @author Mike Wessler
*/
public class Type1CFont extends OutlineFont {
String chr2name[] = new String[256];
byte[] data;
int pos;
byte[] subrs;
float[] stack = new float[100];
int stackptr = 0;
int stemhints = 0;
String names[];
int glyphnames[];
int encoding[] = new int[256];
String fontname;
AffineTransform at = new AffineTransform (0.001f, 0, 0, 0.001f, 0, 0);
int num;
float fnum;
int type;
static int CMD = 0;
static int NUM = 1;
static int FLT = 2;
/**
* create a new Type1CFont based on a font data stream and a descriptor
* @param baseFont the postscript name of this font
* @param src a stream containing the font
* @param descriptor the descriptor for this font
*/
public Type1CFont (String baseFont, PDFObject src,
PDFFontDescriptor descriptor) throws IOException {
super (baseFont, src, descriptor);
PDFObject dataObj = descriptor.getFontFile3 ();
this.data = dataObj.getStream ();
this.pos = 0;
parse ();
// TODO: free up (set to null) unused structures (data, subrs, stack)
}
/**
* a debug method for printing the data
*/
private void printData () {
char[] parts = new char[17];
int partsloc = 0;
for (int i = 0; i < this.data.length; i++) {
int d = (this.data[i]) & 0xff;
if (d == 0) {
parts[partsloc++] = '.';
} else if (d < 32 || d >= 127) {
parts[partsloc++] = '?';
} else {
parts[partsloc++] = (char) d;
}
if (d < 16) {
PDFDebugger.debug("0" + Integer.toHexString (d), 200);
} else {
PDFDebugger.debug(Integer.toHexString (d), 200);
}
if ((i & 15) == 15) {
PDFDebugger.debug(" " + new String (parts), 200);
partsloc = 0;
} else if ((i & 7) == 7) {
PDFDebugger.debug(" ", 200);
parts[partsloc++] = ' ';
} else if ((i & 1) == 1) {
PDFDebugger.debug(" ", 200);
}
}
}
/**
* read the next decoded value from the stream
* @param charstring ????
*/
private int readNext (boolean charstring) {
this.num = (this.data[this.pos++]) & 0xff;
if (this.num == 30 && !charstring) { // goofy floatingpoint rep
readFNum ();
return this.type = FLT;
} else if (this.num == 28) {
this.num = ((this.data[this.pos]) << 8) + ((this.data[this.pos + 1]) & 0xff);
this.pos += 2;
return this.type = NUM;
} else if (this.num == 29 && !charstring) {
this.num = ((this.data[this.pos] & 0xff) << 24) |
((this.data[this.pos + 1] & 0xff) << 16) |
((this.data[this.pos + 2] & 0xff) << 8) |
((this.data[this.pos + 3] & 0xff));
this.pos += 4;
return this.type = NUM;
} else if (this.num == 12) { // two-byte command
this.num = 1000 + ((this.data[this.pos++]) & 0xff);
return this.type = CMD;
} else if (this.num < 32) {
return this.type = CMD;
} else if (this.num < 247) {
this.num -= 139;
return this.type = NUM;
} else if (this.num < 251) {
this.num = (this.num - 247) * 256 + ((this.data[this.pos++]) & 0xff) + 108;
return this.type = NUM;
} else if (this.num < 255) {
this.num = -(this.num - 251) * 256 - ((this.data[this.pos++]) & 0xff) - 108;
return this.type = NUM;
} else if (!charstring) { // dict shouldn't have a 255 code
printData ();
throw new RuntimeException ("Got a 255 code while reading dict");
} else { // num was 255
this.fnum = (((this.data[this.pos] & 0xff) << 24) |
((this.data[this.pos + 1] & 0xff) << 16) |
((this.data[this.pos + 2] & 0xff) << 8) |
((this.data[this.pos + 3] & 0xff))) / 65536f;
this.pos += 4;
return this.type = FLT;
}
}
/**
* read the next funky floating point number from the input stream.
* value gets put into the fnum field.
*/
public void readFNum () {
// work in nybbles: 0-9=0-9, a=. b=E, c=E-, d=rsvd e=neg f=end
float f = 0;
boolean neg = false;
int exp = 0;
int eval = 0;
float mul = 1;
byte work = this.data[this.pos++];
while (true) {
if (work == (byte) 0xdd) {
work = this.data[this.pos++];
}
int nyb = (work >> 4) & 0xf;
work = (byte) ((work << 4) | 0xd);
if (nyb < 10) {
if (exp != 0) { // working on the exponent
eval = eval * 10 + nyb;
} else if (mul == 1) { // working on an int
f = f * 10 + nyb;
} else { // working on decimal part
f += nyb * mul;
mul /= 10f;
}
} else if (nyb == 0xa) { // decimal
mul = 0.1f;
} else if (nyb == 0xb) { // E+
exp = 1;
} else if (nyb == 0xc) { // E-
exp = -1;
} else if (nyb == 0xe) { // neg
neg = true;
} else {
break;
}
}
this.fnum = (neg ? -1 : 1) * f * (float) Math.pow (10, eval * exp);
}
/**
* read an integer from the input stream
* @param len the number of bytes in the integer
* @return the integer
*/
private int readInt (int len) {
int n = 0;
for (int i = 0; i < len; i++) {
n = (n << 8) | ((this.data[this.pos++]) & 0xff);
}
return n;
}
/**
* read the next byte from the stream
* @return the byte
*/
private int readByte () {
return (this.data[this.pos++]) & 0xff;
}
// DICT structure:
// operand operator operand operator ...
// INDEX structure:
// count(2) offsize [offset offset ... offset] data
// offset array has count+1 entries
// data starts at 3+(count+1)*offsize
// offset for data is offset+2+(count+1)*offsize
/**
* get the size of the dictionary located within the stream at
* some offset.
* @param loc the index of the start of the dictionary
* @return the size of the dictionary, in bytes.
*/
public int getIndexSize (int loc) {
int hold = this.pos;
this.pos = loc;
int count = readInt (2);
if (count <= 0) {
return 2;
}
int encsz = readByte ();
if (encsz < 1 || encsz > 4) {
throw new RuntimeException ("Offsize: " + encsz +
", must be in range 1-4.");
}
// pos is now at the first offset. last offset is at count*encsz
this.pos += count * encsz;
int end = readInt (encsz);
this.pos = hold;
return 2 + (count + 1) * encsz + end;
}
/**
* return the number of entries in an Index table.
*
* @param loc
* @return
*/
public int getTableLength (int loc) {
int hold = this.pos;
this.pos = loc;
int count = readInt (2);
if (count <= 0) {
return 2;
}
this.pos = hold;
return count;
}
/**
* A range. There's probably a version of this class floating around
* somewhere already in Java.
*/
static class Range {
private final int start;
private final int len;
public Range (int start, int len) {
this.start = start;
this.len = len;
}
public final int getStart () {
return this.start;
}
public final int getLen () {
return this.len;
}
public final int getEnd () {
return this.start + this.len;
}
@Override
public String toString () {
return "Range: start: " + this.start + ", len: " + this.len;
}
}
/**
* Get the range of a particular index in a dictionary.
* @param index the start of the dictionary.
* @param id the index of the entry in the dictionary
* @return a range describing the offsets of the start and end of
* the entry from the start of the file, not the dictionary
*/
Range getIndexEntry (int index, int id) {
int hold = this.pos;
this.pos = index;
int count = readInt (2);
int encsz = readByte ();
if (encsz < 1 || encsz > 4) {
throw new RuntimeException ("Offsize: " + encsz +
", must be in range 1-4.");
}
this.pos += encsz * id;
int from = readInt (encsz);
Range r = new Range (from + 2 + index + encsz * (count + 1), readInt (
encsz) - from);
this.pos = hold;
return r;
}
// Top DICT: NAME CODE DEFAULT
// charstringtype 12 6 2
// fontmatrix 12 7 0.001 0 0 0.001
// charset 15 - (offset) names of glyphs (ref to name idx)
// encoding 16 - (offset) array of codes
// CharStrings 17 - (offset)
// Private 18 - (size, offset)
// glyph at position i in CharStrings has name charset[i]
// and code encoding[i]
int charstringtype = 2;
float temps[] = new float[32];
int charsetbase = 0;
int encodingbase = 0;
int charstringbase = 0;
int privatebase = 0;
int privatesize = 0;
int gsubrbase = 0;
int lsubrbase = 0;
int gsubrsoffset = 0;
int lsubrsoffset = 0;
int nglyphs = 1;
/**
* read a dictionary that exists within some range, parsing the entries
* within the dictionary.
*/
private void readDict (Range r) {
this.pos = r.getStart ();
while (this.pos < r.getEnd ()) {
int cmd = readCommand (false);
if (cmd == 1006) { // charstringtype, default=2
this.charstringtype = (int) this.stack[0];
} else if (cmd == 1007) { // fontmatrix
if (this.stackptr == 4) {
this.at = new AffineTransform (this.stack[0], this.stack[1],
this.stack[2], this.stack[3],
0, 0);
} else {
this.at = new AffineTransform (this.stack[0], this.stack[1],
this.stack[2], this.stack[3],
this.stack[4], this.stack[5]);
}
} else if (cmd == 15) { // charset
this.charsetbase = (int) this.stack[0];
} else if (cmd == 16) { // encoding
this.encodingbase = (int) this.stack[0];
} else if (cmd == 17) { // charstrings
this.charstringbase = (int) this.stack[0];
} else if (cmd == 18) { // private
this.privatesize = (int) this.stack[0];
this.privatebase = (int) this.stack[1];
} else if (cmd == 19) { // subrs (in Private dict)
this.lsubrbase = this.privatebase + (int) this.stack[0];
this.lsubrsoffset = calcoffset (this.lsubrbase);
}
this.stackptr = 0;
}
}
/**
* read a complete command. this may involve several numbers
* which go onto a stack before an actual command is read.
* @param charstring ????
* @return the command. Some numbers may also be on the stack.
*/
private int readCommand (boolean charstring) {
while (true) {
int t = readNext (charstring);
if (t == CMD) {
return this.num;
} else {
this.stack[this.stackptr++] = (t == NUM) ? (float) this.num : this.fnum;
}
}
}
/**
* parse information about the encoding of this file.
* @param base the start of the encoding data
*/
private void readEncodingData (int base) {
if (base == 0) { // this is the StandardEncoding
System.arraycopy (FontSupport.standardEncoding, 0, this.encoding, 0,
FontSupport.standardEncoding.length);
} else if (base == 1) { // this is the expert encoding
PDFDebugger.debug("**** EXPERT ENCODING not yet implemented!");
// TODO: copy ExpertEncoding
} else {
this.pos = base;
int encodingtype = readByte ();
if ((encodingtype & 127) == 0) {
int ncodes = readByte ();
for (int i = 1; i < ncodes + 1; i++) {
int idx = readByte () & 0xff;
this.encoding[idx] = i;
}
} else if ((encodingtype & 127) == 1) {
int nranges = readByte ();
int p = 1;
for (int i = 0; i < nranges; i++) {
int start = readByte ();
int more = readByte ();
for (int j = start; j < start + more + 1; j++) {
this.encoding[j] = p++;
}
}
} else {
PDFDebugger.debug("Bad encoding type: " + encodingtype);
}
// TODO: now check for supplemental encoding data
}
}
/**
* read the names of the glyphs.
* @param base the start of the glyph name table
*/
private void readGlyphNames (int base) {
if (base == 0) {
this.glyphnames = new int[229];
for (int i = 0; i < this.glyphnames.length; i++) {
this.glyphnames[i] = i;
}
return;
} else if (base == 1) {
this.glyphnames = FontSupport.type1CExpertCharset;
return;
} else if (base == 2) {
this.glyphnames = FontSupport.type1CExpertSubCharset;
return;
}
// nglyphs has already been set.
this.glyphnames = new int[this.nglyphs];
this.glyphnames[0] = 0;
this.pos = base;
int t = readByte ();
if (t == 0) {
for (int i = 1; i < this.nglyphs; i++) {
this.glyphnames[i] = readInt (2);
}
} else if (t == 1) {
int n = 1;
while (n < this.nglyphs) {
int sid = readInt (2);
int range = readByte () + 1;
for (int i = 0; i < range; i++) {
this.glyphnames[n++] = sid++;
}
}
} else if (t == 2) {
int n = 1;
while (n < this.nglyphs) {
int sid = readInt (2);
int range = readInt (2) + 1;
for (int i = 0; i < range; i++) {
this.glyphnames[n++] = sid++;
}
}
}
}
/**
* read a list of names
* @param base the start of the name table
*/
private void readNames (int base) {
this.pos = base;
int nextra = readInt (2);
this.names = new String[nextra];
// safenames= new String[nextra];
for (int i = 0; i < nextra; i++) {
Range r = getIndexEntry (base, i);
this.names[i] = new String (this.data, r.getStart (), r.getLen ());
PDFDebugger.debug("Read name: "+i+" from "+r.getStart()+" to "+r.getEnd()+": "+safe(names[i]), 1000);
}
}
/**
* parse the font data.
* @param encdif a dictionary describing the encoding.
*/
private void parse () throws IOException {
int majorVersion = readByte ();
int minorVersion = readByte ();
int hdrsz = readByte ();
int offsize = readByte ();
// jump over rest of header: base of font names index
int fnames = hdrsz;
// offset in the file of the array of font dicts
int topdicts = fnames + getIndexSize (fnames);
// offset in the file of local names
int theNames = topdicts + getIndexSize (topdicts);
// offset in the file of the array of global subroutines
this.gsubrbase = theNames + getIndexSize (theNames);
this.gsubrsoffset = calcoffset (this.gsubrbase);
// read extra names
readNames (theNames);
// does this file have more than one font?
this.pos = topdicts;
if (readInt (2) != 1) {
printData ();
throw new RuntimeException ("More than one font in this file!");
}
Range r = getIndexEntry (fnames, 0);
this.fontname = new String (this.data, r.getStart (), r.getLen ());
// read first dict
readDict (getIndexEntry (topdicts, 0));
// read the private dictionary
readDict (new Range (this.privatebase, this.privatesize));
// calculate the number of glyphs
this.pos = this.charstringbase;
this.nglyphs = readInt (2);
// now get the glyph names
readGlyphNames (this.charsetbase);
// now figure out the encoding
readEncodingData (this.encodingbase);
}
/**
* get the index of a particular name. The name table starts with
* the standard names in FontSupport.stdNames, and is appended by
* any names in the name table from this font's dictionary.
*/
private int getNameIndex (String name) {
int val = FontSupport.findName (name, FontSupport.stdNames);
if (val == -1) {
val = FontSupport.findName (name, this.names) + FontSupport.stdNames.length;
}
if (val == -1) {
val = 0;
}
return val;
}
/**
* convert a string to one in which any non-printable bytes are
* replaced by "<###>" where ## is the value of the byte.
*/
private String safe (String src) {
StringBuffer sb = new StringBuffer ();
for (int i = 0; i < src.length (); i++) {
char c = src.charAt (i);
if (c >= 32 && c < 128) {
sb.append (c);
} else {
sb.append ("<" + (int) c + ">");
}
}
return sb.toString ();
}
/**
* Read the data for a glyph from the glyph table, and transform
* it based on the current transform.
*
* @param base the start of the glyph table
* @param offset the index of this glyph in the glyph table
*/
private synchronized GeneralPath readGlyph (int base, int offset) {
FlPoint pt = new FlPoint ();
// find this entry
Range r = getIndexEntry (base, offset);
// create a path
GeneralPath gp = new GeneralPath ();
// rember the start position (for recursive calls due to seac)
int hold = this.pos;
// read the glyph itself
this.stackptr = 0;
this.stemhints = 0;
parseGlyph (r, gp, pt);
// restore the start position
this.pos = hold;
gp.transform (this.at);
return gp;
}
/**
* calculate an offset code for a dictionary. Uses the count of entries
* to determine what the offset should be.
*
* @param base the index of the start of the dictionary
*/
public int calcoffset (int base) {
int len = getTableLength (base);
if (len < 1240) {
return 107;
} else if (len < 33900) {
return 1131;
} else {
return 32768;
}
}
/**
* get the name associated with an ID.
* @param id the index of the name
* @return the name from the FontSupport.stdNames table augmented
* by the local name table
*/
public String getSID (int id) {
if (id < FontSupport.stdNames.length) {
return FontSupport.stdNames[id];
} else {
id -= FontSupport.stdNames.length;
return this.names[id];
}
}
/**
* 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 b the index of the base glyph
* @param a the index of the accent glyph
* @param gp the GeneralPath into which the combined glyph will be
* written.
*/
private void buildAccentChar (float x, float y, char b, char a,
GeneralPath gp) {
// get the outline of the accent
GeneralPath pathA = getOutline (a, getWidth (a, null));
// undo the effect of the transform applied in read
AffineTransform xformA = AffineTransform.getTranslateInstance (x, y);
try {
xformA.concatenate (this.at.createInverse ());
} catch (NoninvertibleTransformException nte) {
// oh well ...
}
pathA.transform (xformA);
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);
}
/**
* parse a glyph defined in a particular range
* @param r the range of the glyph definition
* @param gp a GeneralPath in which to store the glyph outline
* @param pt a FlPoint representing the end of the current path
*/
void parseGlyph (Range r, GeneralPath gp, FlPoint pt) {
this.pos = r.getStart ();
int i;
float x1, y1, x2, y2, x3, y3, ybase;
int hold;
while (this.pos < r.getEnd ()) {
int cmd = readCommand (true);
hold = 0;
switch (cmd) {
case 1: // hstem
case 3: // vstem
this.stackptr = 0;
break;
case 4: // vmoveto
if (this.stackptr > 1) { // this is the first call, arg1 is width
this.stack[0] = this.stack[1];
}
pt.y += this.stack[0];
if (pt.open) {
gp.closePath ();
}
pt.open = false;
gp.moveTo (pt.x, pt.y);
this.stackptr = 0;
break;
case 5: // rlineto
for (i = 0; i < this.stackptr;) {
pt.x += this.stack[i++];
pt.y += this.stack[i++];
gp.lineTo (pt.x, pt.y);
}
pt.open = true;
this.stackptr = 0;
break;
case 6: // hlineto
for (i = 0; i < this.stackptr;) {
if ((i & 1) == 0) {
pt.x += this.stack[i++];
} else {
pt.y += this.stack[i++];
}
gp.lineTo (pt.x, pt.y);
}
pt.open = true;
this.stackptr = 0;
break;
case 7: // vlineto
for (i = 0; i < this.stackptr;) {
if ((i & 1) == 0) {
pt.y += this.stack[i++];
} else {
pt.x += this.stack[i++];
}
gp.lineTo (pt.x, pt.y);
}
pt.open = true;
this.stackptr = 0;
break;
case 8: // rrcurveto
for (i = 0; i < this.stackptr;) {
x1 = pt.x + this.stack[i++];
y1 = pt.y + this.stack[i++];
x2 = x1 + this.stack[i++];
y2 = y1 + this.stack[i++];
pt.x = x2 + this.stack[i++];
pt.y = y2 + this.stack[i++];
gp.curveTo (x1, y1, x2, y2, pt.x, pt.y);
}
pt.open = true;
this.stackptr = 0;
break;
case 10: // callsubr
hold = this.pos;
i = (int) this.stack[--this.stackptr] + this.lsubrsoffset;
Range lsubr = getIndexEntry (this.lsubrbase, i);
parseGlyph (lsubr, gp, pt);
this.pos = hold;
break;
case 11: // return
return;
case 14: // endchar
// width x y achar bchar endchar == x y achar bchar seac
if (this.stackptr == 5) {
buildAccentChar (this.stack[1], this.stack[2], (char) this.stack[3],
(char) this.stack[4], gp);
} else if (this.stackptr == 4) {
// see page 58 on specification 5177.Type2.pdf which indicates that
// these parameters are valid for Type1C as the width is optional
buildAccentChar(this.stack[0], this.stack[1], (char) this.stack[2], (char) this.stack[3], gp);
}
if (pt.open) {
gp.closePath ();
}
pt.open = false;
this.stackptr = 0;
stemhints = 0;
break;
case 18: // hstemhm
stemhints += (this.stackptr) / 2;
this.stackptr = 0;
break;
case 19: // hintmask
case 20: // cntrmask
stemhints += (this.stackptr) / 2;
this.pos += (stemhints - 1) / 8 + 1;
this.stackptr = 0;
break;
case 21: // rmoveto
if (this.stackptr > 2) {
this.stack[0] = this.stack[1];
this.stack[1] = this.stack[2];
}
pt.x += this.stack[0];
pt.y += this.stack[1];
if (pt.open) {
gp.closePath ();
}
gp.moveTo (pt.x, pt.y);
pt.open = false;
this.stackptr = 0;
break;
case 22: // hmoveto
if (this.stackptr > 1) {
this.stack[0] = this.stack[1];
}
pt.x += this.stack[0];
if (pt.open) {
gp.closePath ();
}
gp.moveTo (pt.x, pt.y);
pt.open = false;
this.stackptr = 0;
break;
case 23: // vstemhm
stemhints += (this.stackptr) / 2;
this.stackptr = 0;
break;
case 24: // rcurveline
for (i = 0; i < this.stackptr - 2;) {
x1 = pt.x + this.stack[i++];
y1 = pt.y + this.stack[i++];
x2 = x1 + this.stack[i++];
y2 = y1 + this.stack[i++];
pt.x = x2 + this.stack[i++];
pt.y = y2 + this.stack[i++];
gp.curveTo (x1, y1, x2, y2, pt.x, pt.y);
}
pt.x += this.stack[i++];
pt.y += this.stack[i++];
gp.lineTo (pt.x, pt.y);
pt.open = true;
this.stackptr = 0;
break;
case 25: // rlinecurve
for (i = 0; i < this.stackptr - 6;) {
pt.x += this.stack[i++];
pt.y += this.stack[i++];
gp.lineTo (pt.x, pt.y);
}
x1 = pt.x + this.stack[i++];
y1 = pt.y + this.stack[i++];
x2 = x1 + this.stack[i++];
y2 = y1 + this.stack[i++];
pt.x = x2 + this.stack[i++];
pt.y = y2 + this.stack[i++];
gp.curveTo (x1, y1, x2, y2, pt.x, pt.y);
pt.open = true;
this.stackptr = 0;
break;
case 26: // vvcurveto
i = 0;
if ((this.stackptr & 1) == 1) { // odd number of arguments
pt.x += this.stack[i++];
}
while (i < this.stackptr) {
x1 = pt.x;
y1 = pt.y + this.stack[i++];
x2 = x1 + this.stack[i++];
y2 = y1 + this.stack[i++];
pt.x = x2;
pt.y = y2 + this.stack[i++];
gp.curveTo (x1, y1, x2, y2, pt.x, pt.y);
}
pt.open = true;
this.stackptr = 0;
break;
case 27: // hhcurveto
i = 0;
if ((this.stackptr & 1) == 1) { // odd number of arguments
pt.y += this.stack[i++];
}
while (i < this.stackptr) {
x1 = pt.x + this.stack[i++];
y1 = pt.y;
x2 = x1 + this.stack[i++];
y2 = y1 + this.stack[i++];
pt.x = x2 + this.stack[i++];
pt.y = y2;
gp.curveTo (x1, y1, x2, y2, pt.x, pt.y);
}
pt.open = true;
this.stackptr = 0;
break;
case 29: // callgsubr
hold = this.pos;
i = (int) this.stack[--this.stackptr] + this.gsubrsoffset;
Range gsubr = getIndexEntry (this.gsubrbase, i);
parseGlyph (gsubr, gp, pt);
this.pos = hold;
break;
case 30: // vhcurveto
hold = 4;
case 31: // hvcurveto
for (i = 0; i < this.stackptr;) {
boolean hv = (((i + hold) & 4) == 0);
x1 = pt.x + (hv ? this.stack[i++] : 0);
y1 = pt.y + (hv ? 0 : this.stack[i++]);
x2 = x1 + this.stack[i++];
y2 = y1 + this.stack[i++];
pt.x = x2 + (hv ? 0 : this.stack[i++]);
pt.y = y2 + (hv ? this.stack[i++] : 0);
if (i == this.stackptr - 1) {
if (hv) {
pt.x += this.stack[i++];
} else {
pt.y += this.stack[i++];
}
}
gp.curveTo (x1, y1, x2, y2, pt.x, pt.y);
}
pt.open = true;
this.stackptr = 0;
break;
case 1000: // old dotsection command. ignore.
this.stackptr = 0;
break;
case 1003: // and
x1 = this.stack[--this.stackptr];
y1 = this.stack[--this.stackptr];
this.stack[this.stackptr++] = ((x1 != 0) && (y1 != 0)) ? 1 : 0;
break;
case 1004: // or
x1 = this.stack[--this.stackptr];
y1 = this.stack[--this.stackptr];
this.stack[this.stackptr++] = ((x1 != 0) || (y1 != 0)) ? 1 : 0;
break;
case 1005: // not
x1 = this.stack[--this.stackptr];
this.stack[this.stackptr++] = (x1 == 0) ? 1 : 0;
break;
case 1009: // abs
this.stack[this.stackptr - 1] = Math.abs (this.stack[this.stackptr - 1]);
break;
case 1010: // add
x1 = this.stack[--this.stackptr];
y1 = this.stack[--this.stackptr];
this.stack[this.stackptr++] = x1 + y1;
break;
case 1011: // sub
x1 = this.stack[--this.stackptr];
y1 = this.stack[--this.stackptr];
this.stack[this.stackptr++] = y1 - x1;
break;
case 1012: // div
x1 = this.stack[--this.stackptr];
y1 = this.stack[--this.stackptr];
this.stack[this.stackptr++] = y1 / x1;
break;
case 1014: // neg
this.stack[this.stackptr - 1] = -this.stack[this.stackptr - 1];
break;
case 1015: // eq
x1 = this.stack[--this.stackptr];
y1 = this.stack[--this.stackptr];
this.stack[this.stackptr++] = (x1 == y1) ? 1 : 0;
break;
case 1018: // drop
this.stackptr--;
break;
case 1020: // put
i = (int) this.stack[--this.stackptr];
x1 = this.stack[--this.stackptr];
this.temps[i] = x1;
break;
case 1021: // get
i = (int) this.stack[--this.stackptr];
this.stack[this.stackptr++] = this.temps[i];
break;
case 1022: // ifelse
if (this.stack[this.stackptr - 2] > this.stack[this.stackptr - 1]) {
this.stack[this.stackptr - 4] = this.stack[this.stackptr - 3];
}
this.stackptr -= 3;
break;
case 1023: // random
this.stack[this.stackptr++] = (float) Math.random ();
break;
case 1024: // mul
x1 = this.stack[--this.stackptr];
y1 = this.stack[--this.stackptr];
this.stack[this.stackptr++] = y1 * x1;
break;
case 1026: // sqrt
this.stack[this.stackptr - 1] = (float) Math.sqrt (this.stack[this.stackptr - 1]);
break;
case 1027: // dup
x1 = this.stack[this.stackptr - 1];
this.stack[this.stackptr++] = x1;
break;
case 1028: // exch
x1 = this.stack[this.stackptr - 1];
this.stack[this.stackptr - 1] = this.stack[this.stackptr - 2];
this.stack[this.stackptr - 2] = x1;
break;
case 1029: // index
i = (int) this.stack[this.stackptr - 1];
if (i < 0) {
i = 0;
}
this.stack[this.stackptr - 1] = this.stack[this.stackptr - 2 - i];
break;
case 1030: // roll
i = (int) this.stack[--this.stackptr];
int n = (int) this.stack[--this.stackptr];
// roll n number by i (+ = upward)
if (i > 0) {
i = i % n;
} else {
i = n - (-i % n);
}
// x x x x i y y y -> y y y x x x x i (where i=3)
if (i > 0) {
float roll[] = new float[n];
System.arraycopy (this.stack, this.stackptr - 1 - i, roll, 0, i);
System.arraycopy (this.stack, this.stackptr - 1 - n, roll, i,
n - i);
System.arraycopy (roll, 0, this.stack, this.stackptr - 1 - n, n);
}
break;
case 1034: // hflex
x1 = pt.x + this.stack[0];
y1 = ybase = pt.y;
x2 = x1 + this.stack[1];
y2 = y1 + this.stack[2];
pt.x = x2 + this.stack[3];
pt.y = y2;
gp.curveTo (x1, y1, x2, y2, pt.x, pt.y);
x1 = pt.x + this.stack[4];
y1 = pt.y;
x2 = x1 + this.stack[5];
y2 = ybase;
pt.x = x2 + this.stack[6];
pt.y = y2;
gp.curveTo (x1, y1, x2, y2, pt.x, pt.y);
pt.open = true;
this.stackptr = 0;
break;
case 1035: // flex
x1 = pt.x + this.stack[0];
y1 = pt.y + this.stack[1];
x2 = x1 + this.stack[2];
y2 = y1 + this.stack[3];
pt.x = x2 + this.stack[4];
pt.y = y2 + this.stack[5];
gp.curveTo (x1, y1, x2, y2, pt.x, pt.y);
x1 = pt.x + this.stack[6];
y1 = pt.y + this.stack[7];
x2 = x1 + this.stack[8];
y2 = y1 + this.stack[9];
pt.x = x2 + this.stack[10];
pt.y = y2 + this.stack[11];
gp.curveTo (x1, y1, x2, y2, pt.x, pt.y);
pt.open = true;
this.stackptr = 0;
break;
case 1036: // hflex1
ybase = pt.y;
x1 = pt.x + this.stack[0];
y1 = pt.y + this.stack[1];
x2 = x1 + this.stack[2];
y2 = y1 + this.stack[3];
pt.x = x2 + this.stack[4];
pt.y = y2;
gp.curveTo (x1, y1, x2, y2, pt.x, pt.y);
x1 = pt.x + this.stack[5];
y1 = pt.y;
x2 = x1 + this.stack[6];
y2 = y1 + this.stack[7];
pt.x = x2 + this.stack[8];
pt.y = ybase;
gp.curveTo (x1, y1, x2, y2, pt.x, pt.y);
pt.open = true;
this.stackptr = 0;
break;
case 1037: // flex1
ybase = pt.y;
float xbase = pt.x;
x1 = pt.x + this.stack[0];
y1 = pt.y + this.stack[1];
x2 = x1 + this.stack[2];
y2 = y1 + this.stack[3];
pt.x = x2 + this.stack[4];
pt.y = y2 + this.stack[5];
gp.curveTo (x1, y1, x2, y2, pt.x, pt.y);
x1 = pt.x + this.stack[6];
y1 = pt.y + this.stack[7];
x2 = x1 + this.stack[8];
y2 = y1 + this.stack[9];
if (Math.abs (x2 - xbase) > Math.abs (y2 - ybase)) {
pt.x = x2 + this.stack[10];
pt.y = ybase;
} else {
pt.x = xbase;
pt.y = y2 + this.stack[10];
}
gp.curveTo (x1, y1, x2, y2, pt.x, pt.y);
pt.open = true;
this.stackptr = 0;
break;
default:
PDFDebugger.debug("ERROR! TYPE1C CHARSTRING CMD IS " + cmd);
break;
}
}
}
/**
* 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) {
// first find the index of this name
int index = getNameIndex (name);
// now find the glyph with that name
for (int i = 0; i < this.glyphnames.length; i++) {
if (this.glyphnames[i] == index) {
return readGlyph (this.charstringbase, i);
}
}
// not found -- return the unknown glyph
return readGlyph (this.charstringbase, 0);
}
/**
* 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) {
// ignore high bits
int index = (src & 0xff);
// if we use a standard encoding, the mapping is from glyph to SID
// therefore we must find the glyph index in the name table
if (this.encodingbase == 0 || this.encodingbase == 1) {
for (int i = 0; i < this.glyphnames.length; i++) {
if (this.glyphnames[i] == this.encoding[index]) {
return readGlyph (this.charstringbase, i);
}
}
} else {
// for a custom encoding, the mapping is from glyph to GID, so
// we can just map the glyph directly
if (index > 0 && index < this.encoding.length) {
return readGlyph (this.charstringbase, this.encoding[index]);
}
}
// for some reason the glyph was not found, return the empty glyph
return readGlyph (this.charstringbase, 0);
}
}