1 /*
   2  * Copyright 2003-2008 Sun Microsystems, Inc.  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.  Sun designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  22  * CA 95054 USA or visit www.sun.com if you need additional information or
  23  * have any 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="+platName;
 681     }
 682 }