History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/sun/font/Type1Font.java (1 of 18352 files). Show all

Unified · Split

java/pkg/sun/font/Type1Font.java+682−0
@@ -0,0 +1,682 @@
1+/*
2+ * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
3+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+ *
5+ * This code is free software; you can redistribute it and/or modify it
6+ * under the terms of the GNU General Public License version 2 only, as
7+ * published by the Free Software Foundation. Oracle designates this
8+ * particular file as subject to the "Classpath" exception as provided
9+ * by Oracle in the LICENSE file that accompanied this code.
10+ *
11+ * This code is distributed in the hope that it will be useful, but WITHOUT
12+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+ * version 2 for more details (a copy is included in the LICENSE file that
15+ * accompanied this code).
16+ *
17+ * You should have received a copy of the GNU General Public License version
18+ * 2 along with this work; if not, write to the Free Software Foundation,
19+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+ *
21+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+ * or visit www.oracle.com if you need additional information or have any
23+ * questions.
24+ */
25+
26+package sun.font;
27+
28+import java.lang.ref.WeakReference;
29+import java.awt.FontFormatException;
30+import java.io.FileNotFoundException;
31+import java.io.IOException;
32+import java.io.RandomAccessFile;
33+import java.io.UnsupportedEncodingException;
34+import java.lang.ref.WeakReference;
35+import java.nio.ByteBuffer;
36+import java.nio.ByteOrder;
37+import java.nio.MappedByteBuffer;
38+import java.nio.BufferUnderflowException;
39+import java.nio.channels.ClosedChannelException;
40+import java.nio.channels.FileChannel;
41+import sun.java2d.Disposer;
42+import sun.java2d.DisposerRecord;
43+import java.util.HashSet;
44+import java.util.HashMap;
45+import java.awt.Font;
46+
47+/*
48+ * Adobe Technical Note 5040 details the format of PFB files.
49+ * the file is divided into ascii and binary sections. Each section
50+ * starts with a header
51+ * 0x8001 - start of binary data, is followed by 4 bytes length, then data
52+ * 0x8002 - start of ascii data, is followed by 4 bytes length, then data
53+ * 0x8003 - end of data segment
54+ * The length is organised as LSB->MSB.
55+ *
56+ * Note: I experimented with using a MappedByteBuffer and
57+ * there were two problems/questions.
58+ * 1. If a global buffer is used rather than one allocated in the calling
59+ * context, then we need to synchronize on all uses of that data, which
60+ * means more code would beed to be synchronized with probable repercussions
61+ * elsewhere.
62+ * 2. It is not clear whether to free the buffer when the file is closed.
63+ * If we have the contents in memory then why keep open files around?
64+ * The mmapped buffer doesn't need it.
65+ * Also regular GC is what frees the buffer. So closing the file and nulling
66+ * out the reference still needs to wait for the buffer to be GC'd to
67+ * reclaim the storage.
68+ * If the contents of the buffer are persistent there's no need
69+ * to worry about synchronization.
70+ * Perhaps could use a WeakReference, and when its referent is gone, and
71+ * need it can just reopen the file.
72+ * Type1 fonts thus don't use up file descriptor references, but can
73+ * use memory footprint in a way that's managed by the host O/S.
74+ * The main "pain" may be the different model means code needs to be written
75+ * without assumptions as to how this is handled by the different subclasses
76+ * of FileFont.
77+ */
78+public class Type1Font extends FileFont {
79+
80+ private static class T1DisposerRecord implements DisposerRecord {
81+ String fileName = null;
82+
83+ T1DisposerRecord(String name) {
84+ fileName = name;
85+ }
86+
87+ public synchronized void dispose() {
88+ java.security.AccessController.doPrivileged(
89+ new java.security.PrivilegedAction() {
90+ public Object run() {
91+
92+ if (fileName != null) {
93+ (new java.io.File(fileName)).delete();
94+ }
95+ return null;
96+ }
97+ });
98+ }
99+ }
100+
101+ WeakReference bufferRef = new WeakReference(null);
102+
103+ private String psName = null;
104+
105+ static private HashMap styleAbbreviationsMapping;
106+ static private HashSet styleNameTokes;
107+
108+ static {
109+ styleAbbreviationsMapping = new HashMap();
110+ styleNameTokes = new HashSet();
111+
112+ /* These abbreviation rules are taken from Appendix 1 of Adobe Technical Note #5088 */
113+ /* NB: this list is not complete - we did not include abbreviations which contain
114+ several capital letters because current expansion algorithm do not support this.
115+ (namely we have omited MM aka "Multiple Master", OsF aka "Oldstyle figures",
116+ OS aka "Oldstyle", SC aka "Small caps" and DS aka "Display" */
117+ String nm[] = {"Black", "Bold", "Book", "Demi", "Heavy", "Light",
118+ "Meduium", "Nord", "Poster", "Regular", "Super", "Thin",
119+ "Compressed", "Condensed", "Compact", "Extended", "Narrow",
120+ "Inclined", "Italic", "Kursiv", "Oblique", "Upright", "Sloped",
121+ "Semi", "Ultra", "Extra",
122+ "Alternate", "Alternate", "Deutsche Fraktur", "Expert", "Inline", "Ornaments",
123+ "Outline", "Roman", "Rounded", "Script", "Shaded", "Swash", "Titling", "Typewriter"};
124+ String abbrv[] = {"Blk", "Bd", "Bk", "Dm", "Hv", "Lt",
125+ "Md", "Nd", "Po", "Rg", "Su", "Th",
126+ "Cm", "Cn", "Ct", "Ex", "Nr",
127+ "Ic", "It", "Ks", "Obl", "Up", "Sl",
128+ "Sm", "Ult", "X",
129+ "A", "Alt", "Dfr", "Exp", "In", "Or",
130+ "Ou", "Rm", "Rd", "Scr", "Sh", "Sw", "Ti", "Typ"};
131+ /* This is only subset of names from nm[] because we want to distinguish things
132+ like "Lucida Sans TypeWriter Bold" and "Lucida Sans Bold".
133+ Names from "Design and/or special purpose" group are omitted. */
134+ String styleTokens[] = {"Black", "Bold", "Book", "Demi", "Heavy", "Light",
135+ "Medium", "Nord", "Poster", "Regular", "Super", "Thin",
136+ "Compressed", "Condensed", "Compact", "Extended", "Narrow",
137+ "Inclined", "Italic", "Kursiv", "Oblique", "Upright", "Sloped", "Slanted",
138+ "Semi", "Ultra", "Extra"};
139+
140+ for(int i=0; i<nm.length; i++) {
141+ styleAbbreviationsMapping.put(abbrv[i], nm[i]);
142+ }
143+ for(int i=0; i<styleTokens.length; i++) {
144+ styleNameTokes.add(styleTokens[i]);
145+ }
146+ }
147+
148+
149+ /**
150+ * Constructs a Type1 Font.
151+ * @param platname - Platform identifier of the font. Typically file name.
152+ * @param nativeNames - Native names - typically XLFDs on Unix.
153+ */
154+ public Type1Font(String platname, Object nativeNames)
155+ throws FontFormatException {
156+
157+ this(platname, nativeNames, false);
158+ }
159+
160+ /**
161+ * - does basic verification of the file
162+ * - reads the names (full, family).
163+ * - determines the style of the font.
164+ * @throws FontFormatException - if the font can't be opened
165+ * or fails verification, or there's no usable cmap
166+ */
167+ public Type1Font(String platname, Object nativeNames, boolean createdCopy)
168+ throws FontFormatException {
169+ super(platname, nativeNames);
170+ fontRank = Font2D.TYPE1_RANK;
171+ checkedNatives = true;
172+ try {
173+ verify();
174+ } catch (Throwable t) {
175+ if (createdCopy) {
176+ T1DisposerRecord ref = new T1DisposerRecord(platname);
177+ Disposer.addObjectRecord(bufferRef, ref);
178+ bufferRef = null;
179+ }
180+ if (t instanceof FontFormatException) {
181+ throw (FontFormatException)t;
182+ } else {
183+ throw new FontFormatException("Unexpected runtime exception.");
184+ }
185+ }
186+ }
187+
188+ private synchronized ByteBuffer getBuffer() throws FontFormatException {
189+ MappedByteBuffer mapBuf = (MappedByteBuffer)bufferRef.get();
190+ if (mapBuf == null) {
191+ //System.out.println("open T1 " + platName);
192+ try {
193+ RandomAccessFile raf = (RandomAccessFile)
194+ java.security.AccessController.doPrivileged(
195+ new java.security.PrivilegedAction() {
196+ public Object run() {
197+ try {
198+ return new RandomAccessFile(platName, "r");
199+ } catch (FileNotFoundException ffne) {
200+ }
201+ return null;
202+ }
203+ });
204+ FileChannel fc = raf.getChannel();
205+ fileSize = (int)fc.size();
206+ mapBuf = fc.map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
207+ mapBuf.position(0);
208+ bufferRef = new WeakReference(mapBuf);
209+ fc.close();
210+ } catch (NullPointerException e) {
211+ throw new FontFormatException(e.toString());
212+ } catch (ClosedChannelException e) {
213+ /* NIO I/O is interruptible, recurse to retry operation.
214+ * Clear interrupts before recursing in case NIO didn't.
215+ */
216+ Thread.interrupted();
217+ return getBuffer();
218+ } catch (IOException e) {
219+ throw new FontFormatException(e.toString());
220+ }
221+ }
222+ return mapBuf;
223+ }
224+
225+ protected void close() {
226+ }
227+
228+ /* called from native code to read file into a direct byte buffer */
229+ void readFile(ByteBuffer buffer) {
230+ RandomAccessFile raf = null;
231+ FileChannel fc;
232+ try {
233+ raf = (RandomAccessFile)
234+ java.security.AccessController.doPrivileged(
235+ new java.security.PrivilegedAction() {
236+ public Object run() {
237+ try {
238+ return new RandomAccessFile(platName, "r");
239+ } catch (FileNotFoundException fnfe) {
240+ }
241+ return null;
242+ }
243+ });
244+ fc = raf.getChannel();
245+ while (buffer.remaining() > 0 && fc.read(buffer) != -1) {}
246+ } catch (NullPointerException npe) {
247+ } catch (ClosedChannelException e) {
248+ try {
249+ if (raf != null) {
250+ raf.close();
251+ raf = null;
252+ }
253+ } catch (IOException ioe) {
254+ }
255+ /* NIO I/O is interruptible, recurse to retry operation.
256+ * Clear interrupts before recursing in case NIO didn't.
257+ */
258+ Thread.interrupted();
259+ readFile(buffer);
260+ } catch (IOException e) {
261+ } finally {
262+ if (raf != null) {
263+ try {
264+ raf.close();
265+ } catch (IOException e) {
266+ }
267+ }
268+ }
269+ }
270+
271+ public synchronized ByteBuffer readBlock(int offset, int length) {
272+ ByteBuffer mappedBuf = null;
273+ try {
274+ mappedBuf = getBuffer();
275+ if (offset > fileSize) {
276+ offset = fileSize;
277+ }
278+ mappedBuf.position(offset);
279+ return mappedBuf.slice();
280+ } catch (FontFormatException e) {
281+ return null;
282+ }
283+ }
284+
285+ private void verify() throws FontFormatException {
286+ /* Normal usage should not call getBuffer(), as its state
287+ * ie endianness, position etc, are shared. verify() can do
288+ * this as its called only from within the constructor before
289+ * there are other users of this object.
290+ */
291+ ByteBuffer bb = getBuffer();
292+ if (bb.capacity() < 6) {
293+ throw new FontFormatException("short file");
294+ }
295+ int val = bb.get(0) & 0xff;
296+ if ((bb.get(0) & 0xff) == 0x80) {
297+ verifyPFB(bb);
298+ bb.position(6);
299+ } else {
300+ verifyPFA(bb);
301+ bb.position(0);
302+ }
303+ initNames(bb);
304+ if (familyName == null || fullName == null) {
305+ throw new FontFormatException("Font name not found");
306+ }
307+ setStyle();
308+ }
309+
310+ public int getFileSize() {
311+ if (fileSize == 0) {
312+ try {
313+ getBuffer();
314+ } catch (FontFormatException e) {
315+ }
316+ }
317+ return fileSize;
318+ }
319+
320+ private void verifyPFA(ByteBuffer bb) throws FontFormatException {
321+ if (bb.getShort() != 0x2521) { // 0x2521 is %!
322+ throw new FontFormatException("bad pfa font");
323+ }
324+ // remind - additional verification needed?
325+ }
326+
327+ private void verifyPFB(ByteBuffer bb) throws FontFormatException {
328+
329+ int pos = 0;
330+ while (true) {
331+ try {
332+ int segType = bb.getShort(pos) & 0xffff;
333+ if (segType == 0x8001 || segType == 0x8002) {
334+ bb.order(ByteOrder.LITTLE_ENDIAN);
335+ int segLen = bb.getInt(pos+2);
336+ bb.order(ByteOrder.BIG_ENDIAN);
337+ if (segLen <= 0) {
338+ throw new FontFormatException("bad segment length");
339+ }
340+ pos += segLen+6;
341+ } else if (segType == 0x8003) {
342+ return;
343+ } else {
344+ throw new FontFormatException("bad pfb file");
345+ }
346+ } catch (BufferUnderflowException bue) {
347+ throw new FontFormatException(bue.toString());
348+ } catch (Exception e) {
349+ throw new FontFormatException(e.toString());
350+ }
351+ }
352+ }
353+
354+ private static final int PSEOFTOKEN = 0;
355+ private static final int PSNAMETOKEN = 1;
356+ private static final int PSSTRINGTOKEN = 2;
357+
358+ /* Need to parse the ascii contents of the Type1 font file,
359+ * looking for FullName, FamilyName and FontName.
360+ * If explicit names are not found then extract them from first text line.
361+ * Operating on bytes so can't use Java String utilities, which
362+ * is a large part of why this is a hack.
363+ *
364+ * Also check for mandatory FontType and verify if it is supported.
365+ */
366+ private void initNames(ByteBuffer bb) throws FontFormatException {
367+ boolean eof = false;
368+ String fontType = null;
369+ try {
370+ //Parse font looking for explicit FullName, FamilyName and FontName
371+ // (acording to Type1 spec they are optional)
372+ while ((fullName == null || familyName == null || psName == null || fontType == null) && !eof) {
373+ int tokenType = nextTokenType(bb);
374+ if (tokenType == PSNAMETOKEN) {
375+ int pos = bb.position();
376+ if (bb.get(pos) == 'F') {
377+ String s = getSimpleToken(bb);
378+ if ("FullName".equals(s)) {
379+ if (nextTokenType(bb)==PSSTRINGTOKEN) {
380+ fullName = getString(bb);
381+ }
382+ } else if ("FamilyName".equals(s)) {
383+ if (nextTokenType(bb)==PSSTRINGTOKEN) {
384+ familyName = getString(bb);
385+ }
386+ } else if ("FontName".equals(s)) {
387+ if (nextTokenType(bb)==PSNAMETOKEN) {
388+ psName = getSimpleToken(bb);
389+ }
390+ } else if ("FontType".equals(s)) {
391+ /* look for
392+ /FontType id def
393+ */
394+ String token = getSimpleToken(bb);
395+ if ("def".equals(getSimpleToken(bb))) {
396+ fontType = token;
397+ }
398+ }
399+ } else {
400+ while (bb.get() > ' '); // skip token
401+ }
402+ } else if (tokenType == PSEOFTOKEN) {
403+ eof = true;
404+ }
405+ }
406+ } catch (Exception e) {
407+ throw new FontFormatException(e.toString());
408+ }
409+
410+ /* Ignore all fonts besides Type1 (e.g. Type3 fonts) */
411+ if (!"1".equals(fontType)) {
412+ throw new FontFormatException("Unsupported font type");
413+ }
414+
415+ if (psName == null) { //no explicit FontName
416+ // Try to extract font name from the first text line.
417+ // According to Type1 spec first line consist of
418+ // "%!FontType1-SpecVersion: FontName FontVersion"
419+ // or
420+ // "%!PS-AdobeFont-1.0: FontName version"
421+ bb.position(0);
422+ if (bb.getShort() != 0x2521) { //if pfb (do not start with "%!")
423+ //skip segment header and "%!"
424+ bb.position(8);
425+ //NB: assume that first segment is ASCII one
426+ // (is it possible to have valid Type1 font with first binary segment?)
427+ }
428+ String formatType = getSimpleToken(bb);
429+ if (!formatType.startsWith("FontType1-") && !formatType.startsWith("PS-AdobeFont-")) {
430+ throw new FontFormatException("Unsupported font format [" + formatType + "]");
431+ }
432+ psName = getSimpleToken(bb);
433+ }
434+
435+ //if we got to the end of file then we did not find at least one of FullName or FamilyName
436+ //Try to deduce missing names from present ones
437+ //NB: At least psName must be already initialized by this moment
438+ if (eof) {
439+ //if we find fullName or familyName then use it as another name too
440+ if (fullName != null) {
441+ familyName = fullName2FamilyName(fullName);
442+ } else if (familyName != null) {
443+ fullName = familyName;
444+ } else { //fallback - use postscript font name to deduce full and family names
445+ fullName = psName2FullName(psName);
446+ familyName = psName2FamilyName(psName);
447+ }
448+ }
449+ }
450+
451+ private String fullName2FamilyName(String name) {
452+ String res, token;
453+ int len, start, end; //length of family name part
454+
455+ //FamilyName is truncated version of FullName
456+ //Truncated tail must contain only style modifiers
457+
458+ end = name.length();
459+
460+ while (end > 0) {
461+ start = end - 1;
462+ while (start > 0 && name.charAt(start) != ' ')
463+ start--;
464+ //as soon as we meet first non style token truncate
465+ // current tail and return
466+ if (!isStyleToken(name.substring(start+1, end))) {
467+ return name.substring(0, end);
468+ }
469+ end = start;
470+ }
471+
472+ return name; //should not happen
473+ }
474+
475+ private String expandAbbreviation(String abbr) {
476+ if (styleAbbreviationsMapping.containsKey(abbr))
477+ return (String) styleAbbreviationsMapping.get(abbr);
478+ return abbr;
479+ }
480+
481+ private boolean isStyleToken(String token) {
482+ return styleNameTokes.contains(token);
483+ }
484+
485+ private String psName2FullName(String name) {
486+ String res;
487+ int pos;
488+
489+ //According to Adobe technical note #5088 psName (aka FontName) has form
490+ // <Family Name><VendorID>-<Weight><Width><Slant><Character Set>
491+ //where spaces are not allowed.
492+
493+ //Conversion: Expand abbreviations in style portion (everything after '-'),
494+ // replace '-' with space and insert missing spaces
495+ pos = name.indexOf("-");
496+ if (pos >= 0) {
497+ res = expandName(name.substring(0, pos), false);
498+ res += " " + expandName(name.substring(pos+1), true);
499+ } else {
500+ res = expandName(name, false);
501+ }
502+
503+ return res;
504+ }
505+
506+ private String psName2FamilyName(String name) {
507+ String tmp = name;
508+
509+ //According to Adobe technical note #5088 psName (aka FontName) has form
510+ // <Family Name><VendorID>-<Weight><Width><Slant><Character Set>
511+ //where spaces are not allowed.
512+
513+ //Conversion: Truncate style portion (everything after '-')
514+ // and insert missing spaces
515+
516+ if (tmp.indexOf("-") > 0) {
517+ tmp = tmp.substring(0, tmp.indexOf("-"));
518+ }
519+
520+ return expandName(tmp, false);
521+ }
522+
523+ private int nextCapitalLetter(String s, int off) {
524+ for (; (off >=0) && off < s.length(); off++) {
525+ if (s.charAt(off) >= 'A' && s.charAt(off) <= 'Z')
526+ return off;
527+ }
528+ return -1;
529+ }
530+
531+ private String expandName(String s, boolean tryExpandAbbreviations) {
532+ StringBuffer res = new StringBuffer(s.length() + 10);
533+ int start=0, end;
534+
535+ while(start < s.length()) {
536+ end = nextCapitalLetter(s, start + 1);
537+ if (end < 0) {
538+ end = s.length();
539+ }
540+
541+ if (start != 0) {
542+ res.append(" ");
543+ }
544+
545+ if (tryExpandAbbreviations) {
546+ res.append(expandAbbreviation(s.substring(start, end)));
547+ } else {
548+ res.append(s.substring(start, end));
549+ }
550+ start = end;
551+ }
552+
553+ return res.toString();
554+ }
555+
556+ /* skip lines beginning with "%" and leading white space on a line */
557+ private byte skip(ByteBuffer bb) {
558+ byte b = bb.get();
559+ while (b == '%') {
560+ while (true) {
561+ b = bb.get();
562+ if (b == '\r' || b == '\n') {
563+ break;
564+ }
565+ }
566+ }
567+ while (b <= ' ') {
568+ b = bb.get();
569+ }
570+ return b;
571+ }
572+
573+ /*
574+ * Token types:
575+ * PSNAMETOKEN - /
576+ * PSSTRINGTOKEN - literal text string
577+ */
578+ private int nextTokenType(ByteBuffer bb) {
579+
580+ try {
581+ byte b = skip(bb);
582+
583+ while (true) {
584+ if (b == (byte)'/') { // PS defined name follows.
585+ return PSNAMETOKEN;
586+ } else if (b == (byte)'(') { // PS string follows
587+ return PSSTRINGTOKEN;
588+ } else if ((b == (byte)'\r') || (b == (byte)'\n')) {
589+ b = skip(bb);
590+ } else {
591+ b = bb.get();
592+ }
593+ }
594+ } catch (BufferUnderflowException e) {
595+ return PSEOFTOKEN;
596+ }
597+ }
598+
599+ /* Read simple token (sequence of non-whitespace characters)
600+ starting from the current position.
601+ Skip leading whitespaces (if any). */
602+ private String getSimpleToken(ByteBuffer bb) {
603+ while (bb.get() <= ' ');
604+ int pos1 = bb.position()-1;
605+ while (bb.get() > ' ');
606+ int pos2 = bb.position();
607+ byte[] nameBytes = new byte[pos2-pos1-1];
608+ bb.position(pos1);
609+ bb.get(nameBytes);
610+ try {
611+ return new String(nameBytes, "US-ASCII");
612+ } catch (UnsupportedEncodingException e) {
613+ return new String(nameBytes);
614+ }
615+ }
616+
617+ private String getString(ByteBuffer bb) {
618+ int pos1 = bb.position();
619+ while (bb.get() != ')');
620+ int pos2 = bb.position();
621+ byte[] nameBytes = new byte[pos2-pos1-1];
622+ bb.position(pos1);
623+ bb.get(nameBytes);
624+ try {
625+ return new String(nameBytes, "US-ASCII");
626+ } catch (UnsupportedEncodingException e) {
627+ return new String(nameBytes);
628+ }
629+ }
630+
631+
632+ public String getPostscriptName() {
633+ return psName;
634+ }
635+
636+ protected synchronized FontScaler getScaler() {
637+ if (scaler == null) {
638+ scaler = FontScaler.getScaler(this, 0, false, fileSize);
639+ }
640+
641+ return scaler;
642+ }
643+
644+ CharToGlyphMapper getMapper() {
645+ if (mapper == null) {
646+ mapper = new Type1GlyphMapper(this);
647+ }
648+ return mapper;
649+ }
650+
651+ public int getNumGlyphs() {
652+ try {
653+ return getScaler().getNumGlyphs();
654+ } catch (FontScalerException e) {
655+ scaler = FontScaler.getNullScaler();
656+ return getNumGlyphs();
657+ }
658+ }
659+
660+ public int getMissingGlyphCode() {
661+ try {
662+ return getScaler().getMissingGlyphCode();
663+ } catch (FontScalerException e) {
664+ scaler = FontScaler.getNullScaler();
665+ return getMissingGlyphCode();
666+ }
667+ }
668+
669+ public int getGlyphCode(char charCode) {
670+ try {
671+ return getScaler().getGlyphCode(charCode);
672+ } catch (FontScalerException e) {
673+ scaler = FontScaler.getNullScaler();
674+ return getGlyphCode(charCode);
675+ }
676+ }
677+
678+ public String toString() {
679+ return "** Type1 Font: Family="+familyName+ " Name="+fullName+
680+ " style="+style+" fileName="+getPublicFileName();
681+ }
682+}