001: /*
002: * $Id: Type1Font.java,v 1.2 2007/12/20 18:33:31 rbair Exp $
003: *
004: * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005: * Santa Clara, California 95054, U.S.A. All rights reserved.
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
020: */
021:
022: package com.sun.pdfview.font;
023:
024: import java.awt.geom.AffineTransform;
025: import java.awt.geom.GeneralPath;
026: import java.awt.geom.NoninvertibleTransformException;
027: import java.awt.geom.Point2D;
028: import java.io.IOException;
029: import java.util.HashMap;
030: import java.util.Map;
031: import java.util.TreeMap;
032:
033: import com.sun.pdfview.PDFFile;
034: import com.sun.pdfview.PDFObject;
035:
036: /**
037: * A representation, with parser, of an Adobe Type 1 font.
038: * @author Mike Wessler
039: */
040: public class Type1Font extends OutlineFont {
041: String chr2name[];
042: int password;
043: byte[] subrs[];
044:
045: int lenIV;
046:
047: Map name2outline;
048: Map name2width;
049:
050: AffineTransform at;
051:
052: /** the Type1 stack of command values */
053: float stack[] = new float[100];
054: /** the current position in the Type1 stack */
055: int sloc = 0;
056:
057: /** the stack of postscript commands (used by callothersubr) */
058: float psStack[] = new float[3];
059: /** the current position in the postscript stack */
060: int psLoc = 0;
061:
062: /**
063: * create a new Type1Font based on a font data stream and an encoding.
064: * @param baseName the postscript name of this font
065: * @param src the Font object as a stream with a dictionary
066: * @param descriptor the descriptor for this font
067: */
068: public Type1Font(String baseName, PDFObject src,
069: PDFFontDescriptor descriptor) throws IOException {
070: super (baseName, src, descriptor);
071:
072: if (descriptor != null && descriptor.getFontFile() != null) {
073: // parse that file, filling name2outline and chr2name
074: int start = descriptor.getFontFile().getDictRef("Length1")
075: .getIntValue();
076: int len = descriptor.getFontFile().getDictRef("Length2")
077: .getIntValue();
078: byte font[] = descriptor.getFontFile().getStream();
079:
080: parseFont(font, start, len);
081: }
082: }
083:
084: /** Read a font from it's data, start position and length */
085: protected void parseFont(byte[] font, int start, int len) {
086: name2width = new HashMap();
087:
088: byte data[] = null;
089:
090: if (isASCII(font, start)) {
091: byte[] bData = readASCII(font, start, start + len);
092: data = decrypt(bData, 0, bData.length, 55665, 4);
093: } else {
094: data = decrypt(font, start, start + len, 55665, 4);
095: }
096:
097: // encoding is in cleartext area
098: chr2name = readEncoding(font);
099: int lenIVLoc = findSlashName(data, "lenIV");
100: PSParser psp = new PSParser(data, 0);
101: if (lenIVLoc < 0) {
102: lenIV = 4;
103: } else {
104: psp.setLoc(lenIVLoc + 6);
105: lenIV = Integer.parseInt(psp.readThing());
106: }
107: password = 4330;
108: int matrixloc = findSlashName(font, "FontMatrix");
109: if (matrixloc < 0) {
110: System.out.println("No FontMatrix!");
111: at = new AffineTransform(0.001f, 0, 0, 0.001f, 0, 0);
112: } else {
113: PSParser psp2 = new PSParser(font, matrixloc + 11);
114: // read [num num num num num num]
115: float xf[] = psp2.readArray(6);
116: // System.out.println("FONT MATRIX: "+xf);
117: at = new AffineTransform(xf);
118: }
119:
120: subrs = readSubrs(data);
121: name2outline = new TreeMap(readChars(data));
122: // at this point, name2outline holds name -> byte[].
123: }
124:
125: /**
126: * parse the encoding portion of the font definition
127: * @param d the font definition stream
128: * @return an array of the glyphs corresponding to each byte
129: */
130: private String[] readEncoding(byte[] d) {
131: byte[][] ary = readArray(d, "Encoding", "def");
132: String res[] = new String[256];
133: for (int i = 0; i < ary.length; i++) {
134: if (ary[i] != null) {
135: if (ary[i][0] == '/') {
136: res[i] = new String(ary[i]).substring(1);
137: } else {
138: res[i] = new String(ary[i]);
139: }
140: } else {
141: res[i] = null;
142: }
143: }
144: return res;
145: }
146:
147: /**
148: * read the subroutines out of the font definition
149: * @param d the font definition stream
150: * @return an array of the subroutines, each as a byte array.
151: */
152: private byte[][] readSubrs(byte[] d) {
153: return readArray(d, "Subrs", "index");
154: }
155:
156: /**
157: * read a named array out of the font definition.
158: * <p>
159: * this function attempts to parse an array out of a postscript
160: * definition without doing any postscript. It's actually looking
161: * for things that look like "dup <i>id</i> <i>elt</i> put", and
162: * placing the <i>elt</i> at the <i>i</i>th position in the array.
163: * @param d the font definition stream
164: * @param key the name of the array
165: * @param end a string that appears at the end of the array
166: * @return an array consisting of a byte array for each entry
167: */
168: private byte[][] readArray(byte[] d, String key, String end) {
169: int i = findSlashName(d, key);
170: if (i < 0) {
171: // not found.
172: return new byte[0][];
173: }
174: // now find things that look like "dup id elt put"
175: // end at "def"
176: PSParser psp = new PSParser(d, i);
177: psp.readThing(); // read the key (i is the start of the key)
178: double val;
179: String type = psp.readThing();
180: if (type.equals("StandardEncoding")) {
181: byte[] stdenc[] = new byte[FontSupport.standardEncoding.length][];
182: for (i = 0; i < stdenc.length; i++) {
183: stdenc[i] = FontSupport.getName(
184: FontSupport.standardEncoding[i]).getBytes();
185: }
186: return stdenc;
187: }
188: int len = Integer.parseInt(type);
189: byte[] out[] = new byte[len][];
190: byte[] line;
191: while (true) {
192: String s = psp.readThing();
193: if (s.equals("dup")) {
194: int id = Integer.parseInt(psp.readThing());
195: String elt = psp.readThing();
196: line = elt.getBytes();
197: if (Character.isDigit(elt.charAt(0))) {
198: int hold = Integer.parseInt(elt);
199: String special = psp.readThing();
200: if (special.equals("-|") || special.equals("RD")) {
201: psp.setLoc(psp.getLoc() + 1);
202: line = psp.getNEncodedBytes(hold, password,
203: lenIV);
204: }
205: }
206: out[id] = line;
207: } else if (s.equals(end)) {
208: break;
209: }
210: }
211: return out;
212: }
213:
214: /**
215: * decrypt an array using the Adobe Type 1 Font decryption algorithm.
216: * @param d the input array of bytes
217: * @param start where in the array to start decoding
218: * @param end where in the array to stop decoding
219: * @param key the decryption key
220: * @param skip how many bytes to skip initially
221: * @return the decrypted bytes. The length of this array will be
222: * (start-end-skip) bytes long
223: */
224: private byte[] decrypt(byte[] d, int start, int end, int key,
225: int skip) {
226: if (end - start - skip < 0) {
227: skip = 0;
228: }
229: byte[] o = new byte[end - start - skip];
230: int r = key;
231: int ipos;
232: int c1 = 52845;
233: int c2 = 22719;
234: for (ipos = start; ipos < end; ipos++) {
235: int c = d[ipos] & 0xff;
236: int p = (c ^ (r >> 8)) & 0xff;
237: r = ((c + r) * c1 + c2) & 0xffff;
238: if (ipos - start - skip >= 0) {
239: o[ipos - start - skip] = (byte) p;
240: }
241: }
242: return o;
243: }
244:
245: /**
246: * Read data formatted as ASCII strings as binary data
247: *
248: * @param data the data, formatted as ASCII strings
249: * @param start where in the array to start decrypting
250: * @param end where in the array to stop decrypting
251: */
252: private byte[] readASCII(byte[] data, int start, int end) {
253: // each byte of output is derived from one character (two bytes) of
254: // input
255: byte[] o = new byte[(end - start) / 2];
256:
257: int count = 0;
258: int bit = 0;
259:
260: for (int loc = start; loc < end; loc++) {
261: char c = (char) (data[loc] & 0xff);
262: byte b = (byte) 0;
263:
264: if (c >= '0' && c <= '9') {
265: b = (byte) (c - '0');
266: } else if (c >= 'a' && c <= 'f') {
267: b = (byte) (10 + (c - 'a'));
268: } else if (c >= 'A' && c <= 'F') {
269: b = (byte) (10 + (c - 'A'));
270: } else {
271: // linefeed or something. Skip.
272: continue;
273: }
274:
275: // which half of the byte are we?
276: if ((bit++ % 2) == 0) {
277: o[count] = (byte) (b << 4);
278: } else {
279: o[count++] |= b;
280: }
281: }
282:
283: return o;
284: }
285:
286: /**
287: * Determine if data is in ASCII or binary format. According to the spec,
288: * if any of the first 4 bytes are not character codes ('0' - '9' or
289: * 'A' - 'F' or 'a' - 'f'), then the data is binary. Otherwise it is
290: * ASCII
291: */
292: private boolean isASCII(byte[] data, int start) {
293: // look at the first 4 bytes
294: for (int i = start; i < start + 4; i++) {
295: // get the byte as a character
296: char c = (char) (data[i] & 0xff);
297:
298: if (c >= '0' && c <= '9') {
299: continue;
300: } else if (c >= 'a' && c <= 'f') {
301: continue;
302: } else if (c >= 'A' && c <= 'F') {
303: continue;
304: } else {
305: // out of range
306: return false;
307: }
308: }
309:
310: // all were in range, so it is ASCII
311: return true;
312: }
313:
314: /**
315: * PostScript reader (not a parser, as the name would seem to indicate).
316: */
317: class PSParser {
318: byte[] data;
319: int loc;
320:
321: /**
322: * create a PostScript reader given some data and an initial offset
323: * into that data.
324: * @param data the bytes of the postscript information
325: * @param start an initial offset into the data
326: */
327: public PSParser(byte[] data, int start) {
328: this .data = data;
329: this .loc = start;
330: }
331:
332: /**
333: * get the next postscript "word". This is basically the next
334: * non-whitespace block between two whitespace delimiters.
335: * This means that something like " [2 4 53]" will produce
336: * three items, while " [2 4 56 ]" will produce four.
337: */
338: public String readThing() {
339: // skip whitespace
340: while (PDFFile.isWhiteSpace(data[loc])) {
341: loc++;
342: }
343: // read thing
344: int start = loc;
345: while (!PDFFile.isWhiteSpace(data[loc])) {
346: loc++;
347: }
348: String s = new String(data, start, loc - start);
349: // System.out.println("Read: "+s);
350: return s;
351: }
352:
353: /**
354: * read a set of numbers from the input. This method doesn't
355: * pay any attention to "[" or "]" delimiters, and reads any
356: * non-numeric items as the number 0.
357: * @param count the number of items to read
358: * @return an array of count floats
359: */
360: public float[] readArray(int count) {
361: float[] ary = new float[count];
362: int idx = 0;
363: while (idx < count) {
364: String thing = readThing();
365: if (thing.charAt(0) == '[') {
366: thing = thing.substring(1);
367: }
368: if (thing.endsWith("]")) {
369: thing = thing.substring(0, thing.length() - 1);
370: }
371: ary[idx++] = Float.valueOf(thing).floatValue();
372: }
373: return ary;
374: }
375:
376: /**
377: * get the current location within the input stream
378: */
379: public int getLoc() {
380: return loc;
381: }
382:
383: /**
384: * set the current location within the input stream
385: */
386: public void setLoc(int loc) {
387: this .loc = loc;
388: }
389:
390: /**
391: * treat the next n bytes of the input stream as encoded
392: * information to be decrypted.
393: * @param n the number of bytes to decrypt
394: * @param key the decryption key
395: * @param skip the number of bytes to skip at the beginning of the
396: * decryption
397: * @return an array of decrypted bytes. The length of the array
398: * will be n-skip.
399: */
400: public byte[] getNEncodedBytes(int n, int key, int skip) {
401: byte[] result = decrypt(data, loc, loc + n, key, skip);
402: loc += n;
403: return result;
404: }
405: }
406:
407: /**
408: * get the index into the byte array of a slashed name, like "/name".
409: * @param d the search array
410: * @param name the name to look for, without the initial /
411: * @return the index of the first occurance of /name in the array.
412: */
413: private int findSlashName(byte[] d, String name) {
414: int i;
415: for (i = 0; i < d.length; i++) {
416: if (d[i] == '/') {
417: // check for key
418: boolean found = true;
419: for (int j = 0; j < name.length(); j++) {
420: if (d[i + j + 1] != name.charAt(j)) {
421: found = false;
422: break;
423: }
424: }
425: if (found) {
426: return i;
427: }
428: }
429: }
430: return -1;
431: }
432:
433: /**
434: * get the character definitions of the font.
435: * @param d the font data
436: * @return a HashMap that maps string glyph names to byte arrays of
437: * decoded font data.
438: */
439: private HashMap readChars(byte[] d) {
440: // skip thru data until we find "/"+key
441: HashMap hm = new HashMap();
442: int i = findSlashName(d, "CharStrings");
443: if (i < 0) {
444: // not found
445: return hm;
446: }
447: PSParser psp = new PSParser(d, i);
448: // read /name len -| [len bytes] |-
449: // until "end"
450: while (true) {
451: String s = psp.readThing();
452: char c = s.charAt(0);
453: if (c == '/') {
454: int len = Integer.parseInt(psp.readThing());
455: String go = psp.readThing(); // it's -| or RD
456: if (go.equals("-|") || go.equals("RD")) {
457: psp.setLoc(psp.getLoc() + 1);
458: byte[] line = psp.getNEncodedBytes(len, password,
459: lenIV);
460: hm.put(s.substring(1), line);
461: }
462: } else if (s.equals("end")) {
463: break;
464: }
465: }
466: return hm;
467: }
468:
469: /**
470: * pop the next item off the stack
471: */
472: private float pop() {
473: float val = 0;
474: if (sloc > 0) {
475: val = stack[--sloc];
476: }
477: return val;
478: }
479:
480: int callcount = 0;
481:
482: /**
483: * parse glyph data into a GeneralPath, and return the advance width.
484: * The working point is passed in as a parameter in order to allow
485: * recursion.
486: * @param cs the decrypted glyph data
487: * @param gp a GeneralPath into which the glyph shape will be stored
488: * @param pt a FlPoint object that will be used to generate the path
489: * @param wid a FlPoint into which the advance width will be placed.
490: */
491: private void parse(byte[] cs, GeneralPath gp, FlPoint pt,
492: FlPoint wid) {
493: // System.out.println("--- cmd length is "+cs.length);
494: int loc = 0;
495: float x1, x2, x3, y1, y2, y3;
496: while (loc < cs.length) {
497: int v = ((int) cs[loc++]) & 0xff;
498: if (v == 255) {
499: stack[sloc++] = ((((int) cs[loc]) & 0xff) << 24)
500: + ((((int) cs[loc + 1]) & 0xff) << 16)
501: + ((((int) cs[loc + 2]) & 0xff) << 8)
502: + ((((int) cs[loc + 3]) & 0xff));
503: loc += 4;
504: // System.out.println("Pushed long "+stack[sloc-1]);
505: } else if (v >= 251) {
506: stack[sloc++] = -((v - 251) << 8)
507: - (((int) cs[loc]) & 0xff) - 108;
508: loc++;
509: // System.out.println("Pushed lo "+stack[sloc-1]);
510: } else if (v >= 247) {
511: stack[sloc++] = ((v - 247) << 8)
512: + (((int) cs[loc]) & 0xff) + 108;
513: loc++;
514: // System.out.println("Pushed hi "+stack[sloc-1]);
515: } else if (v >= 32) {
516: stack[sloc++] = v - 139;
517: // System.out.println("Pushed "+stack[sloc-1]);
518: } else {
519: // System.out.println("CMD: "+v+" (stack is size "+sloc+")");
520: switch (v) {
521: case 0: // x
522: throw new RuntimeException("Bad command (" + v
523: + ")");
524: case 1: // hstem
525: sloc = 0;
526: break;
527: case 2: // x
528: throw new RuntimeException("Bad command (" + v
529: + ")");
530: case 3: // vstem
531: sloc = 0;
532: break;
533: case 4: // y vmoveto
534: pt.y += pop();
535: gp.moveTo(pt.x, pt.y);
536: sloc = 0;
537: break;
538: case 5: // x y rlineto
539: pt.y += pop();
540: pt.x += pop();
541: gp.lineTo(pt.x, pt.y);
542: sloc = 0;
543: break;
544: case 6: // x hlineto
545: pt.x += pop();
546: gp.lineTo(pt.x, pt.y);
547: sloc = 0;
548: break;
549: case 7: // y vlineto
550: pt.y += pop();
551: gp.lineTo(pt.x, pt.y);
552: sloc = 0;
553: break;
554: case 8: // x1 y1 x2 y2 x3 y3 rcurveto
555: y3 = pop();
556: x3 = pop();
557: y2 = pop();
558: x2 = pop();
559: y1 = pop();
560: x1 = pop();
561: gp.curveTo(pt.x + x1, pt.y + y1, pt.x + x1 + x2,
562: pt.y + y1 + y2, pt.x + x1 + x2 + x3, pt.y
563: + y1 + y2 + y3);
564: pt.x += x1 + x2 + x3;
565: pt.y += y1 + y2 + y3;
566: sloc = 0;
567: break;
568: case 9: // closepath
569: gp.closePath();
570: sloc = 0;
571: break;
572: case 10: // n callsubr
573: int n = (int) pop();
574: if (subrs[n] == null) {
575: System.out.println("No subroutine #" + n);
576: } else {
577: callcount++;
578: if (callcount > 10) {
579: System.out.println("Call stack too large");
580: // throw new RuntimeException("Call stack too large");
581: } else {
582: parse(subrs[n], gp, pt, wid);
583: }
584: callcount--;
585: }
586: break;
587: case 11: // return
588: return;
589: case 12: // ext...
590: v = ((int) cs[loc++]) & 0xff;
591: if (v == 6) { // s x y a b seac
592: char b = (char) pop();
593: char a = (char) pop();
594: float y = pop();
595: float x = pop();
596: buildAccentChar(x, y, a, b, gp);
597: sloc = 0;
598: } else if (v == 7) { // x y w h sbw
599: wid.y = pop();
600: wid.x = pop();
601: pt.y = pop();
602: pt.x = pop();
603: sloc = 0;
604: } else if (v == 12) { // a b div -> a/b
605: float b = pop();
606: float a = pop();
607: stack[sloc++] = a / b;
608: } else if (v == 33) { // a b setcurrentpoint
609: pt.y = pop();
610: pt.x = pop();
611: gp.moveTo(pt.x, pt.y);
612: sloc = 0;
613: } else if (v == 0) { // dotsection
614: sloc = 0;
615: } else if (v == 1) { // vstem3
616: sloc = 0;
617: } else if (v == 2) { // hstem3
618: sloc = 0;
619: } else if (v == 16) { // n callothersubr
620: int cn = (int) pop();
621: int countargs = (int) pop();
622:
623: // System.out.println("Called othersubr with index "+cn);
624:
625: switch (cn) {
626: case 0:
627: // push args2 and args3 onto stack
628: psStack[psLoc++] = pop();
629: psStack[psLoc++] = pop();
630: pop();
631: break;
632: case 3:
633: // push 3 onto the postscript stack
634: psStack[psLoc++] = 3;
635: break;
636: default:
637: // push arguments onto the postscript stack
638: for (int i = 0; i > countargs; i--) {
639: psStack[psLoc++] = pop();
640: }
641: break;
642: }
643: } else if (v == 17) { // pop
644: // pop from the postscript stack onto the type1 stack
645: stack[sloc++] = psStack[psLoc - 1];
646: psLoc--;
647: } else {
648: throw new RuntimeException("Bad command (" + v
649: + ")");
650: }
651: break;
652: case 13: // s w hsbw
653: wid.x = pop();
654: wid.y = 0;
655: pt.x = pop();
656: pt.y = 0;
657: // gp.moveTo(pt.x, pt.y);
658: sloc = 0;
659: break;
660: case 14: // endchar
661: // return;
662: break;
663: case 15: // x
664: case 16: // x
665: case 17: // x
666: case 18: // x
667: case 19: // x
668: case 20: // x
669: throw new RuntimeException("Bad command (" + v
670: + ")");
671: case 21: // x y rmoveto
672: pt.y += pop();
673: pt.x += pop();
674: gp.moveTo(pt.x, pt.y);
675: sloc = 0;
676: break;
677: case 22: // x hmoveto
678: pt.x += pop();
679: gp.moveTo(pt.x, pt.y);
680: sloc = 0;
681: break;
682: case 23: // x
683: case 24: // x
684: case 25: // x
685: case 26: // x
686: case 27: // x
687: case 28: // x
688: case 29: // x
689: throw new RuntimeException("Bad command (" + v
690: + ")");
691: case 30: // y1 x2 y2 x3 vhcurveto
692: x3 = pop();
693: y2 = pop();
694: x2 = pop();
695: y1 = pop();
696: x1 = y3 = 0;
697: gp.curveTo(pt.x, pt.y + y1, pt.x + x2, pt.y + y1
698: + y2, pt.x + x2 + x3, pt.y + y1 + y2);
699: pt.x += x2 + x3;
700: pt.y += y1 + y2;
701: sloc = 0;
702: break;
703: case 31: // x1 x2 y2 y3 hvcurveto
704: y3 = pop();
705: y2 = pop();
706: x2 = pop();
707: x1 = pop();
708: y1 = x3 = 0;
709: gp.curveTo(pt.x + x1, pt.y, pt.x + x1 + x2, pt.y
710: + y2, pt.x + x1 + x2, pt.y + y2 + y3);
711: pt.x += x1 + x2;
712: pt.y += y2 + y3;
713: sloc = 0;
714: break;
715: }
716: }
717: }
718: }
719:
720: /**
721: * build an accented character out of two pre-defined glyphs.
722: * @param x the x offset of the accent
723: * @param y the y offset of the accent
724: * @param a the index of the accent glyph
725: * @param b the index of the base glyph
726: * @param gp the GeneralPath into which the combined glyph will be
727: * written.
728: */
729: private void buildAccentChar(float x, float y, char a, char b,
730: GeneralPath gp) {
731: // get the outline of the accent
732: GeneralPath pathA = getOutline(a, getWidth(a, null));
733:
734: try {
735: // undo the effect of the transform applied in read
736: AffineTransform xformA = at.createInverse();
737: xformA.translate(x, y);
738: pathA.transform(xformA);
739: } catch (NoninvertibleTransformException nte) {
740: pathA.transform(AffineTransform.getTranslateInstance(x, y));
741: }
742:
743: GeneralPath pathB = getOutline(b, getWidth(b, null));
744:
745: try {
746: AffineTransform xformB = at.createInverse();
747: pathB.transform(xformB);
748: } catch (NoninvertibleTransformException nte) {
749: // ignore
750: }
751:
752: gp.append(pathB, false);
753: gp.append(pathA, false);
754: }
755:
756: /**
757: * Get the width of a given character
758: *
759: * This method is overridden to work if the width array hasn't been
760: * populated (as for one of the 14 base fonts)
761: */
762: public float getWidth(char code, String name) {
763: // we don't have first and last chars, so therefore no width array
764: if (getFirstChar() == -1 || getLastChar() == -1) {
765: String key = chr2name[code & 0xff];
766:
767: // use a name if one is provided
768: if (name != null) {
769: key = name;
770: }
771:
772: if (key != null && name2outline.containsKey(key)) {
773: if (!name2width.containsKey(key)) {
774: // glyph has not yet been parsed
775: // getting the outline will force it to get read
776: getOutline(key, 0);
777: }
778:
779: FlPoint width = (FlPoint) name2width.get(key);
780: if (width != null) {
781: return width.x / getDefaultWidth();
782: }
783: }
784:
785: return 0;
786: }
787:
788: // return the width that has been specified
789: return super .getWidth(code, name);
790: }
791:
792: /**
793: * Decrypt a glyph stored in byte form
794: */
795: private synchronized GeneralPath parseGlyph(byte[] cs,
796: FlPoint advance, AffineTransform at) {
797: GeneralPath gp = new GeneralPath();
798: FlPoint curpoint = new FlPoint();
799:
800: sloc = 0;
801: parse(cs, gp, curpoint, advance);
802:
803: gp.transform(at);
804: return gp;
805: }
806:
807: /**
808: * Get a glyph outline by name
809: *
810: * @param name the name of the desired glyph
811: * @return the glyph outline, or null if unavailable
812: */
813: protected GeneralPath getOutline(String name, float width) {
814: // make sure we have a valid name
815: if (name == null || !name2outline.containsKey(name)) {
816: name = ".notdef";
817: }
818:
819: // get whatever is stored in name
820: Object obj = name2outline.get(name);
821:
822: // if it's a byte array, it needs to be parsed
823: // otherwise, just return the path
824: if (obj instanceof GeneralPath) {
825: return (GeneralPath) obj;
826: } else {
827: byte[] cs = (byte[]) obj;
828: FlPoint advance = new FlPoint();
829:
830: GeneralPath gp = parseGlyph(cs, advance, at);
831:
832: if (width != 0 && advance.x != 0) {
833: // scale the glyph to fit in the width
834: Point2D p = new Point2D.Float(advance.x, advance.y);
835: at.transform(p, p);
836:
837: double scale = width / p.getX();
838: AffineTransform xform = AffineTransform
839: .getScaleInstance(scale, 1.0);
840: gp.transform(xform);
841: }
842:
843: // put the parsed object in the cache
844: name2outline.put(name, gp);
845: name2width.put(name, advance);
846: return gp;
847: }
848: }
849:
850: /**
851: * Get a glyph outline by character code
852: *
853: * Note this method must always return an outline
854: *
855: * @param src the character code of the desired glyph
856: * @return the glyph outline
857: */
858: protected GeneralPath getOutline(char src, float width) {
859: return getOutline(chr2name[src & 0xff], width);
860: }
861: }
|