Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members | Related Pages

GfxFont.cc

Go to the documentation of this file.
00001 //========================================================================
00002 //
00003 // GfxFont.cc
00004 //
00005 // Copyright 1996-2002 Glyph & Cog, LLC
00006 //
00007 //========================================================================
00008 
00009 #ifdef __GNUC__
00010 #pragma implementation
00011 #endif
00012 
00013 #include <aconf.h>
00014 #include <stdio.h>
00015 #include <stdlib.h>
00016 #include <string.h>
00017 #include <ctype.h>
00018 #include "gmem.h"
00019 #include "Error.h"
00020 #include "Object.h"
00021 #include "Dict.h"
00022 #include "GlobalParams.h"
00023 #include "CMap.h"
00024 #include "CharCodeToUnicode.h"
00025 #include "FontEncodingTables.h"
00026 #include "BuiltinFontTables.h"
00027 #include "FontFile.h"
00028 #include "GfxFont.h"
00029 
00030 //------------------------------------------------------------------------
00031 
00032 struct StdFontMapEntry {
00033   char *altName;
00034   char *properName;
00035 };
00036 
00037 static StdFontMapEntry stdFontMap[] = {
00038   { "Arial",                        "Helvetica" },
00039   { "Arial,Bold",                   "Helvetica-Bold" },
00040   { "Arial,BoldItalic",             "Helvetica-BoldOblique" },
00041   { "Arial,Italic",                 "Helvetica-Oblique" },
00042   { "Arial-Bold",                   "Helvetica-Bold" },
00043   { "Arial-BoldItalic",             "Helvetica-BoldOblique" },
00044   { "Arial-BoldItalicMT",           "Helvetica-BoldOblique" },
00045   { "Arial-BoldMT",                 "Helvetica-Bold" },
00046   { "Arial-Italic",                 "Helvetica-Oblique" },
00047   { "Arial-ItalicMT",               "Helvetica-Oblique" },
00048   { "ArialMT",                      "Helvetica" },
00049   { "Courier,Bold",                 "Courier-Bold" },
00050   { "Courier,Italic",               "Courier-Oblique" },
00051   { "Courier,BoldItalic",           "Courier-BoldOblique" },
00052   { "CourierNew",                   "Courier" },
00053   { "CourierNew,Bold",              "Courier-Bold" },
00054   { "CourierNew,BoldItalic",        "Courier-BoldOblique" },
00055   { "CourierNew,Italic",            "Courier-Oblique" },
00056   { "CourierNew-Bold",              "Courier-Bold" },
00057   { "CourierNew-BoldItalic",        "Courier-BoldOblique" },
00058   { "CourierNew-Italic",            "Courier-Oblique" },
00059   { "CourierNewPS-BoldItalicMT",    "Courier-BoldOblique" },
00060   { "CourierNewPS-BoldMT",          "Courier-Bold" },
00061   { "CourierNewPS-ItalicMT",        "Courier-Oblique" },
00062   { "CourierNewPSMT",               "Courier" },
00063   { "Helvetica,Bold",               "Helvetica-Bold" },
00064   { "Helvetica,BoldItalic",         "Helvetica-BoldOblique" },
00065   { "Helvetica,Italic",             "Helvetica-Oblique" },
00066   { "Helvetica-BoldItalic",         "Helvetica-BoldOblique" },
00067   { "Helvetica-Italic",             "Helvetica-Oblique" },
00068   { "TimesNewRoman",                "Times-Roman" },
00069   { "TimesNewRoman,Bold",           "Times-Bold" },
00070   { "TimesNewRoman,BoldItalic",     "Times-BoldItalic" },
00071   { "TimesNewRoman,Italic",         "Times-Italic" },
00072   { "TimesNewRoman-Bold",           "Times-Bold" },
00073   { "TimesNewRoman-BoldItalic",     "Times-BoldItalic" },
00074   { "TimesNewRoman-Italic",         "Times-Italic" },
00075   { "TimesNewRomanPS",              "Times-Roman" },
00076   { "TimesNewRomanPS-Bold",         "Times-Bold" },
00077   { "TimesNewRomanPS-BoldItalic",   "Times-BoldItalic" },
00078   { "TimesNewRomanPS-BoldItalicMT", "Times-BoldItalic" },
00079   { "TimesNewRomanPS-BoldMT",       "Times-Bold" },
00080   { "TimesNewRomanPS-Italic",       "Times-Italic" },
00081   { "TimesNewRomanPS-ItalicMT",     "Times-Italic" },
00082   { "TimesNewRomanPSMT",            "Times-Roman" }
00083 };
00084 
00085 //------------------------------------------------------------------------
00086 // GfxFont
00087 //------------------------------------------------------------------------
00088 
00089 GfxFont *GfxFont::makeFont(XRef *xref, char *tagA, Ref idA, Dict *fontDict) {
00090   GString *nameA;
00091   GfxFont *font;
00092   Object obj1;
00093 
00094   // get base font name
00095   nameA = NULL;
00096   fontDict->lookup("BaseFont", &obj1);
00097   if (obj1.isName()) {
00098     nameA = new GString(obj1.getName());
00099   }
00100   obj1.free();
00101 
00102   // get font type
00103   font = NULL;
00104   fontDict->lookup("Subtype", &obj1);
00105   if (obj1.isName("Type1") || obj1.isName("MMType1")) {
00106     font = new Gfx8BitFont(xref, tagA, idA, nameA, fontType1, fontDict);
00107   } else if (obj1.isName("Type1C")) {
00108     font = new Gfx8BitFont(xref, tagA, idA, nameA, fontType1C, fontDict);
00109   } else if (obj1.isName("Type3")) {
00110     font = new Gfx8BitFont(xref, tagA, idA, nameA, fontType3, fontDict);
00111   } else if (obj1.isName("TrueType")) {
00112     font = new Gfx8BitFont(xref, tagA, idA, nameA, fontTrueType, fontDict);
00113   } else if (obj1.isName("Type0")) {
00114     font = new GfxCIDFont(xref, tagA, idA, nameA, fontDict);
00115   } else {
00116     error(-1, "Unknown font type: '%s'",
00117           obj1.isName() ? obj1.getName() : "???");
00118     font = new Gfx8BitFont(xref, tagA, idA, nameA, fontUnknownType, fontDict);
00119   }
00120   obj1.free();
00121 
00122   return font;
00123 }
00124 
00125 GfxFont::GfxFont(char *tagA, Ref idA, GString *nameA) {
00126   ok = gFalse;
00127   tag = new GString(tagA);
00128   id = idA;
00129   name = nameA;
00130   embFontName = NULL;
00131   extFontFile = NULL;
00132 }
00133 
00134 GfxFont::~GfxFont() {
00135   delete tag;
00136   if (name) {
00137     delete name;
00138   }
00139   if (embFontName) {
00140     delete embFontName;
00141   }
00142   if (extFontFile) {
00143     delete extFontFile;
00144   }
00145 }
00146 
00147 void GfxFont::readFontDescriptor(XRef *xref, Dict *fontDict) {
00148   Object obj1, obj2, obj3, obj4;
00149   fouble t;
00150   int i;
00151 
00152   // assume Times-Roman by default (for substitution purposes)
00153   flags = fontSerif;
00154 
00155   embFontID.num = -1;
00156   embFontID.gen = -1;
00157   missingWidth = 0;
00158 
00159   if (fontDict->lookup("FontDescriptor", &obj1)->isDict()) {
00160 
00161     // get flags
00162     if (obj1.dictLookup("Flags", &obj2)->isInt()) {
00163       flags = obj2.getInt();
00164     }
00165     obj2.free();
00166 
00167     // get name
00168     obj1.dictLookup("FontName", &obj2);
00169     if (obj2.isName()) {
00170       embFontName = new GString(obj2.getName());
00171     }
00172     obj2.free();
00173 
00174     // look for embedded font file
00175     if (obj1.dictLookupNF("FontFile", &obj2)->isRef()) {
00176       if (type == fontType1) {
00177         embFontID = obj2.getRef();
00178       } else {
00179         error(-1, "Mismatch between font type and embedded font file");
00180       }
00181     }
00182     obj2.free();
00183     if (embFontID.num == -1 &&
00184         obj1.dictLookupNF("FontFile2", &obj2)->isRef()) {
00185       if (type == fontTrueType || type == fontCIDType2) {
00186         embFontID = obj2.getRef();
00187       } else {
00188         error(-1, "Mismatch between font type and embedded font file");
00189       }
00190     }
00191     obj2.free();
00192     if (embFontID.num == -1 &&
00193         obj1.dictLookupNF("FontFile3", &obj2)->isRef()) {
00194       if (obj2.fetch(xref, &obj3)->isStream()) {
00195         obj3.streamGetDict()->lookup("Subtype", &obj4);
00196         if (obj4.isName("Type1")) {
00197           if (type == fontType1) {
00198             embFontID = obj2.getRef();
00199           } else {
00200             error(-1, "Mismatch between font type and embedded font file");
00201           }
00202         } else if (obj4.isName("Type1C")) {
00203           if (type == fontType1) {
00204             type = fontType1C;
00205             embFontID = obj2.getRef();
00206           } else if (type == fontType1C) {
00207             embFontID = obj2.getRef();
00208           } else {
00209             error(-1, "Mismatch between font type and embedded font file");
00210           }
00211         } else if (obj4.isName("TrueType")) {
00212           if (type == fontTrueType) {
00213             embFontID = obj2.getRef();
00214           } else {
00215             error(-1, "Mismatch between font type and embedded font file");
00216           }
00217         } else if (obj4.isName("CIDFontType0C")) {
00218           if (type == fontCIDType0) {
00219             type = fontCIDType0C;
00220             embFontID = obj2.getRef();
00221           } else {
00222             error(-1, "Mismatch between font type and embedded font file");
00223           }
00224         } else {
00225           error(-1, "Unknown embedded font type '%s'",
00226                 obj4.isName() ? obj4.getName() : "???");
00227         }
00228         obj4.free();
00229       }
00230       obj3.free();
00231     }
00232     obj2.free();
00233 
00234     // look for MissingWidth
00235     obj1.dictLookup("MissingWidth", &obj2);
00236     if (obj2.isNum()) {
00237       missingWidth = obj2.getNum();
00238     }
00239     obj2.free();
00240 
00241     // get Ascent and Descent
00242     obj1.dictLookup("Ascent", &obj2);
00243     if (obj2.isNum()) {
00244       t = 0.001 * obj2.getNum();
00245       // some broken font descriptors set ascent and descent to 0
00246       if (t != 0) {
00247         ascent = t;
00248       }
00249     }
00250     obj2.free();
00251     obj1.dictLookup("Descent", &obj2);
00252     if (obj2.isNum()) {
00253       t = 0.001 * obj2.getNum();
00254       // some broken font descriptors set ascent and descent to 0
00255       if (t != 0) {
00256         descent = t;
00257       }
00258     }
00259     obj2.free();
00260 
00261     // font FontBBox
00262     if (obj1.dictLookup("FontBBox", &obj2)->isArray()) {
00263       for (i = 0; i < 4 && i < obj2.arrayGetLength(); ++i) {
00264         if (obj2.arrayGet(i, &obj3)->isNum()) {
00265           fontBBox[i] = 0.001 * obj3.getNum();
00266         }
00267         obj3.free();
00268       }
00269     }
00270     obj2.free();
00271 
00272   }
00273   obj1.free();
00274 }
00275 
00276 CharCodeToUnicode *GfxFont::readToUnicodeCMap(Dict *fontDict, int nBits) {
00277   CharCodeToUnicode *ctu;
00278   GString *buf;
00279   Object obj1;
00280   int c;
00281 
00282   if (!fontDict->lookup("ToUnicode", &obj1)->isStream()) {
00283     obj1.free();
00284     return NULL;
00285   }
00286   buf = new GString();
00287   obj1.streamReset();
00288   while ((c = obj1.streamGetChar()) != EOF) {
00289     buf->append(c);
00290   }
00291   obj1.streamClose();
00292   obj1.free();
00293   ctu = CharCodeToUnicode::parseCMap(buf, nBits);
00294   delete buf;
00295   return ctu;
00296 }
00297 
00298 void GfxFont::findExtFontFile() {
00299   if (name) {
00300     if (type == fontType1) {
00301       extFontFile = globalParams->findFontFile(name, ".pfa", ".pfb");
00302     } else if (type == fontTrueType) {
00303       extFontFile = globalParams->findFontFile(name, ".ttf", NULL);
00304     }
00305   }
00306 }
00307 
00308 char *GfxFont::readExtFontFile(int *len) {
00309   FILE *f;
00310   char *buf;
00311 
00312   if (!(f = fopen(extFontFile->getCString(), "rb"))) {
00313     error(-1, "External font file '%s' vanished", extFontFile->getCString());
00314     return NULL;
00315   }
00316   fseek(f, 0, SEEK_END);
00317   *len = (int)ftell(f);
00318   fseek(f, 0, SEEK_SET);
00319   buf = (char *)gmalloc(*len);
00320   if ((int)fread(buf, 1, *len, f) != *len) {
00321     error(-1, "Error reading external font file '%s'", extFontFile);
00322   }
00323   fclose(f);
00324   return buf;
00325 }
00326 
00327 char *GfxFont::readEmbFontFile(XRef *xref, int *len) {
00328   char *buf;
00329   Object obj1, obj2;
00330   Stream *str;
00331   int c;
00332   int size, i;
00333 
00334   obj1.initRef(embFontID.num, embFontID.gen);
00335   obj1.fetch(xref, &obj2);
00336   if (!obj2.isStream()) {
00337     error(-1, "Embedded font file is not a stream");
00338     obj2.free();
00339     obj1.free();
00340     embFontID.num = -1;
00341     return NULL;
00342   }
00343   str = obj2.getStream();
00344 
00345   buf = NULL;
00346   i = size = 0;
00347   str->reset();
00348   while ((c = str->getChar()) != EOF) {
00349     if (i == size) {
00350       size += 4096;
00351       buf = (char *)grealloc(buf, size);
00352     }
00353     buf[i++] = c;
00354   }
00355   *len = i;
00356   str->close();
00357 
00358   obj2.free();
00359   obj1.free();
00360 
00361   return buf;
00362 }
00363 
00364 //------------------------------------------------------------------------
00365 // Gfx8BitFont
00366 //------------------------------------------------------------------------
00367 
00368 Gfx8BitFont::Gfx8BitFont(XRef *xref, char *tagA, Ref idA, GString *nameA,
00369                          GfxFontType typeA, Dict *fontDict):
00370   GfxFont(tagA, idA, nameA)
00371 {
00372   BuiltinFont *builtinFont;
00373   char **baseEnc;
00374   GBool baseEncFromFontFile;
00375   char *buf;
00376   int len;
00377   FontFile *fontFile;
00378   int code, code2;
00379   char *charName;
00380   GBool missing, hex;
00381   Unicode toUnicode[256];
00382   fouble mul;
00383   int firstChar, lastChar;
00384   Gushort w;
00385   Object obj1, obj2, obj3;
00386   int n, i, a, b, m;
00387 
00388   type = typeA;
00389   ctu = NULL;
00390 
00391   // Acrobat 4.0 and earlier substituted Base14-compatible fonts
00392   // without providing Widths and a FontDescriptor, so we munge the
00393   // names into the proper Base14 names.  (This table is from
00394   // implementation note 44 in the PDF 1.4 spec.)
00395   if (name) {
00396     a = 0;
00397     b = sizeof(stdFontMap) / sizeof(StdFontMapEntry);
00398     // invariant: stdFontMap[a].altName <= name < stdFontMap[b].altName
00399     while (b - a > 1) {
00400       m = (a + b) / 2;
00401       if (name->cmp(stdFontMap[m].altName) >= 0) {
00402         a = m;
00403       } else {
00404         b = m;
00405       }
00406     }
00407     if (!name->cmp(stdFontMap[a].altName)) {
00408       delete name;
00409       name = new GString(stdFontMap[a].properName);
00410     }
00411   }
00412 
00413   // is it a built-in font?
00414   builtinFont = NULL;
00415   if (name) {
00416     for (i = 0; i < nBuiltinFonts; ++i) {
00417       if (!name->cmp(builtinFonts[i].name)) {
00418         builtinFont = &builtinFonts[i];
00419         break;
00420       }
00421     }
00422   }
00423 
00424   // default ascent/descent values
00425   if (builtinFont) {
00426     ascent = 0.001 * builtinFont->ascent;
00427     descent = 0.001 * builtinFont->descent;
00428     fontBBox[0] = 0.001 * builtinFont->bbox[0];
00429     fontBBox[1] = 0.001 * builtinFont->bbox[1];
00430     fontBBox[2] = 0.001 * builtinFont->bbox[2];
00431     fontBBox[3] = 0.001 * builtinFont->bbox[3];
00432   } else {
00433     ascent = 0.95;
00434     descent = -0.35;
00435     fontBBox[0] = fontBBox[1] = fontBBox[2] = fontBBox[3] = 0;
00436   }
00437 
00438   // get info from font descriptor
00439   readFontDescriptor(xref, fontDict);
00440 
00441   // look for an external font file
00442   findExtFontFile();
00443 
00444   // get font matrix
00445   fontMat[0] = fontMat[3] = 1;
00446   fontMat[1] = fontMat[2] = fontMat[4] = fontMat[5] = 0;
00447   if (fontDict->lookup("FontMatrix", &obj1)->isArray()) {
00448     for (i = 0; i < 6 && i < obj1.arrayGetLength(); ++i) {
00449       if (obj1.arrayGet(i, &obj2)->isNum()) {
00450         fontMat[i] = obj2.getNum();
00451       }
00452       obj2.free();
00453     }
00454   }
00455   obj1.free();
00456 
00457   // get Type 3 bounding box, font definition, and resources
00458   if (type == fontType3) {
00459     if (fontDict->lookup("FontBBox", &obj1)->isArray()) {
00460       for (i = 0; i < 4 && i < obj1.arrayGetLength(); ++i) {
00461         if (obj1.arrayGet(i, &obj2)->isNum()) {
00462           fontBBox[i] = obj2.getNum();
00463         }
00464         obj2.free();
00465       }
00466     }
00467     obj1.free();
00468     if (!fontDict->lookup("CharProcs", &charProcs)->isDict()) {
00469       error(-1, "Missing or invalid CharProcs dictionary in Type 3 font");
00470       charProcs.free();
00471     }
00472     if (!fontDict->lookup("Resources", &resources)->isDict()) {
00473       resources.free();
00474     }
00475   }
00476 
00477   //----- build the font encoding -----
00478 
00479   // Encodings start with a base encoding, which can come from
00480   // (in order of priority):
00481   //   1. FontDict.Encoding or FontDict.Encoding.BaseEncoding
00482   //        - MacRoman / MacExpert / WinAnsi / Standard
00483   //   2. embedded or external font file
00484   //   3. default:
00485   //        - builtin --> builtin encoding
00486   //        - TrueType --> MacRomanEncoding
00487   //        - others --> StandardEncoding
00488   // and then add a list of differences (if any) from
00489   // FontDict.Encoding.Differences.
00490 
00491   // check FontDict for base encoding
00492   hasEncoding = gFalse;
00493   baseEnc = NULL;
00494   baseEncFromFontFile = gFalse;
00495   fontDict->lookup("Encoding", &obj1);
00496   if (obj1.isDict()) {
00497     obj1.dictLookup("BaseEncoding", &obj2);
00498     if (obj2.isName("MacRomanEncoding")) {
00499       hasEncoding = gTrue;
00500       baseEnc = macRomanEncoding;
00501     } else if (obj2.isName("MacExpertEncoding")) {
00502       hasEncoding = gTrue;
00503       baseEnc = macExpertEncoding;
00504     } else if (obj2.isName("WinAnsiEncoding")) {
00505       hasEncoding = gTrue;
00506       baseEnc = winAnsiEncoding;
00507     } else if (obj2.isName("StandardEncoding")) {
00508       hasEncoding = gTrue;
00509       baseEnc = standardEncoding;
00510     }
00511     obj2.free();
00512   } else if (obj1.isName("MacRomanEncoding")) {
00513     hasEncoding = gTrue;
00514     baseEnc = macRomanEncoding;
00515   } else if (obj1.isName("MacExpertEncoding")) {
00516     hasEncoding = gTrue;
00517     baseEnc = macExpertEncoding;
00518   } else if (obj1.isName("WinAnsiEncoding")) {
00519     hasEncoding = gTrue;
00520     baseEnc = winAnsiEncoding;
00521   } else if (obj1.isName("StandardEncoding")) {
00522     hasEncoding = gTrue;
00523     baseEnc = standardEncoding;
00524   }
00525 
00526   // check embedded or external font file for base encoding
00527   // (only for Type 1 fonts - trying to get an encoding out of a
00528   // TrueType font is a losing proposition)
00529   fontFile = NULL;
00530   buf = NULL;
00531   if ((type == fontType1 || type == fontType1C) &&
00532       (extFontFile || embFontID.num >= 0)) {
00533     if (extFontFile) {
00534       buf = readExtFontFile(&len);
00535     } else {
00536       buf = readEmbFontFile(xref, &len);
00537     }
00538     if (buf) {
00539 #if 0
00540       if (type == fontType1C && !strncmp(buf, "%!", 2)) {
00541         // various tools (including Adobe's) occasionally embed Type 1
00542         // fonts but label them Type 1C
00543         type = fontType1;
00544       }
00545       if (type == fontType1) {
00546         fontFile = new Type1FontFile(buf, len);
00547       } else {
00548         fontFile = new Type1CFontFile(buf, len);
00549       }
00550       if (fontFile->getName()) {
00551         if (embFontName) {
00552           delete embFontName;
00553         }
00554         embFontName = new GString(fontFile->getName());
00555       }
00556       if (!baseEnc) {
00557         baseEnc = fontFile->getEncoding();
00558         baseEncFromFontFile = gTrue;
00559       }
00560 #endif
00561       gfree(buf);
00562     }
00563   }
00564 
00565   // get default base encoding
00566   if (!baseEnc) {
00567     if (builtinFont) {
00568       baseEnc = builtinFont->defaultBaseEnc;
00569     } else if (type == fontTrueType) {
00570       baseEnc = macRomanEncoding;
00571     } else {
00572       baseEnc = standardEncoding;
00573     }
00574   }
00575 
00576   // copy the base encoding
00577   for (i = 0; i < 256; ++i) {
00578     enc[i] = baseEnc[i];
00579     if ((encFree[i] = baseEncFromFontFile) && enc[i]) {
00580       enc[i] = copyString(baseEnc[i]);
00581     }
00582   }
00583 
00584   // merge differences into encoding
00585   if (obj1.isDict()) {
00586     obj1.dictLookup("Differences", &obj2);
00587     if (obj2.isArray()) {
00588       hasEncoding = gTrue;
00589       code = 0;
00590       for (i = 0; i < obj2.arrayGetLength(); ++i) {
00591         obj2.arrayGet(i, &obj3);
00592         if (obj3.isInt()) {
00593           code = obj3.getInt();
00594         } else if (obj3.isName()) {
00595           if (code < 256) {
00596             if (encFree[code]) {
00597               gfree(enc[code]);
00598             }
00599             enc[code] = copyString(obj3.getName());
00600             encFree[code] = gTrue;
00601           }
00602           ++code;
00603         } else {
00604           error(-1, "Wrong type in font encoding resource differences (%s)",
00605                 obj3.getTypeName());
00606         }
00607         obj3.free();
00608       }
00609     }
00610     obj2.free();
00611   }
00612   obj1.free();
00613   if (fontFile) {
00614     delete fontFile;
00615   }
00616 
00617   //----- build the mapping to Unicode -----
00618 
00619   // look for a ToUnicode CMap
00620   if (!(ctu = readToUnicodeCMap(fontDict, 8))) {
00621 
00622     // no ToUnicode CMap, so use the char names
00623 
00624     // pass 1: use the name-to-Unicode mapping table
00625     missing = hex = gFalse;
00626     for (code = 0; code < 256; ++code) {
00627       if ((charName = enc[code])) {
00628         if (!(toUnicode[code] = globalParams->mapNameToUnicode(charName)) &&
00629             strcmp(charName, ".notdef")) {
00630           // if it wasn't in the name-to-Unicode table, check for a
00631           // name that looks like 'Axx' or 'xx', where 'A' is any letter
00632           // and 'xx' is two hex digits
00633           if ((strlen(charName) == 3 &&
00634                isalpha(charName[0]) &&
00635                isxdigit(charName[1]) && isxdigit(charName[2]) &&
00636                ((charName[1] >= 'a' && charName[1] <= 'f') ||
00637                 (charName[1] >= 'A' && charName[1] <= 'F') ||
00638                 (charName[2] >= 'a' && charName[2] <= 'f') ||
00639                 (charName[2] >= 'A' && charName[2] <= 'F'))) ||
00640               (strlen(charName) == 2 &&
00641                isxdigit(charName[0]) && isxdigit(charName[1]) &&
00642                ((charName[0] >= 'a' && charName[0] <= 'f') ||
00643                 (charName[0] >= 'A' && charName[0] <= 'F') ||
00644                 (charName[1] >= 'a' && charName[1] <= 'f') ||
00645                 (charName[1] >= 'A' && charName[1] <= 'F')))) {
00646             hex = gTrue;
00647           }
00648           missing = gTrue;
00649         }
00650       } else {
00651         toUnicode[code] = 0;
00652       }
00653     }
00654 
00655     // pass 2: try to fill in the missing chars, looking for names of
00656     // the form 'Axx', 'xx', 'Ann', 'ABnn', or 'nn', where 'A' and 'B'
00657     // are any letters, 'xx' is two hex digits, and 'nn' is 2-4
00658     // decimal digits
00659     if (missing && globalParams->getMapNumericCharNames()) {
00660       for (code = 0; code < 256; ++code) {
00661         if ((charName = enc[code]) && !toUnicode[code] &&
00662             strcmp(charName, ".notdef")) {
00663           n = strlen(charName);
00664           code2 = -1;
00665           if (hex && n == 3 && isalpha(charName[0]) &&
00666               isxdigit(charName[1]) && isxdigit(charName[2])) {
00667             sscanf(charName+1, "%x", &code2);
00668           } else if (hex && n == 2 &&
00669                      isxdigit(charName[0]) && isxdigit(charName[1])) {
00670             sscanf(charName, "%x", &code2);
00671           } else if (!hex && n >= 2 && n <= 4 &&
00672                      isdigit(charName[0]) && isdigit(charName[1])) {
00673             code2 = atoi(charName);
00674           } else if (n >= 3 && n <= 5 &&
00675                      isdigit(charName[1]) && isdigit(charName[2])) {
00676             code2 = atoi(charName+1);
00677           } else if (n >= 4 && n <= 6 &&
00678                      isdigit(charName[2]) && isdigit(charName[3])) {
00679             code2 = atoi(charName+2);
00680           }
00681           if (code2 >= 0 && code2 <= 0xff) {
00682             toUnicode[code] = (Unicode)code2;
00683           }
00684         }
00685       }
00686     }
00687 
00688     ctu = CharCodeToUnicode::make8BitToUnicode(toUnicode);
00689   }
00690 
00691   //----- get the character widths -----
00692 
00693   // initialize all widths
00694   for (code = 0; code < 256; ++code) {
00695     widths[code] = missingWidth * 0.001;
00696   }
00697 
00698   // use widths from font dict, if present
00699   fontDict->lookup("FirstChar", &obj1);
00700   firstChar = obj1.isInt() ? obj1.getInt() : 0;
00701   obj1.free();
00702   fontDict->lookup("LastChar", &obj1);
00703   lastChar = obj1.isInt() ? obj1.getInt() : 255;
00704   obj1.free();
00705   mul = (type == fontType3) ? fontMat[0] : fouble(0.001);
00706   fontDict->lookup("Widths", &obj1);
00707   if (obj1.isArray()) {
00708     flags |= fontFixedWidth;
00709     for (code = firstChar; code <= lastChar; ++code) {
00710       obj1.arrayGet(code - firstChar, &obj2);
00711       if (obj2.isNum()) {
00712         widths[code] = obj2.getNum() * mul;
00713         if (widths[code] != widths[firstChar]) {
00714           flags &= ~fontFixedWidth;
00715         }
00716       }
00717       obj2.free();
00718     }
00719 
00720   // use widths from built-in font
00721   } else if (builtinFont) {
00722     // this is a kludge for broken PDF files that encode char 32
00723     // as .notdef
00724     if (builtinFont->widths->getWidth("space", &w)) {
00725       widths[32] = 0.001 * w;
00726     }
00727     for (code = 0; code < 256; ++code) {
00728       if (enc[code] && builtinFont->widths->getWidth(enc[code], &w)) {
00729         widths[code] = 0.001 * w;
00730       }
00731     }
00732 
00733   // couldn't find widths -- use defaults 
00734   } else {
00735     // this is technically an error -- the Widths entry is required
00736     // for all but the Base-14 fonts -- but certain PDF generators
00737     // apparently don't include widths for Arial and TimesNewRoman
00738     if (isFixedWidth()) {
00739       i = 0;
00740     } else if (isSerif()) {
00741       i = 8;
00742     } else {
00743       i = 4;
00744     }
00745     if (isBold()) {
00746       i += 2;
00747     }
00748     if (isItalic()) {
00749       i += 1;
00750     }
00751     builtinFont = builtinFontSubst[i];
00752     // this is a kludge for broken PDF files that encode char 32
00753     // as .notdef
00754     if (builtinFont->widths->getWidth("space", &w)) {
00755       widths[32] = 0.001 * w;
00756     }
00757     for (code = 0; code < 256; ++code) {
00758       if (enc[code] && builtinFont->widths->getWidth(enc[code], &w)) {
00759         widths[code] = 0.001 * w;
00760       }
00761     }
00762   }
00763   obj1.free();
00764 
00765   ok = gTrue;
00766 }
00767 
00768 Gfx8BitFont::~Gfx8BitFont() {
00769   int i;
00770 
00771   for (i = 0; i < 256; ++i) {
00772     if (encFree[i] && enc[i]) {
00773       gfree(enc[i]);
00774     }
00775   }
00776   ctu->decRefCnt();
00777   if (charProcs.isDict()) {
00778     charProcs.free();
00779   }
00780   if (resources.isDict()) {
00781     resources.free();
00782   }
00783 }
00784 
00785 int Gfx8BitFont::getNextChar(char *s, int len, CharCode *code,
00786                              Unicode *u, int uSize, int *uLen,
00787                              fouble *dx, fouble *dy, fouble *ox, fouble *oy) {
00788   CharCode c;
00789 
00790   *code = c = (CharCode)(*s & 0xff);
00791   *uLen = ctu->mapToUnicode(c, u, uSize);
00792   *dx = widths[c];
00793   *dy = *ox = *oy = 0;
00794   return 1;
00795 }
00796 
00797 CharCodeToUnicode *Gfx8BitFont::getToUnicode() {
00798   ctu->incRefCnt();
00799   return ctu;
00800 }
00801 
00802 Dict *Gfx8BitFont::getCharProcs() {
00803   return charProcs.isDict() ? charProcs.getDict() : (Dict *)NULL;
00804 }
00805 
00806 Object *Gfx8BitFont::getCharProc(int code, Object *proc) {
00807   if (charProcs.isDict()) {
00808     charProcs.dictLookup(enc[code], proc);
00809   } else {
00810     proc->initNull();
00811   }
00812   return proc;
00813 }
00814 
00815 Dict *Gfx8BitFont::getResources() {
00816   return resources.isDict() ? resources.getDict() : (Dict *)NULL;
00817 }
00818 
00819 //------------------------------------------------------------------------
00820 // GfxCIDFont
00821 //------------------------------------------------------------------------
00822 
00823 static int cmpWidthExcep(const void *w1, const void *w2) {
00824   return ((GfxFontCIDWidthExcep *)w1)->first -
00825          ((GfxFontCIDWidthExcep *)w2)->first;
00826 }
00827 
00828 static int cmpWidthExcepV(const void *w1, const void *w2) {
00829   return ((GfxFontCIDWidthExcepV *)w1)->first -
00830          ((GfxFontCIDWidthExcepV *)w2)->first;
00831 }
00832 
00833 GfxCIDFont::GfxCIDFont(XRef *xref, char *tagA, Ref idA, GString *nameA,
00834                        Dict *fontDict):
00835   GfxFont(tagA, idA, nameA)
00836 {
00837   Dict *desFontDict;
00838   GString *collection, *cMapName;
00839   Object desFontDictObj;
00840   Object obj1, obj2, obj3, obj4, obj5, obj6;
00841   int c1, c2;
00842   int excepsSize, i, j, k;
00843 
00844   ascent = 0.95;
00845   descent = -0.35;
00846   fontBBox[0] = fontBBox[1] = fontBBox[2] = fontBBox[3] = 0;
00847   cMap = NULL;
00848   ctu = NULL;
00849   widths.defWidth = 1.0;
00850   widths.defHeight = -1.0;
00851   widths.defVY = 0.880;
00852   widths.exceps = NULL;
00853   widths.nExceps = 0;
00854   widths.excepsV = NULL;
00855   widths.nExcepsV = 0;
00856   cidToGID = NULL;
00857   cidToGIDLen = 0;
00858 
00859   // get the descendant font
00860   if (!fontDict->lookup("DescendantFonts", &obj1)->isArray()) {
00861     error(-1, "Missing DescendantFonts entry in Type 0 font");
00862     obj1.free();
00863     goto err1;
00864   }
00865   if (!obj1.arrayGet(0, &desFontDictObj)->isDict()) {
00866     error(-1, "Bad descendant font in Type 0 font");
00867     goto err3;
00868   }
00869   obj1.free();
00870   desFontDict = desFontDictObj.getDict();
00871 
00872   // font type
00873   if (!desFontDict->lookup("Subtype", &obj1)) {
00874     error(-1, "Missing Subtype entry in Type 0 descendant font");
00875     goto err3;
00876   }
00877   if (obj1.isName("CIDFontType0")) {
00878     type = fontCIDType0;
00879   } else if (obj1.isName("CIDFontType2")) {
00880     type = fontCIDType2;
00881   } else {
00882     error(-1, "Unknown Type 0 descendant font type '%s'",
00883           obj1.isName() ? obj1.getName() : "???");
00884     goto err3;
00885   }
00886   obj1.free();
00887 
00888   // get info from font descriptor
00889   readFontDescriptor(xref, desFontDict);
00890 
00891   // look for an external font file
00892   findExtFontFile();
00893 
00894   //----- encoding info -----
00895 
00896   // char collection
00897   if (!desFontDict->lookup("CIDSystemInfo", &obj1)->isDict()) {
00898     error(-1, "Missing CIDSystemInfo dictionary in Type 0 descendant font");
00899     goto err3;
00900   }
00901   obj1.dictLookup("Registry", &obj2);
00902   obj1.dictLookup("Ordering", &obj3);
00903   if (!obj2.isString() || !obj3.isString()) {
00904     error(-1, "Invalid CIDSystemInfo dictionary in Type 0 descendant font");
00905     goto err4;
00906   }
00907   collection = obj2.getString()->copy()->append('-')->append(obj3.getString());
00908   obj3.free();
00909   obj2.free();
00910   obj1.free();
00911 
00912   // look for a ToUnicode CMap
00913   if (!(ctu = readToUnicodeCMap(fontDict, 16))) {
00914 
00915     // the "Adobe-Identity" and "Adobe-UCS" collections don't have
00916     // cidToUnicode files
00917     if (collection->cmp("Adobe-Identity") &&
00918         collection->cmp("Adobe-UCS")) {
00919 
00920       // look for a user-supplied .cidToUnicode file
00921       if (!(ctu = globalParams->getCIDToUnicode(collection))) {
00922         error(-1, "Unknown character collection '%s'",
00923               collection->getCString());
00924         delete collection;
00925         goto err2;
00926       }
00927     }
00928   }
00929 
00930   // encoding (i.e., CMap)
00931   //~ need to handle a CMap stream here
00932   //~ also need to deal with the UseCMap entry in the stream dict
00933   if (!fontDict->lookup("Encoding", &obj1)->isName()) {
00934     error(-1, "Missing or invalid Encoding entry in Type 0 font");
00935     delete collection;
00936     goto err3;
00937   }
00938   cMapName = new GString(obj1.getName());
00939   obj1.free();
00940   if (!(cMap = globalParams->getCMap(collection, cMapName))) {
00941     error(-1, "Unknown CMap '%s' for character collection '%s'",
00942           cMapName->getCString(), collection->getCString());
00943     delete collection;
00944     delete cMapName;
00945     goto err2;
00946   }
00947   delete collection;
00948   delete cMapName;
00949 
00950   // CIDToGIDMap (for embedded TrueType fonts)
00951   if (type == fontCIDType2) {
00952     fontDict->lookup("CIDToGIDMap", &obj1);
00953     if (obj1.isStream()) {
00954       cidToGIDLen = 0;
00955       i = 64;
00956       cidToGID = (Gushort *)gmalloc(i * sizeof(Gushort));
00957       obj1.streamReset();
00958       while ((c1 = obj1.streamGetChar()) != EOF &&
00959              (c2 = obj1.streamGetChar()) != EOF) {
00960         if (cidToGIDLen == i) {
00961           i *= 2;
00962           cidToGID = (Gushort *)grealloc(cidToGID, i * sizeof(Gushort));
00963         }
00964         cidToGID[cidToGIDLen++] = (Gushort)((c1 << 8) + c2);
00965       }
00966     } else if (!obj1.isName("Identity") && !obj1.isNull()) {
00967       error(-1, "Invalid CIDToGIDMap entry in CID font");
00968     }
00969     obj1.free();
00970   }
00971 
00972   //----- character metrics -----
00973 
00974   // default char width
00975   if (desFontDict->lookup("DW", &obj1)->isInt()) {
00976     widths.defWidth = obj1.getInt() * 0.001;
00977   }
00978   obj1.free();
00979 
00980   // char width exceptions
00981   if (desFontDict->lookup("W", &obj1)->isArray()) {
00982     excepsSize = 0;
00983     i = 0;
00984     while (i + 1 < obj1.arrayGetLength()) {
00985       obj1.arrayGet(i, &obj2);
00986       obj1.arrayGet(i + 1, &obj3);
00987       if (obj2.isInt() && obj3.isInt() && i + 2 < obj1.arrayGetLength()) {
00988         if (obj1.arrayGet(i + 2, &obj4)->isNum()) {
00989           if (widths.nExceps == excepsSize) {
00990             excepsSize += 16;
00991             widths.exceps = (GfxFontCIDWidthExcep *)
00992               grealloc(widths.exceps,
00993                        excepsSize * sizeof(GfxFontCIDWidthExcep));
00994           }
00995           widths.exceps[widths.nExceps].first = obj2.getInt();
00996           widths.exceps[widths.nExceps].last = obj3.getInt();
00997           widths.exceps[widths.nExceps].width = obj4.getNum() * 0.001;
00998           ++widths.nExceps;
00999         } else {
01000           error(-1, "Bad widths array in Type 0 font");
01001         }
01002         obj4.free();
01003         i += 3;
01004       } else if (obj2.isInt() && obj3.isArray()) {
01005         if (widths.nExceps + obj3.arrayGetLength() > excepsSize) {
01006           excepsSize = (widths.nExceps + obj3.arrayGetLength() + 15) & ~15;
01007           widths.exceps = (GfxFontCIDWidthExcep *)
01008             grealloc(widths.exceps,
01009                      excepsSize * sizeof(GfxFontCIDWidthExcep));
01010         }
01011         j = obj2.getInt();
01012         for (k = 0; k < obj3.arrayGetLength(); ++k) {
01013           if (obj3.arrayGet(k, &obj4)->isNum()) {
01014             widths.exceps[widths.nExceps].first = j;
01015             widths.exceps[widths.nExceps].last = j;
01016             widths.exceps[widths.nExceps].width = obj4.getNum() * 0.001;
01017             ++j;
01018             ++widths.nExceps;
01019           } else {
01020             error(-1, "Bad widths array in Type 0 font");
01021           }
01022           obj4.free();
01023         }
01024         i += 2;
01025       } else {
01026         error(-1, "Bad widths array in Type 0 font");
01027         ++i;
01028       }
01029       obj3.free();
01030       obj2.free();
01031     }
01032     qsort(widths.exceps, widths.nExceps, sizeof(GfxFontCIDWidthExcep),
01033           &cmpWidthExcep);
01034   }
01035   obj1.free();
01036 
01037   // default metrics for vertical font
01038   if (desFontDict->lookup("DW2", &obj1)->isArray() &&
01039       obj1.arrayGetLength() == 2) {
01040     if (obj1.arrayGet(0, &obj2)->isNum()) {
01041       widths.defVY = obj1.getNum() * 0.001;
01042     }
01043     obj2.free();
01044     if (obj1.arrayGet(1, &obj2)->isNum()) {
01045       widths.defHeight = obj1.getNum() * 0.001;
01046     }
01047     obj2.free();
01048   }
01049   obj1.free();
01050 
01051   // char metric exceptions for vertical font
01052   if (desFontDict->lookup("W2", &obj1)->isArray()) {
01053     excepsSize = 0;
01054     i = 0;
01055     while (i + 1 < obj1.arrayGetLength()) {
01056       obj1.arrayGet(0, &obj2);
01057       obj2.arrayGet(0, &obj3);
01058       if (obj2.isInt() && obj3.isInt() && i + 4 < obj1.arrayGetLength()) {
01059         if (obj1.arrayGet(i + 2, &obj4)->isNum() &&
01060             obj1.arrayGet(i + 3, &obj5)->isNum() &&
01061             obj1.arrayGet(i + 4, &obj6)->isNum()) {
01062           if (widths.nExcepsV == excepsSize) {
01063             excepsSize += 16;
01064             widths.excepsV = (GfxFontCIDWidthExcepV *)
01065               grealloc(widths.excepsV,
01066                        excepsSize * sizeof(GfxFontCIDWidthExcepV));
01067           }
01068           widths.excepsV[widths.nExcepsV].first = obj2.getInt();
01069           widths.excepsV[widths.nExcepsV].last = obj3.getInt();
01070           widths.excepsV[widths.nExcepsV].height = obj4.getNum() * 0.001;
01071           widths.excepsV[widths.nExcepsV].vx = obj5.getNum() * 0.001;
01072           widths.excepsV[widths.nExcepsV].vy = obj6.getNum() * 0.001;
01073           ++widths.nExcepsV;
01074         } else {
01075           error(-1, "Bad widths (W2) array in Type 0 font");
01076         }
01077         obj6.free();
01078         obj5.free();
01079         obj4.free();
01080         i += 5;
01081       } else if (obj2.isInt() && obj3.isArray()) {
01082         if (widths.nExcepsV + obj3.arrayGetLength() / 3 > excepsSize) {
01083           excepsSize =
01084             (widths.nExcepsV + obj3.arrayGetLength() / 3 + 15) & ~15;
01085           widths.excepsV = (GfxFontCIDWidthExcepV *)
01086             grealloc(widths.excepsV,
01087                      excepsSize * sizeof(GfxFontCIDWidthExcepV));
01088         }
01089         j = obj2.getInt();
01090         for (k = 0; k < obj3.arrayGetLength(); ++k) {
01091           if (obj3.arrayGet(k, &obj4)->isNum() &&
01092               obj3.arrayGet(k, &obj5)->isNum() &&
01093               obj3.arrayGet(k, &obj6)->isNum()) {
01094             widths.excepsV[widths.nExceps].first = j;
01095             widths.excepsV[widths.nExceps].last = j;
01096             widths.excepsV[widths.nExceps].height = obj4.getNum() * 0.001;
01097             widths.excepsV[widths.nExceps].vx = obj5.getNum() * 0.001;
01098             widths.excepsV[widths.nExceps].vy = obj6.getNum() * 0.001;
01099             ++j;
01100             ++widths.nExcepsV;
01101           } else {
01102             error(-1, "Bad widths (W2) array in Type 0 font");
01103           }
01104           obj6.free();
01105           obj5.free();
01106           obj4.free();
01107         }
01108         i += 2;
01109       } else {
01110         error(-1, "Bad widths (W2) array in Type 0 font");
01111         ++i;
01112       }
01113       obj3.free();
01114       obj2.free();
01115     }
01116     qsort(widths.excepsV, widths.nExcepsV, sizeof(GfxFontCIDWidthExcepV),
01117           &cmpWidthExcepV);
01118   }
01119   obj1.free();
01120 
01121   desFontDictObj.free();
01122   ok = gTrue;
01123   return;
01124 
01125  err4:
01126   obj3.free();
01127   obj2.free();
01128  err3:
01129   obj1.free();
01130  err2:
01131   desFontDictObj.free();
01132  err1:;
01133 }
01134 
01135 GfxCIDFont::~GfxCIDFont() {
01136   if (cMap) {
01137     cMap->decRefCnt();
01138   }
01139   if (ctu) {
01140     ctu->decRefCnt();
01141   }
01142   gfree(widths.exceps);
01143   gfree(widths.excepsV);
01144   if (cidToGID) {
01145     gfree(cidToGID);
01146   }
01147 }
01148 
01149 int GfxCIDFont::getNextChar(char *s, int len, CharCode *code,
01150                             Unicode *u, int uSize, int *uLen,
01151                             fouble *dx, fouble *dy, fouble *ox, fouble *oy) {
01152   CID cid;
01153   fouble w, h, vx, vy;
01154   int n, a, b, m;
01155 
01156   if (!cMap) {
01157     *code = 0;
01158     *uLen = 0;
01159     *dx = *dy = 0;
01160     return 1;
01161   }
01162 
01163   *code = (CharCode)(cid = cMap->getCID(s, len, &n));
01164   if (ctu) {
01165     *uLen = ctu->mapToUnicode(cid, u, uSize);
01166   } else {
01167     *uLen = 0;
01168   }
01169 
01170   // horizontal
01171   if (cMap->getWMode() == 0) {
01172     w = widths.defWidth;
01173     h = vx = vy = 0;
01174     if (widths.nExceps > 0 && cid >= widths.exceps[0].first) {
01175       a = 0;
01176       b = widths.nExceps;
01177       // invariant: widths.exceps[a].first <= cid < widths.exceps[b].first
01178       while (b - a > 1) {
01179         m = (a + b) / 2;
01180         if (widths.exceps[m].first <= cid) {
01181           a = m;
01182         } else {
01183           b = m;
01184         }
01185       }
01186       if (cid <= widths.exceps[a].last) {
01187         w = widths.exceps[a].width;
01188       }
01189     }
01190 
01191   // vertical
01192   } else {
01193     w = 0;
01194     h = widths.defHeight;
01195     vx = widths.defWidth / 2;
01196     vy = widths.defVY;
01197     if (widths.nExcepsV > 0 && cid >= widths.excepsV[0].first) {
01198       a = 0;
01199       b = widths.nExcepsV;
01200       // invariant: widths.excepsV[a].first <= cid < widths.excepsV[b].first
01201       while (b - a > 1) {
01202         m = (a + b) / 2;
01203         if (widths.excepsV[m].last <= cid) {
01204           a = m;
01205         } else {
01206           b = m;
01207         }
01208       }
01209       if (cid <= widths.excepsV[a].last) {
01210         h = widths.excepsV[a].height;
01211         vx = widths.excepsV[a].vx;
01212         vy = widths.excepsV[a].vy;
01213       }
01214     }
01215   }
01216 
01217   *dx = w;
01218   *dy = h;
01219   *ox = vx;
01220   *oy = vy;
01221 
01222   return n;
01223 }
01224 
01225 int GfxCIDFont::getWMode() {
01226   return cMap ? cMap->getWMode() : 0;
01227 }
01228 
01229 CharCodeToUnicode *GfxCIDFont::getToUnicode() {
01230   ctu->incRefCnt();
01231   return ctu;
01232 }
01233 
01234 GString *GfxCIDFont::getCollection() {
01235   return cMap ? cMap->getCollection() : (GString *)NULL;
01236 }
01237 
01238 //------------------------------------------------------------------------
01239 // GfxFontDict
01240 //------------------------------------------------------------------------
01241 
01242 GfxFontDict::GfxFontDict(XRef *xref, Dict *fontDict) {
01243   int i;
01244   Object obj1, obj2;
01245 
01246   numFonts = fontDict->getLength();
01247   fonts = (GfxFont **)gmalloc(numFonts * sizeof(GfxFont *));
01248   for (i = 0; i < numFonts; ++i) {
01249     fontDict->getValNF(i, &obj1);
01250     obj1.fetch(xref, &obj2);
01251     if (obj1.isRef() && obj2.isDict()) {
01252       fonts[i] = GfxFont::makeFont(xref, fontDict->getKey(i),
01253                                    obj1.getRef(), obj2.getDict());
01254       if (fonts[i] && !fonts[i]->isOk()) {
01255         delete fonts[i];
01256         fonts[i] = NULL;
01257       }
01258     } else {
01259       error(-1, "font resource is not a dictionary reference");
01260       fonts[i] = NULL;
01261     }
01262     obj1.free();
01263     obj2.free();
01264   }
01265 }
01266 
01267 GfxFontDict::~GfxFontDict() {
01268   int i;
01269 
01270   for (i = 0; i < numFonts; ++i) {
01271     if (fonts[i]) {
01272       delete fonts[i];
01273     }
01274   }
01275   gfree(fonts);
01276 }
01277 
01278 GfxFont *GfxFontDict::lookup(char *tag) {
01279   int i;
01280 
01281   for (i = 0; i < numFonts; ++i) {
01282     if (fonts[i] && fonts[i]->matches(tag)) {
01283       return fonts[i];
01284     }
01285   }
01286   return NULL;
01287 }

Generated on Sat Nov 5 16:18:14 2005 for OPIE by  doxygen 1.4.2