1 /*
   2  * Copyright (c) 2003, 2018, 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<Object>() {
  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<Object> bufferRef = new WeakReference<>(null);
 102 
 103     private String psName = null;
 104 
 105     private static HashMap<String, String> styleAbbreviationsMapping;
 106     private static HashSet<String> 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         try {
 172             verify();
 173         } catch (Throwable t) {
 174             if (createdCopy) {
 175                 T1DisposerRecord ref = new T1DisposerRecord(platname);
 176                 Disposer.addObjectRecord(bufferRef, ref);
 177                 bufferRef = null;
 178             }
 179             if (t instanceof FontFormatException) {
 180                 throw (FontFormatException)t;
 181             } else {
 182                 throw new FontFormatException("Unexpected runtime exception.");
 183             }
 184         }
 185     }
 186 
 187     private synchronized ByteBuffer getBuffer() throws FontFormatException {
 188         MappedByteBuffer mapBuf = (MappedByteBuffer)bufferRef.get();
 189         if (mapBuf == null) {
 190           //System.out.println("open T1 " + platName);
 191             try {
 192                 RandomAccessFile raf = (RandomAccessFile)
 193                 java.security.AccessController.doPrivileged(
 194                     new java.security.PrivilegedAction<Object>() {
 195                         public Object run() {
 196                             try {
 197                                 return new RandomAccessFile(platName, "r");
 198                             } catch (FileNotFoundException ffne) {
 199                             }
 200                             return null;
 201                     }
 202                 });
 203                 FileChannel fc = raf.getChannel();
 204                 fileSize = (int)fc.size();
 205                 mapBuf = fc.map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
 206                 mapBuf.position(0);
 207                 bufferRef = new WeakReference<>(mapBuf);
 208                 fc.close();
 209             } catch (NullPointerException e) {
 210                 throw new FontFormatException(e.toString());
 211             } catch (ClosedChannelException e) {
 212                 /* NIO I/O is interruptible, recurse to retry operation.
 213                  * Clear interrupts before recursing in case NIO didn't.
 214                  */
 215                 Thread.interrupted();
 216                 return getBuffer();
 217             } catch (IOException e) {
 218                 throw new FontFormatException(e.toString());
 219             }
 220         }
 221         return mapBuf;
 222     }
 223 
 224     protected void close() {
 225     }
 226 
 227     /* called from native code to read file into a direct byte buffer */
 228     void readFile(ByteBuffer buffer) {
 229         RandomAccessFile raf = null;
 230         FileChannel fc;
 231         try {
 232             raf = (RandomAccessFile)
 233                 java.security.AccessController.doPrivileged(
 234                     new java.security.PrivilegedAction<Object>() {
 235                         public Object run() {
 236                             try {
 237                                 return new RandomAccessFile(platName, "r");
 238                             } catch (FileNotFoundException fnfe) {
 239                             }
 240                             return null;
 241                     }
 242             });
 243             fc = raf.getChannel();
 244             while (buffer.remaining() > 0 && fc.read(buffer) != -1) {}
 245         } catch (NullPointerException npe) {
 246         } catch (ClosedChannelException e) {
 247             try {
 248                 if (raf != null) {
 249                     raf.close();
 250                     raf = null;
 251                 }
 252             } catch (IOException ioe) {
 253             }
 254             /* NIO I/O is interruptible, recurse to retry operation.
 255              * Clear interrupts before recursing in case NIO didn't.
 256              */
 257             Thread.interrupted();
 258             readFile(buffer);
 259         } catch (IOException e) {
 260         } finally  {
 261             if (raf != null) {
 262                 try {
 263                     raf.close();
 264                 } catch (IOException e) {
 265                 }
 266             }
 267         }
 268     }
 269 
 270     public synchronized ByteBuffer readBlock(int offset, int length) {
 271         ByteBuffer mappedBuf = null;
 272         try {
 273             mappedBuf = getBuffer();
 274             if (offset > fileSize) {
 275                 offset = fileSize;
 276             }
 277             mappedBuf.position(offset);
 278             return mappedBuf.slice();
 279         } catch (FontFormatException e) {
 280             return null;
 281         }
 282     }
 283 
 284     private void verify() throws FontFormatException {
 285         /* Normal usage should not call getBuffer(), as its state
 286          * ie endianness, position etc, are shared. verify() can do
 287          * this as its called only from within the constructor before
 288          * there are other users of this object.
 289          */
 290         ByteBuffer bb = getBuffer();
 291         if (bb.capacity() < 6) {
 292             throw new FontFormatException("short file");
 293         }
 294         int val = bb.get(0) & 0xff;
 295         if ((bb.get(0) & 0xff) == 0x80) {
 296             verifyPFB(bb);
 297             bb.position(6);
 298         } else {
 299             verifyPFA(bb);
 300             bb.position(0);
 301         }
 302         initNames(bb);
 303         if (familyName == null || fullName == null) {
 304             throw new FontFormatException("Font name not found");
 305         }
 306         setStyle();
 307     }
 308 
 309     public int getFileSize() {
 310         if (fileSize == 0) {
 311             try {
 312                 getBuffer();
 313             } catch (FontFormatException e) {
 314             }
 315         }
 316         return fileSize;
 317     }
 318 
 319     private void verifyPFA(ByteBuffer bb) throws FontFormatException {
 320         if (bb.getShort() != 0x2521) { // 0x2521 is %!
 321             throw new FontFormatException("bad pfa font");
 322         }
 323         // remind - additional verification needed?
 324     }
 325 
 326     private void verifyPFB(ByteBuffer bb) throws FontFormatException {
 327 
 328         int pos = 0;
 329         while (true) {
 330             try {
 331                 int segType = bb.getShort(pos) & 0xffff;
 332                 if (segType == 0x8001 || segType == 0x8002) {
 333                     bb.order(ByteOrder.LITTLE_ENDIAN);
 334                     int segLen = bb.getInt(pos+2);
 335                     bb.order(ByteOrder.BIG_ENDIAN);
 336                     if (segLen <= 0) {
 337                         throw new FontFormatException("bad segment length");
 338                     }
 339                     pos += segLen+6;
 340                 } else if (segType == 0x8003) {
 341                     return;
 342                 } else {
 343                     throw new FontFormatException("bad pfb file");
 344                 }
 345             } catch (BufferUnderflowException bue) {
 346                 throw new FontFormatException(bue.toString());
 347             } catch (Exception e) {
 348                 throw new FontFormatException(e.toString());
 349             }
 350         }
 351     }
 352 
 353     private static final int PSEOFTOKEN = 0;
 354     private static final int PSNAMETOKEN = 1;
 355     private static final int PSSTRINGTOKEN = 2;
 356 
 357     /* Need to parse the ascii contents of the Type1 font file,
 358      * looking for FullName, FamilyName and FontName.
 359      * If explicit names are not found then extract them from first text line.
 360      * Operating on bytes so can't use Java String utilities, which
 361      * is a large part of why this is a hack.
 362      *
 363      * Also check for mandatory FontType and verify if it is supported.
 364      */
 365     private void initNames(ByteBuffer bb) throws FontFormatException {
 366         boolean eof = false;
 367         String fontType = null;
 368         try {
 369             //Parse font looking for explicit FullName, FamilyName and FontName
 370             //  (according to Type1 spec they are optional)
 371             while ((fullName == null || familyName == null || psName == null || fontType == null) && !eof) {
 372                 int tokenType = nextTokenType(bb);
 373                 if (tokenType == PSNAMETOKEN) {
 374                     int pos = bb.position();
 375                     if (bb.get(pos) == 'F') {
 376                         String s = getSimpleToken(bb);
 377                         if ("FullName".equals(s)) {
 378                             if (nextTokenType(bb)==PSSTRINGTOKEN) {
 379                                 fullName = getString(bb);
 380                             }
 381                         } else if ("FamilyName".equals(s)) {
 382                             if (nextTokenType(bb)==PSSTRINGTOKEN) {
 383                                 familyName = getString(bb);
 384                             }
 385                         } else if ("FontName".equals(s)) {
 386                             if (nextTokenType(bb)==PSNAMETOKEN) {
 387                                 psName = getSimpleToken(bb);
 388                             }
 389                         } else if ("FontType".equals(s)) {
 390                             /* look for
 391                                  /FontType id def
 392                             */
 393                             String token = getSimpleToken(bb);
 394                             if ("def".equals(getSimpleToken(bb))) {
 395                                 fontType = token;
 396                         }
 397                         }
 398                     } else {
 399                         while (bb.get() > ' '); // skip token
 400                     }
 401                 } else if (tokenType == PSEOFTOKEN) {
 402                     eof = true;
 403                 }
 404             }
 405         } catch (Exception e) {
 406                 throw new FontFormatException(e.toString());
 407         }
 408 
 409         /* Ignore all fonts besides Type1 (e.g. Type3 fonts) */
 410         if (!"1".equals(fontType)) {
 411             throw new FontFormatException("Unsupported font type");
 412         }
 413 
 414     if (psName == null) { //no explicit FontName
 415                 // Try to extract font name from the first text line.
 416                 // According to Type1 spec first line consist of
 417                 //  "%!FontType1-SpecVersion: FontName FontVersion"
 418                 // or
 419                 //  "%!PS-AdobeFont-1.0: FontName version"
 420                 bb.position(0);
 421                 if (bb.getShort() != 0x2521) { //if pfb (do not start with "%!")
 422                     //skip segment header and "%!"
 423                     bb.position(8);
 424                     //NB: assume that first segment is ASCII one
 425                     //  (is it possible to have valid Type1 font with first binary segment?)
 426                 }
 427                 String formatType = getSimpleToken(bb);
 428                 if (!formatType.startsWith("FontType1-") && !formatType.startsWith("PS-AdobeFont-")) {
 429                         throw new FontFormatException("Unsupported font format [" + formatType + "]");
 430                 }
 431                 psName = getSimpleToken(bb);
 432         }
 433 
 434     //if we got to the end of file then we did not find at least one of FullName or FamilyName
 435     //Try to deduce missing names from present ones
 436     //NB: At least psName must be already initialized by this moment
 437         if (eof) {
 438             //if we find fullName or familyName then use it as another name too
 439             if (fullName != null) {
 440                 familyName = fullName2FamilyName(fullName);
 441             } else if (familyName != null) {
 442                 fullName = familyName;
 443             } else { //fallback - use postscript font name to deduce full and family names
 444                 fullName = psName2FullName(psName);
 445                 familyName = psName2FamilyName(psName);
 446             }
 447         }
 448     }
 449 
 450     private String fullName2FamilyName(String name) {
 451         String res, token;
 452         int len, start, end; //length of family name part
 453 
 454         //FamilyName is truncated version of FullName
 455         //Truncated tail must contain only style modifiers
 456 
 457         end = name.length();
 458 
 459         while (end > 0) {
 460             start = end - 1;
 461             while (start > 0 && name.charAt(start) != ' ')
 462               start--;
 463             //as soon as we meet first non style token truncate
 464             // current tail and return
 465                         if (!isStyleToken(name.substring(start+1, end))) {
 466                                 return name.substring(0, end);
 467             }
 468                         end = start;
 469         }
 470 
 471                 return name; //should not happen
 472         }
 473 
 474     private String expandAbbreviation(String abbr) {
 475         if (styleAbbreviationsMapping.containsKey(abbr))
 476                         return styleAbbreviationsMapping.get(abbr);
 477         return abbr;
 478     }
 479 
 480     private boolean isStyleToken(String token) {
 481         return styleNameTokes.contains(token);
 482     }
 483 
 484     private String psName2FullName(String name) {
 485         String res;
 486         int pos;
 487 
 488         //According to Adobe technical note #5088 psName (aka FontName) has form
 489         //   <Family Name><VendorID>-<Weight><Width><Slant><Character Set>
 490         //where spaces are not allowed.
 491 
 492         //Conversion: Expand abbreviations in style portion (everything after '-'),
 493         //            replace '-' with space and insert missing spaces
 494         pos = name.indexOf('-');
 495         if (pos >= 0) {
 496             res =  expandName(name.substring(0, pos), false);
 497             res += " " + expandName(name.substring(pos+1), true);
 498         } else {
 499             res = expandName(name, false);
 500         }
 501 
 502         return res;
 503     }
 504 
 505     private String psName2FamilyName(String name) {
 506         String tmp = name;
 507 
 508         //According to Adobe technical note #5088 psName (aka FontName) has form
 509         //   <Family Name><VendorID>-<Weight><Width><Slant><Character Set>
 510         //where spaces are not allowed.
 511 
 512         //Conversion: Truncate style portion (everything after '-')
 513         //            and insert missing spaces
 514 
 515         if (tmp.indexOf('-') > 0) {
 516             tmp = tmp.substring(0, tmp.indexOf('-'));
 517         }
 518 
 519         return expandName(tmp, false);
 520     }
 521 
 522     private int nextCapitalLetter(String s, int off) {
 523         for (; (off >=0) && off < s.length(); off++) {
 524             if (s.charAt(off) >= 'A' && s.charAt(off) <= 'Z')
 525                 return off;
 526         }
 527         return -1;
 528     }
 529 
 530     private String expandName(String s, boolean tryExpandAbbreviations) {
 531         StringBuilder res = new StringBuilder(s.length() + 10);
 532         int start=0, end;
 533 
 534         while(start < s.length()) {
 535             end = nextCapitalLetter(s, start + 1);
 536             if (end < 0) {
 537                 end = s.length();
 538             }
 539 
 540             if (start != 0) {
 541                 res.append(" ");
 542             }
 543 
 544             if (tryExpandAbbreviations) {
 545                 res.append(expandAbbreviation(s.substring(start, end)));
 546             } else {
 547                 res.append(s.substring(start, end));
 548             }
 549             start = end;
 550                 }
 551 
 552         return res.toString();
 553     }
 554 
 555     /* skip lines beginning with "%" and leading white space on a line */
 556     private byte skip(ByteBuffer bb) {
 557         byte b = bb.get();
 558         while (b == '%') {
 559             while (true) {
 560                 b = bb.get();
 561                 if (b == '\r' || b == '\n') {
 562                     break;
 563                 }
 564             }
 565         }
 566         while (b <= ' ') {
 567             b = bb.get();
 568         }
 569         return b;
 570     }
 571 
 572     /*
 573      * Token types:
 574      * PSNAMETOKEN - /
 575      * PSSTRINGTOKEN - literal text string
 576      */
 577     private int nextTokenType(ByteBuffer bb) {
 578 
 579         try {
 580             byte b = skip(bb);
 581 
 582             while (true) {
 583                 if (b == (byte)'/') { // PS defined name follows.
 584                     return PSNAMETOKEN;
 585                 } else if (b == (byte)'(') { // PS string follows
 586                     return PSSTRINGTOKEN;
 587                 } else if ((b == (byte)'\r') || (b == (byte)'\n')) {
 588                 b = skip(bb);
 589                 } else {
 590                     b = bb.get();
 591                 }
 592             }
 593         } catch (BufferUnderflowException e) {
 594             return PSEOFTOKEN;
 595         }
 596     }
 597 
 598     /* Read simple token (sequence of non-whitespace characters)
 599          starting from the current position.
 600          Skip leading whitespaces (if any). */
 601     private String getSimpleToken(ByteBuffer bb) {
 602         while (bb.get() <= ' ');
 603         int pos1 = bb.position()-1;
 604         while (bb.get() > ' ');
 605         int pos2 = bb.position();
 606         byte[] nameBytes = new byte[pos2-pos1-1];
 607         bb.position(pos1);
 608         bb.get(nameBytes);
 609         try {
 610             return new String(nameBytes, "US-ASCII");
 611         } catch (UnsupportedEncodingException e) {
 612             return new String(nameBytes);
 613         }
 614     }
 615 
 616     private String getString(ByteBuffer bb) {
 617         int pos1 = bb.position();
 618         while (bb.get() != ')');
 619         int pos2 = bb.position();
 620         byte[] nameBytes = new byte[pos2-pos1-1];
 621         bb.position(pos1);
 622         bb.get(nameBytes);
 623         try {
 624             return new String(nameBytes, "US-ASCII");
 625         } catch (UnsupportedEncodingException e) {
 626             return new String(nameBytes);
 627         }
 628     }
 629 
 630 
 631     public String getPostscriptName() {
 632         return psName;
 633     }
 634 
 635     protected synchronized FontScaler getScaler() {
 636         if (scaler == null) {
 637             scaler = FontScaler.getScaler(this, 0, false, fileSize);
 638         }
 639 
 640         return scaler;
 641     }
 642 
 643     CharToGlyphMapper getMapper() {
 644         if (mapper == null) {
 645             mapper = new Type1GlyphMapper(this);
 646         }
 647         return mapper;
 648     }
 649 
 650     public int getNumGlyphs() {
 651         try {
 652             return getScaler().getNumGlyphs();
 653         } catch (FontScalerException e) {
 654             scaler = FontScaler.getNullScaler();
 655             return getNumGlyphs();
 656         }
 657     }
 658 
 659     public int getMissingGlyphCode() {
 660         try {
 661             return getScaler().getMissingGlyphCode();
 662         } catch (FontScalerException e) {
 663             scaler = FontScaler.getNullScaler();
 664             return getMissingGlyphCode();
 665         }
 666     }
 667 
 668     public int getGlyphCode(char charCode) {
 669         try {
 670             return getScaler().getGlyphCode(charCode);
 671         } catch (FontScalerException e) {
 672             scaler = FontScaler.getNullScaler();
 673             return getGlyphCode(charCode);
 674         }
 675     }
 676 
 677     public String toString() {
 678         return "** Type1 Font: Family="+familyName+ " Name="+fullName+
 679             " style="+style+" fileName="+getPublicFileName();
 680     }
 681 }