root/lib/pdf/xpdf/FoFiTrueType.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. cmpTrueTypeLocaOffset
  2. cmpTrueTypeLocaIdx
  3. cmpTrueTypeTableTag
  4. make
  5. load
  6. getNumCmaps
  7. getCmapPlatform
  8. getCmapEncoding
  9. findCmap
  10. mapCodeToGID
  11. mapNameToGID
  12. getCIDToGIDMap
  13. getEmbeddingRights
  14. convertToType42
  15. convertToType1
  16. convertToCIDType2
  17. convertToCIDType0
  18. convertToType0
  19. convertToType0
  20. writeTTF
  21. cvtEncoding
  22. cvtCharStrings
  23. cvtSfnts
  24. dumpString
  25. computeTableChecksum
  26. parse
  27. readPostTable
  28. seekTable

//========================================================================
//
// FoFiTrueType.cc
//
// Copyright 1999-2003 Glyph & Cog, LLC
//
//========================================================================

#include <aconf.h>

#ifdef USE_GCC_PRAGMAS
#pragma implementation
#endif

#include <stdlib.h>
#include <string.h>
#include "gtypes.h"
#include "gmem.h"
#include "GString.h"
#include "GHash.h"
#include "FoFiType1C.h"
#include "FoFiTrueType.h"

//
// Terminology
// -----------
//
// character code = number used as an element of a text string
//
// character name = glyph name = name for a particular glyph within a
//                  font
//
// glyph index = GID = position (within some internal table in the font)
//               where the instructions to draw a particular glyph are
//               stored
//
// Type 1 fonts
// ------------
//
// Type 1 fonts contain:
//
// Encoding: array of glyph names, maps char codes to glyph names
//
//           Encoding[charCode] = charName
//
// CharStrings: dictionary of instructions, keyed by character names,
//              maps character name to glyph data
//
//              CharStrings[charName] = glyphData
//
// TrueType fonts
// --------------
//
// TrueType fonts contain:
//
// 'cmap' table: mapping from character code to glyph index; there may
//               be multiple cmaps in a TrueType font
//
//               cmap[charCode] = gid
//
// 'post' table: mapping from glyph index to glyph name
//
//               post[gid] = glyphName
//
// Type 42 fonts
// -------------
//
// Type 42 fonts contain:
//
// Encoding: array of glyph names, maps char codes to glyph names
//
//           Encoding[charCode] = charName
//
// CharStrings: dictionary of glyph indexes, keyed by character names,
//              maps character name to glyph index
//
//              CharStrings[charName] = gid
//

//------------------------------------------------------------------------

#define ttcfTag 0x74746366

//------------------------------------------------------------------------

struct TrueTypeTable {
  Guint tag;
  Guint checksum;
  int offset;
  int origOffset;
  int len;
};

struct TrueTypeCmap {
  int platform;
  int encoding;
  int offset;
  int len;
  int fmt;
};

struct TrueTypeLoca {
  int idx;
  int origOffset;
  int newOffset;
  int len;
};

#define cmapTag 0x636d6170
#define glyfTag 0x676c7966
#define headTag 0x68656164
#define hheaTag 0x68686561
#define hmtxTag 0x686d7478
#define locaTag 0x6c6f6361
#define nameTag 0x6e616d65
#define os2Tag  0x4f532f32
#define postTag 0x706f7374

static int cmpTrueTypeLocaOffset(const void *p1, const void *p2) {
  TrueTypeLoca *loca1 = (TrueTypeLoca *)p1;
  TrueTypeLoca *loca2 = (TrueTypeLoca *)p2;

  if (loca1->origOffset == loca2->origOffset) {
    return loca1->idx - loca2->idx;
  }
  return loca1->origOffset - loca2->origOffset;
}

static int cmpTrueTypeLocaIdx(const void *p1, const void *p2) {
  TrueTypeLoca *loca1 = (TrueTypeLoca *)p1;
  TrueTypeLoca *loca2 = (TrueTypeLoca *)p2;

  return loca1->idx - loca2->idx;
}

static int cmpTrueTypeTableTag(const void *p1, const void *p2) {
  TrueTypeTable *tab1 = (TrueTypeTable *)p1;
  TrueTypeTable *tab2 = (TrueTypeTable *)p2;

  return (int)tab1->tag - (int)tab2->tag;
}

//------------------------------------------------------------------------

struct T42Table {
  char *tag;                    // 4-byte tag
  GBool required;               // required by the TrueType spec?
};

// TrueType tables to be embedded in Type 42 fonts.
// NB: the table names must be in alphabetical order here.
#define nT42Tables 11
static T42Table t42Tables[nT42Tables] = {
  { "cvt ", gTrue  },
  { "fpgm", gTrue  },
  { "glyf", gTrue  },
  { "head", gTrue  },
  { "hhea", gTrue  },
  { "hmtx", gTrue  },
  { "loca", gTrue  },
  { "maxp", gTrue  },
  { "prep", gTrue  },
  { "vhea", gFalse },
  { "vmtx", gFalse }
};
#define t42HeadTable  3
#define t42LocaTable  6
#define t42GlyfTable  2
#define t42VheaTable  9
#define t42VmtxTable 10

//------------------------------------------------------------------------

// Glyph names in some arbitrary standard order that Apple uses for
// their TrueType fonts.
static char *macGlyphNames[258] = {
  ".notdef",        "null",           "CR",             "space",
  "exclam",         "quotedbl",       "numbersign",     "dollar",
  "percent",        "ampersand",      "quotesingle",    "parenleft",
  "parenright",     "asterisk",       "plus",           "comma",
  "hyphen",         "period",         "slash",          "zero",
  "one",            "two",            "three",          "four",
  "five",           "six",            "seven",          "eight",
  "nine",           "colon",          "semicolon",      "less",
  "equal",          "greater",        "question",       "at",
  "A",              "B",              "C",              "D",
  "E",              "F",              "G",              "H",
  "I",              "J",              "K",              "L",
  "M",              "N",              "O",              "P",
  "Q",              "R",              "S",              "T",
  "U",              "V",              "W",              "X",
  "Y",              "Z",              "bracketleft",    "backslash",
  "bracketright",   "asciicircum",    "underscore",     "grave",
  "a",              "b",              "c",              "d",
  "e",              "f",              "g",              "h",
  "i",              "j",              "k",              "l",
  "m",              "n",              "o",              "p",
  "q",              "r",              "s",              "t",
  "u",              "v",              "w",              "x",
  "y",              "z",              "braceleft",      "bar",
  "braceright",     "asciitilde",     "Adieresis",      "Aring",
  "Ccedilla",       "Eacute",         "Ntilde",         "Odieresis",
  "Udieresis",      "aacute",         "agrave",         "acircumflex",
  "adieresis",      "atilde",         "aring",          "ccedilla",
  "eacute",         "egrave",         "ecircumflex",    "edieresis",
  "iacute",         "igrave",         "icircumflex",    "idieresis",
  "ntilde",         "oacute",         "ograve",         "ocircumflex",
  "odieresis",      "otilde",         "uacute",         "ugrave",
  "ucircumflex",    "udieresis",      "dagger",         "degree",
  "cent",           "sterling",       "section",        "bullet",
  "paragraph",      "germandbls",     "registered",     "copyright",
  "trademark",      "acute",          "dieresis",       "notequal",
  "AE",             "Oslash",         "infinity",       "plusminus",
  "lessequal",      "greaterequal",   "yen",            "mu1",
  "partialdiff",    "summation",      "product",        "pi",
  "integral",       "ordfeminine",    "ordmasculine",   "Ohm",
  "ae",             "oslash",         "questiondown",   "exclamdown",
  "logicalnot",     "radical",        "florin",         "approxequal",
  "increment",      "guillemotleft",  "guillemotright", "ellipsis",
  "nbspace",        "Agrave",         "Atilde",         "Otilde",
  "OE",             "oe",             "endash",         "emdash",
  "quotedblleft",   "quotedblright",  "quoteleft",      "quoteright",
  "divide",         "lozenge",        "ydieresis",      "Ydieresis",
  "fraction",       "currency",       "guilsinglleft",  "guilsinglright",
  "fi",             "fl",             "daggerdbl",      "periodcentered",
  "quotesinglbase", "quotedblbase",   "perthousand",    "Acircumflex",
  "Ecircumflex",    "Aacute",         "Edieresis",      "Egrave",
  "Iacute",         "Icircumflex",    "Idieresis",      "Igrave",
  "Oacute",         "Ocircumflex",    "applelogo",      "Ograve",
  "Uacute",         "Ucircumflex",    "Ugrave",         "dotlessi",
  "circumflex",     "tilde",          "overscore",      "breve",
  "dotaccent",      "ring",           "cedilla",        "hungarumlaut",
  "ogonek",         "caron",          "Lslash",         "lslash",
  "Scaron",         "scaron",         "Zcaron",         "zcaron",
  "brokenbar",      "Eth",            "eth",            "Yacute",
  "yacute",         "Thorn",          "thorn",          "minus",
  "multiply",       "onesuperior",    "twosuperior",    "threesuperior",
  "onehalf",        "onequarter",     "threequarters",  "franc",
  "Gbreve",         "gbreve",         "Idot",           "Scedilla",
  "scedilla",       "Cacute",         "cacute",         "Ccaron",
  "ccaron",         "dmacron"
};

//------------------------------------------------------------------------
// FoFiTrueType
//------------------------------------------------------------------------

FoFiTrueType *FoFiTrueType::make(char *fileA, int lenA) {
  FoFiTrueType *ff;

  ff = new FoFiTrueType(fileA, lenA, gFalse);
  if (!ff->parsedOk) {
    delete ff;
    return NULL;
  }
  return ff;
}

FoFiTrueType *FoFiTrueType::load(char *fileName) {
  FoFiTrueType *ff;
  char *fileA;
  int lenA;

  if (!(fileA = FoFiBase::readFile(fileName, &lenA))) {
    return NULL;
  }
  ff = new FoFiTrueType(fileA, lenA, gTrue);
  if (!ff->parsedOk) {
    delete ff;
    return NULL;
  }
  return ff;
}

FoFiTrueType::FoFiTrueType(char *fileA, int lenA, GBool freeFileDataA):
  FoFiBase(fileA, lenA, freeFileDataA)
{
  tables = NULL;
  nTables = 0;
  cmaps = NULL;
  nCmaps = 0;
  nameToGID = NULL;
  parsedOk = gFalse;

  parse();
}

FoFiTrueType::~FoFiTrueType() {
  gfree(tables);
  gfree(cmaps);
  if (nameToGID) {
    delete nameToGID;
  }
}

int FoFiTrueType::getNumCmaps() {
  return nCmaps;
}

int FoFiTrueType::getCmapPlatform(int i) {
  return cmaps[i].platform;
}

int FoFiTrueType::getCmapEncoding(int i) {
  return cmaps[i].encoding;
}

int FoFiTrueType::findCmap(int platform, int encoding) {
  int i;

  for (i = 0; i < nCmaps; ++i) {
    if (cmaps[i].platform == platform && cmaps[i].encoding == encoding) {
      return i;
    }
  }
  return -1;
}

Gushort FoFiTrueType::mapCodeToGID(int i, int c) {
  Gushort gid;
  int segCnt, segEnd, segStart, segDelta, segOffset;
  int cmapFirst, cmapLen;
  int pos, a, b, m;
  GBool ok;

  if (i < 0 || i >= nCmaps) {
    return 0;
  }
  ok = gTrue;
  pos = cmaps[i].offset;
  switch (cmaps[i].fmt) {
  case 0:
    if (c < 0 || c >= cmaps[i].len - 6) {
      return 0;
    }
    gid = getU8(cmaps[i].offset + 6 + c, &ok);
    break;
  case 4:
    segCnt = getU16BE(pos + 6, &ok) / 2;
    a = -1;
    b = segCnt - 1;
    segEnd = getU16BE(pos + 14 + 2*b, &ok);
    if (c > segEnd) {
      // malformed font -- the TrueType spec requires the last segEnd
      // to be 0xffff
      return 0;
    }
    // invariant: seg[a].end < code <= seg[b].end
    while (b - a > 1 && ok) {
      m = (a + b) / 2;
      segEnd = getU16BE(pos + 14 + 2*m, &ok);
      if (segEnd < c) {
        a = m;
      } else {
        b = m;
      }
    }
    segStart = getU16BE(pos + 16 + 2*segCnt + 2*b, &ok);
    segDelta = getU16BE(pos + 16 + 4*segCnt + 2*b, &ok);
    segOffset = getU16BE(pos + 16 + 6*segCnt + 2*b, &ok);
    if (c < segStart) {
      return 0;
    }
    if (segOffset == 0) {
      gid = (c + segDelta) & 0xffff;
    } else {
      gid = getU16BE(pos + 16 + 6*segCnt + 2*b +
                       segOffset + 2 * (c - segStart), &ok);
      if (gid != 0) {
        gid = (gid + segDelta) & 0xffff;
      }
    }
    break;
  case 6:
    cmapFirst = getU16BE(pos + 6, &ok);
    cmapLen = getU16BE(pos + 8, &ok);
    if (c < cmapFirst || c >= cmapFirst + cmapLen) {
      return 0;
    }
    gid = getU16BE(pos + 10 + 2 * (c - cmapFirst), &ok);
    break;
  default:
    return 0;
  }
  if (!ok) {
    return 0;
  }
  return gid;
}

int FoFiTrueType::mapNameToGID(char *name) {
  if (!nameToGID) {
    return 0;
  }
  return nameToGID->lookupInt(name);
}

Gushort *FoFiTrueType::getCIDToGIDMap(int *nCIDs) {
  FoFiType1C *ff;
  Gushort *map;
  int i;

  *nCIDs = 0;
  if (!openTypeCFF) {
    return NULL;
  }
  i = seekTable("CFF ");
  if (!checkRegion(tables[i].offset, tables[i].len)) {
    return NULL;
  }
  if (!(ff = FoFiType1C::make((char *)file + tables[i].offset,
                              tables[i].len))) {
    return NULL;
  }
  map = ff->getCIDToGIDMap(nCIDs);
  delete ff;
  return map;
}

int FoFiTrueType::getEmbeddingRights() {
  int i, fsType;
  GBool ok;

  if ((i = seekTable("OS/2")) < 0) {
    return 4;
  }
  ok = gTrue;
  fsType = getU16BE(tables[i].offset + 8, &ok);
  if (!ok) {
    return 4;
  }
  if (fsType & 0x0008) {
    return 2;
  }
  if (fsType & 0x0004) {
    return 1;
  }
  if (fsType & 0x0002) {
    return 0;
  }
  return 3;
}

void FoFiTrueType::convertToType42(char *psName, char **encoding,
                                   Gushort *codeToGID,
                                   FoFiOutputFunc outputFunc,
                                   void *outputStream) {
  GString *buf;
  GBool ok;

  if (openTypeCFF) {
    return;
  }

  // write the header
  ok = gTrue;
  buf = GString::format("%!PS-TrueTypeFont-{0:2g}\n",
                        (double)getS32BE(0, &ok) / 65536.0);
  (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
  delete buf;

  // begin the font dictionary
  (*outputFunc)(outputStream, "10 dict begin\n", 14);
  (*outputFunc)(outputStream, "/FontName /", 11);
  (*outputFunc)(outputStream, psName, strlen(psName));
  (*outputFunc)(outputStream, " def\n", 5);
  (*outputFunc)(outputStream, "/FontType 42 def\n", 17);
  (*outputFunc)(outputStream, "/FontMatrix [1 0 0 1 0 0] def\n", 30);
  buf = GString::format("/FontBBox [{0:d} {1:d} {2:d} {3:d}] def\n",
                        bbox[0], bbox[1], bbox[2], bbox[3]);
  (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
  delete buf;
  (*outputFunc)(outputStream, "/PaintType 0 def\n", 17);

  // write the guts of the dictionary
  cvtEncoding(encoding, outputFunc, outputStream);
  cvtCharStrings(encoding, codeToGID, outputFunc, outputStream);
  cvtSfnts(outputFunc, outputStream, NULL, gFalse);

  // end the dictionary and define the font
  (*outputFunc)(outputStream, "FontName currentdict end definefont pop\n", 40);
}

void FoFiTrueType::convertToType1(char *psName, char **newEncoding,
                                  GBool ascii, FoFiOutputFunc outputFunc,
                                  void *outputStream) {
  FoFiType1C *ff;
  int i;

  if (!openTypeCFF) {
    return;
  }
  i = seekTable("CFF ");
  if (!checkRegion(tables[i].offset, tables[i].len)) {
    return;
  }
  if (!(ff = FoFiType1C::make((char *)file + tables[i].offset,
                              tables[i].len))) {
    return;
  }
  ff->convertToType1(psName, newEncoding, ascii, outputFunc, outputStream);
  delete ff;
}

void FoFiTrueType::convertToCIDType2(char *psName,
                                     Gushort *cidMap, int nCIDs,
                                     GBool needVerticalMetrics,
                                     FoFiOutputFunc outputFunc,
                                     void *outputStream) {
  GString *buf;
  Gushort cid;
  GBool ok;
  int i, j, k;

  if (openTypeCFF) {
    return;
  }

  // write the header
  ok = gTrue;
  buf = GString::format("%!PS-TrueTypeFont-{0:2g}\n",
                        (double)getS32BE(0, &ok) / 65536.0);
  (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
  delete buf;

  // begin the font dictionary
  (*outputFunc)(outputStream, "20 dict begin\n", 14);
  (*outputFunc)(outputStream, "/CIDFontName /", 14);
  (*outputFunc)(outputStream, psName, strlen(psName));
  (*outputFunc)(outputStream, " def\n", 5);
  (*outputFunc)(outputStream, "/CIDFontType 2 def\n", 19);
  (*outputFunc)(outputStream, "/FontType 42 def\n", 17);
  (*outputFunc)(outputStream, "/CIDSystemInfo 3 dict dup begin\n", 32);
  (*outputFunc)(outputStream, "  /Registry (Adobe) def\n", 24);
  (*outputFunc)(outputStream, "  /Ordering (Identity) def\n", 27);
  (*outputFunc)(outputStream, "  /Supplement 0 def\n", 20);
  (*outputFunc)(outputStream, "  end def\n", 10);
  (*outputFunc)(outputStream, "/GDBytes 2 def\n", 15);
  if (cidMap) {
    buf = GString::format("/CIDCount {0:d} def\n", nCIDs);
    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
    delete buf;
    if (nCIDs > 32767) {
      (*outputFunc)(outputStream, "/CIDMap [", 9);
      for (i = 0; i < nCIDs; i += 32768 - 16) {
        (*outputFunc)(outputStream, "<\n", 2);
        for (j = 0; j < 32768 - 16 && i+j < nCIDs; j += 16) {
          (*outputFunc)(outputStream, "  ", 2);
          for (k = 0; k < 16 && i+j+k < nCIDs; ++k) {
            cid = cidMap[i+j+k];
            buf = GString::format("{0:02x}{1:02x}",
                                  (cid >> 8) & 0xff, cid & 0xff);
            (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
            delete buf;
          }
          (*outputFunc)(outputStream, "\n", 1);
        }
        (*outputFunc)(outputStream, "  >", 3);
      }
      (*outputFunc)(outputStream, "\n", 1);
      (*outputFunc)(outputStream, "] def\n", 6);
    } else {
      (*outputFunc)(outputStream, "/CIDMap <\n", 10);
      for (i = 0; i < nCIDs; i += 16) {
        (*outputFunc)(outputStream, "  ", 2);
        for (j = 0; j < 16 && i+j < nCIDs; ++j) {
          cid = cidMap[i+j];
          buf = GString::format("{0:02x}{1:02x}",
                                (cid >> 8) & 0xff, cid & 0xff);
          (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
          delete buf;
        }
        (*outputFunc)(outputStream, "\n", 1);
      }
      (*outputFunc)(outputStream, "> def\n", 6);
    }
  } else {
    // direct mapping - just fill the string(s) with s[i]=i
    buf = GString::format("/CIDCount {0:d} def\n", nGlyphs);
    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
    delete buf;
    if (nGlyphs > 32767) {
      (*outputFunc)(outputStream, "/CIDMap [\n", 10);
      for (i = 0; i < nGlyphs; i += 32767) {
        j = nGlyphs - i < 32767 ? nGlyphs - i : 32767;
        buf = GString::format("  {0:d} string 0 1 {1:d} {{\n", 2 * j, j - 1);
        (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
        delete buf;
        buf = GString::format("    2 copy dup 2 mul exch {0:d} add -8 bitshift put\n",
                              i);
        (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
        delete buf;
        buf = GString::format("    1 index exch dup 2 mul 1 add exch {0:d} add"
                              " 255 and put\n", i);
        (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
        delete buf;
        (*outputFunc)(outputStream, "  } for\n", 8);
      }
      (*outputFunc)(outputStream, "] def\n", 6);
    } else {
      buf = GString::format("/CIDMap {0:d} string\n", 2 * nGlyphs);
      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
      delete buf;
      buf = GString::format("  0 1 {0:d} {{\n", nGlyphs - 1);
      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
      delete buf;
      (*outputFunc)(outputStream,
                    "    2 copy dup 2 mul exch -8 bitshift put\n", 42);
      (*outputFunc)(outputStream,
                    "    1 index exch dup 2 mul 1 add exch 255 and put\n", 50);
      (*outputFunc)(outputStream, "  } for\n", 8);
      (*outputFunc)(outputStream, "def\n", 4);
    }
  }
  (*outputFunc)(outputStream, "/FontMatrix [1 0 0 1 0 0] def\n", 30);
  buf = GString::format("/FontBBox [{0:d} {1:d} {2:d} {3:d}] def\n",
                        bbox[0], bbox[1], bbox[2], bbox[3]);
  (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
  delete buf;
  (*outputFunc)(outputStream, "/PaintType 0 def\n", 17);
  (*outputFunc)(outputStream, "/Encoding [] readonly def\n", 26);
  (*outputFunc)(outputStream, "/CharStrings 1 dict dup begin\n", 30);
  (*outputFunc)(outputStream, "  /.notdef 0 def\n", 17);
  (*outputFunc)(outputStream, "  end readonly def\n", 19);

  // write the guts of the dictionary
  cvtSfnts(outputFunc, outputStream, NULL, needVerticalMetrics);

  // end the dictionary and define the font
  (*outputFunc)(outputStream,
                "CIDFontName currentdict end /CIDFont defineresource pop\n",
                56);
}

void FoFiTrueType::convertToCIDType0(char *psName,
                                     FoFiOutputFunc outputFunc,
                                     void *outputStream) {
  FoFiType1C *ff;
  int i;

  if (!openTypeCFF) {
    return;
  }
  i = seekTable("CFF ");
  if (!checkRegion(tables[i].offset, tables[i].len)) {
    return;
  }
  if (!(ff = FoFiType1C::make((char *)file + tables[i].offset,
                              tables[i].len))) {
    return;
  }
  ff->convertToCIDType0(psName, outputFunc, outputStream);
  delete ff;
}

void FoFiTrueType::convertToType0(char *psName, Gushort *cidMap, int nCIDs,
                                  GBool needVerticalMetrics,
                                  FoFiOutputFunc outputFunc,
                                  void *outputStream) {
  GString *buf;
  GString *sfntsName;
  int n, i, j;

  if (openTypeCFF) {
    return;
  }

  // write the Type 42 sfnts array
  sfntsName = (new GString(psName))->append("_sfnts");
  cvtSfnts(outputFunc, outputStream, sfntsName, needVerticalMetrics);
  delete sfntsName;

  // write the descendant Type 42 fonts
  n = cidMap ? nCIDs : nGlyphs;
  for (i = 0; i < n; i += 256) {
    (*outputFunc)(outputStream, "10 dict begin\n", 14);
    (*outputFunc)(outputStream, "/FontName /", 11);
    (*outputFunc)(outputStream, psName, strlen(psName));
    buf = GString::format("_{0:02x} def\n", i >> 8);
    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
    delete buf;
    (*outputFunc)(outputStream, "/FontType 42 def\n", 17);
    (*outputFunc)(outputStream, "/FontMatrix [1 0 0 1 0 0] def\n", 30);
    buf = GString::format("/FontBBox [{0:d} {1:d} {2:d} {3:d}] def\n",
                          bbox[0], bbox[1], bbox[2], bbox[3]);
    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
    delete buf;
    (*outputFunc)(outputStream, "/PaintType 0 def\n", 17);
    (*outputFunc)(outputStream, "/sfnts ", 7);
    (*outputFunc)(outputStream, psName, strlen(psName));
    (*outputFunc)(outputStream, "_sfnts def\n", 11);
    (*outputFunc)(outputStream, "/Encoding 256 array\n", 20);
    for (j = 0; j < 256 && i+j < n; ++j) {
      buf = GString::format("dup {0:d} /c{1:02x} put\n", j, j);
      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
      delete buf;
    }
    (*outputFunc)(outputStream, "readonly def\n", 13);
    (*outputFunc)(outputStream, "/CharStrings 257 dict dup begin\n", 32);
    (*outputFunc)(outputStream, "/.notdef 0 def\n", 15);
    for (j = 0; j < 256 && i+j < n; ++j) {
      buf = GString::format("/c{0:02x} {1:d} def\n",
                            j, cidMap ? cidMap[i+j] : i+j);
      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
      delete buf;
    }
    (*outputFunc)(outputStream, "end readonly def\n", 17);
    (*outputFunc)(outputStream,
                  "FontName currentdict end definefont pop\n", 40);
  }

  // write the Type 0 parent font
  (*outputFunc)(outputStream, "16 dict begin\n", 14);
  (*outputFunc)(outputStream, "/FontName /", 11);
  (*outputFunc)(outputStream, psName, strlen(psName));
  (*outputFunc)(outputStream, " def\n", 5);
  (*outputFunc)(outputStream, "/FontType 0 def\n", 16);
  (*outputFunc)(outputStream, "/FontMatrix [1 0 0 1 0 0] def\n", 30);
  (*outputFunc)(outputStream, "/FMapType 2 def\n", 16);
  (*outputFunc)(outputStream, "/Encoding [\n", 12);
  for (i = 0; i < n; i += 256) {
    buf = GString::format("{0:d}\n", i >> 8);
    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
    delete buf;
  }
  (*outputFunc)(outputStream, "] def\n", 6);
  (*outputFunc)(outputStream, "/FDepVector [\n", 14);
  for (i = 0; i < n; i += 256) {
    (*outputFunc)(outputStream, "/", 1);
    (*outputFunc)(outputStream, psName, strlen(psName));
    buf = GString::format("_{0:02x} findfont\n", i >> 8);
    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
    delete buf;
  }
  (*outputFunc)(outputStream, "] def\n", 6);
  (*outputFunc)(outputStream, "FontName currentdict end definefont pop\n", 40);
}

void FoFiTrueType::convertToType0(char *psName,
                                  FoFiOutputFunc outputFunc,
                                  void *outputStream) {
  FoFiType1C *ff;
  int i;

  if (!openTypeCFF) {
    return;
  }
  i = seekTable("CFF ");
  if (!checkRegion(tables[i].offset, tables[i].len)) {
    return;
  }
  if (!(ff = FoFiType1C::make((char *)file + tables[i].offset,
                              tables[i].len))) {
    return;
  }
  ff->convertToType0(psName, outputFunc, outputStream);
  delete ff;
}

void FoFiTrueType::writeTTF(FoFiOutputFunc outputFunc,
                            void *outputStream, char *name,
                            Gushort *codeToGID) {
  // this substitute cmap table maps char codes 0000-ffff directly to
  // glyphs 0000-ffff
  static char cmapTab[36] = {
    0, 0,                       // table version number
    0, 1,                       // number of encoding tables
    0, 1,                       // platform ID
    0, 0,                       // encoding ID
    0, 0, 0, 12,                // offset of subtable
    0, 4,                       // subtable format
    0, 24,                      // subtable length
    0, 0,                       // subtable version
    0, 2,                       // segment count * 2
    0, 2,                       // 2 * 2 ^ floor(log2(segCount))
    0, 0,                       // floor(log2(segCount))
    0, 0,                       // 2*segCount - 2*2^floor(log2(segCount))
    (char)0xff, (char)0xff,     // endCount[0]
    0, 0,                       // reserved
    0, 0,                       // startCount[0]
    0, 0,                       // idDelta[0]
    0, 0                        // pad to a mulitple of four bytes
  };
  static char nameTab[8] = {
    0, 0,                       // format
    0, 0,                       // number of name records
    0, 6,                       // offset to start of string storage
    0, 0                        // pad to multiple of four bytes
  };
  static char postTab[32] = {
    0, 1, 0, 0,                 // format
    0, 0, 0, 0,                 // italic angle
    0, 0,                       // underline position
    0, 0,                       // underline thickness
    0, 0, 0, 0,                 // fixed pitch
    0, 0, 0, 0,                 // min Type 42 memory
    0, 0, 0, 0,                 // max Type 42 memory
    0, 0, 0, 0,                 // min Type 1 memory
    0, 0, 0, 0                  // max Type 1 memory
  };
  static char os2Tab[86] = {
    0, 1,                       // version
    0, 1,                       // xAvgCharWidth
    0, 0,                       // usWeightClass
    0, 0,                       // usWidthClass
    0, 0,                       // fsType
    0, 0,                       // ySubscriptXSize
    0, 0,                       // ySubscriptYSize
    0, 0,                       // ySubscriptXOffset
    0, 0,                       // ySubscriptYOffset
    0, 0,                       // ySuperscriptXSize
    0, 0,                       // ySuperscriptYSize
    0, 0,                       // ySuperscriptXOffset
    0, 0,                       // ySuperscriptYOffset
    0, 0,                       // yStrikeoutSize
    0, 0,                       // yStrikeoutPosition
    0, 0,                       // sFamilyClass
    0, 0, 0, 0, 0,              // panose
    0, 0, 0, 0, 0,
    0, 0, 0, 0,                 // ulUnicodeRange1
    0, 0, 0, 0,                 // ulUnicodeRange2
    0, 0, 0, 0,                 // ulUnicodeRange3
    0, 0, 0, 0,                 // ulUnicodeRange4
    0, 0, 0, 0,                 // achVendID
    0, 0,                       // fsSelection
    0, 0,                       // usFirstCharIndex
    0, 0,                       // usLastCharIndex
    0, 0,                       // sTypoAscender
    0, 0,                       // sTypoDescender
    0, 0,                       // sTypoLineGap
    0, 0,                       // usWinAscent
    0, 0,                       // usWinDescent
    0, 0, 0, 0,                 // ulCodePageRange1
    0, 0, 0, 0                  // ulCodePageRange2
  };
  GBool missingCmap, missingName, missingPost, missingOS2;
  GBool unsortedLoca, badCmapLen, abbrevHMTX;
  int nZeroLengthTables;
  int nHMetrics, advWidth, lsb;
  TrueTypeLoca *locaTable;
  TrueTypeTable *newTables;
  char *newNameTab, *newCmapTab, *newHHEATab, *newHMTXTab;
  int nNewTables, cmapIdx, cmapLen, glyfLen, newNameLen, newCmapLen, next;
  int newHHEALen, newHMTXLen;
  Guint locaChecksum, glyfChecksum, fileChecksum;
  char *tableDir;
  char locaBuf[4], checksumBuf[4];
  GBool ok;
  Guint t;
  int pos, i, j, k, n;

  if (openTypeCFF) {
    return;
  }

  // check for missing tables
  // (Note: if the OS/2 table is missing, the Microsoft PCL5 driver
  // will embed a PCL TrueType font with the pitch field set to zero,
  // which apparently causes divide-by-zero errors.  As far as I can
  // tell, the only important field in the OS/2 table is
  // xAvgCharWidth.)
  missingCmap = (cmapIdx = seekTable("cmap")) < 0;
  missingName = seekTable("name") < 0;
  missingPost = seekTable("post") < 0;
  missingOS2 = seekTable("OS/2") < 0;

  // read the loca table, check to see if it's sorted
  locaTable = (TrueTypeLoca *)gmallocn(nGlyphs + 1, sizeof(TrueTypeLoca));
  unsortedLoca = gFalse;
  i = seekTable("loca");
  pos = tables[i].offset;
  ok = gTrue;
  for (i = 0; i <= nGlyphs; ++i) {
    if (locaFmt) {
      locaTable[i].origOffset = (int)getU32BE(pos + i*4, &ok);
    } else {
      locaTable[i].origOffset = 2 * getU16BE(pos + i*2, &ok);
    }
    if (i > 0 && locaTable[i].origOffset < locaTable[i-1].origOffset) {
      unsortedLoca = gTrue;
    }
    // glyph descriptions must be at least 12 bytes long (nContours,
    // xMin, yMin, xMax, yMax, instructionLength - two bytes each);
    // invalid glyph descriptions (even if they're never used) make
    // Windows choke, so we work around that problem here (ideally,
    // this would parse the glyph descriptions in the glyf table and
    // remove any that were invalid, but this quick test is a decent
    // start)
    if (i > 0 &&
        locaTable[i].origOffset - locaTable[i-1].origOffset > 0 &&
        locaTable[i].origOffset - locaTable[i-1].origOffset < 12) {
      locaTable[i-1].origOffset = locaTable[i].origOffset;
      unsortedLoca = gTrue;
    }
    locaTable[i].idx = i;
  }

  // check for zero-length tables
  nZeroLengthTables = 0;
  for (i = 0; i < nTables; ++i) {
    if (tables[i].len == 0) {
      ++nZeroLengthTables;
    }
  }

  // check for an incorrect cmap table length
  badCmapLen = gFalse;
  cmapLen = 0; // make gcc happy
  if (!missingCmap) {
    cmapLen = cmaps[0].offset + cmaps[0].len;
    for (i = 1; i < nCmaps; ++i) {
      if (cmaps[i].offset + cmaps[i].len > cmapLen) {
        cmapLen = cmaps[i].offset + cmaps[i].len;
      }
    }
    cmapLen -= tables[cmapIdx].offset;
    if (cmapLen > tables[cmapIdx].len) {
      badCmapLen = gTrue;
    }
  }

  // check for an abbreviated hmtx table (this is completely legal,
  // but confuses the Microsoft PCL5 printer driver, which generates
  // embedded fonts with the pitch field set to zero)
  i = seekTable("hhea");
  nHMetrics = getU16BE(tables[i].offset + 34, &ok);
  abbrevHMTX = nHMetrics < nGlyphs;

  // if nothing is broken, just write the TTF file as is
  if (!missingCmap && !missingName && !missingPost && !missingOS2 &&
      !unsortedLoca && !badCmapLen && !abbrevHMTX && nZeroLengthTables == 0 &&
      !name && !codeToGID) {
    (*outputFunc)(outputStream, (char *)file, len);
    goto done1;
  }

  // sort the 'loca' table: some (non-compliant) fonts have
  // out-of-order loca tables; in order to correctly handle the case
  // where (compliant) fonts have empty entries in the middle of the
  // table, cmpTrueTypeLocaOffset uses offset as its primary sort key,
  // and idx as its secondary key (ensuring that adjacent entries with
  // the same pos value remain in the same order)
  glyfLen = 0; // make gcc happy
  if (unsortedLoca) {
    qsort(locaTable, nGlyphs + 1, sizeof(TrueTypeLoca),
          &cmpTrueTypeLocaOffset);
    for (i = 0; i < nGlyphs; ++i) {
      locaTable[i].len = locaTable[i+1].origOffset - locaTable[i].origOffset;
    }
    locaTable[nGlyphs].len = 0;
    qsort(locaTable, nGlyphs + 1, sizeof(TrueTypeLoca),
          &cmpTrueTypeLocaIdx);
    pos = 0;
    for (i = 0; i <= nGlyphs; ++i) {
      locaTable[i].newOffset = pos;
      pos += locaTable[i].len;
      if (pos & 3) {
        pos += 4 - (pos & 3);
      }
    }
    glyfLen = pos;
  }

  // compute checksums for the loca and glyf tables
  locaChecksum = glyfChecksum = 0;
  if (unsortedLoca) {
    if (locaFmt) {
      for (j = 0; j <= nGlyphs; ++j) {
        locaChecksum += locaTable[j].newOffset;
      }
    } else {
      for (j = 0; j <= nGlyphs; j += 2) {
        locaChecksum += locaTable[j].newOffset << 16;
        if (j + 1 <= nGlyphs) {
          locaChecksum += locaTable[j+1].newOffset;
        }
      }
    }
    pos = tables[seekTable("glyf")].offset;
    for (j = 0; j < nGlyphs; ++j) {
      n = locaTable[j].len;
      if (n > 0) {
        k = locaTable[j].origOffset;
        if (checkRegion(pos + k, n)) {
          glyfChecksum += computeTableChecksum(file + pos + k, n);
        }
      }
    }
  }

  // construct the new name table
  if (name) {
    n = strlen(name);
    newNameLen = (6 + 4*12 + 2 * (3*n + 7) + 3) & ~3;
    newNameTab = (char *)gmalloc(newNameLen);
    memset(newNameTab, 0, newNameLen);
    newNameTab[0] = 0;          // format selector
    newNameTab[1] = 0;
    newNameTab[2] = 0;          // number of name records
    newNameTab[3] = 4;
    newNameTab[4] = 0;          // offset to start of string storage
    newNameTab[5] = 6 + 4*12;
    next = 0;
    for (i = 0; i < 4; ++i) {
      newNameTab[6 + i*12 + 0] = 0;     // platform ID = Microsoft
      newNameTab[6 + i*12 + 1] = 3;
      newNameTab[6 + i*12 + 2] = 0;     // encoding ID = Unicode
      newNameTab[6 + i*12 + 3] = 1;
      newNameTab[6 + i*12 + 4] = 0x04;  // language ID = American English
      newNameTab[6 + i*12 + 5] = 0x09;
      newNameTab[6 + i*12 + 6] = 0;     // name ID
      newNameTab[6 + i*12 + 7] = i + 1;
      newNameTab[6 + i*12 + 8] = i+1 == 2 ? 0 : ((2*n) >> 8); // string length
      newNameTab[6 + i*12 + 9] = i+1 == 2 ? 14 : ((2*n) & 0xff);
      newNameTab[6 + i*12 + 10] = next >> 8;                // string offset
      newNameTab[6 + i*12 + 11] = next & 0xff;
      if (i+1 == 2) {
        memcpy(newNameTab + 6 + 4*12 + next, "\0R\0e\0g\0u\0l\0a\0r", 14);
        next += 14;
      } else {
        for (j = 0; j < n; ++j) {
          newNameTab[6 + 4*12 + next + 2*j] = 0;
          newNameTab[6 + 4*12 + next + 2*j + 1] = name[j];
        }
        next += 2*n;
      }
    }
  } else {
    newNameLen = 0;
    newNameTab = NULL;
  }

  // construct the new cmap table
  if (codeToGID) {
    newCmapLen = 44 + 256 * 2;
    newCmapTab = (char *)gmalloc(newCmapLen);
    newCmapTab[0] = 0;          // table version number = 0
    newCmapTab[1] = 0;
    newCmapTab[2] = 0;          // number of encoding tables = 1
    newCmapTab[3] = 1;
    newCmapTab[4] = 0;          // platform ID = Microsoft
    newCmapTab[5] = 3;
    newCmapTab[6] = 0;          // encoding ID = Unicode
    newCmapTab[7] = 1;
    newCmapTab[8] = 0;          // offset of subtable
    newCmapTab[9] = 0;
    newCmapTab[10] = 0;
    newCmapTab[11] = 12;
    newCmapTab[12] = 0;         // subtable format = 4
    newCmapTab[13] = 4;
    newCmapTab[14] = 0x02;      // subtable length
    newCmapTab[15] = 0x20;
    newCmapTab[16] = 0;         // subtable version = 0
    newCmapTab[17] = 0;
    newCmapTab[18] = 0;         // segment count * 2
    newCmapTab[19] = 4;
    newCmapTab[20] = 0;         // 2 * 2 ^ floor(log2(segCount))
    newCmapTab[21] = 4;
    newCmapTab[22] = 0;         // floor(log2(segCount))
    newCmapTab[23] = 1;
    newCmapTab[24] = 0;         // 2*segCount - 2*2^floor(log2(segCount))
    newCmapTab[25] = 0;
    newCmapTab[26] = 0x00;      // endCount[0]
    newCmapTab[27] = (char)0xff;
    newCmapTab[28] = (char)0xff; // endCount[1]
    newCmapTab[29] = (char)0xff;
    newCmapTab[30] = 0;         // reserved
    newCmapTab[31] = 0;
    newCmapTab[32] = 0x00;      // startCount[0]
    newCmapTab[33] = 0x00;
    newCmapTab[34] = (char)0xff; // startCount[1]
    newCmapTab[35] = (char)0xff;
    newCmapTab[36] = 0;         // idDelta[0]
    newCmapTab[37] = 0;
    newCmapTab[38] = 0;         // idDelta[1]
    newCmapTab[39] = 1;
    newCmapTab[40] = 0;         // idRangeOffset[0]
    newCmapTab[41] = 4;
    newCmapTab[42] = 0;         // idRangeOffset[1]
    newCmapTab[43] = 0;
    for (i = 0; i < 256; ++i) {
      newCmapTab[44 + 2*i] = codeToGID[i] >> 8;
      newCmapTab[44 + 2*i + 1] = codeToGID[i] & 0xff;
    }
  } else {
    newCmapLen = 0;
    newCmapTab = NULL;
  }

  // generate the new hmtx table and the updated hhea table
  if (abbrevHMTX) {
    i = seekTable("hhea");
    pos = tables[i].offset;
    newHHEALen = 36;
    newHHEATab = (char *)gmalloc(newHHEALen);
    for (i = 0; i < newHHEALen; ++i) {
      newHHEATab[i] = getU8(pos++, &ok);
    }
    newHHEATab[34] = nGlyphs >> 8;
    newHHEATab[35] = nGlyphs & 0xff;
    i = seekTable("hmtx");
    pos = tables[i].offset;
    newHMTXLen = 4 * nGlyphs;
    newHMTXTab = (char *)gmalloc(newHMTXLen);
    advWidth = 0;
    for (i = 0; i < nHMetrics; ++i) {
      advWidth = getU16BE(pos, &ok);
      lsb = getU16BE(pos + 2, &ok);
      pos += 4;
      newHMTXTab[4*i    ] = advWidth >> 8;
      newHMTXTab[4*i + 1] = advWidth & 0xff;
      newHMTXTab[4*i + 2] = lsb >> 8;
      newHMTXTab[4*i + 3] = lsb & 0xff;
    }
    for (; i < nGlyphs; ++i) {
      lsb = getU16BE(pos, &ok);
      pos += 2;
      newHMTXTab[4*i    ] = advWidth >> 8;
      newHMTXTab[4*i + 1] = advWidth & 0xff;
      newHMTXTab[4*i + 2] = lsb >> 8;
      newHMTXTab[4*i + 3] = lsb & 0xff;
    }
  } else {
    newHHEATab = newHMTXTab = NULL;
    newHHEALen = newHMTXLen = 0; // make gcc happy
  }

  // construct the new table directory:
  // - keep all original tables with non-zero length
  // - fix the cmap table's length, if necessary
  // - add missing tables
  // - sort the table by tag
  // - compute new table positions, including 4-byte alignment
  // - (re)compute table checksums
  nNewTables = nTables - nZeroLengthTables +
               (missingCmap ? 1 : 0) + (missingName ? 1 : 0) +
               (missingPost ? 1 : 0) + (missingOS2 ? 1 : 0);
  newTables = (TrueTypeTable *)gmallocn(nNewTables, sizeof(TrueTypeTable));
  j = 0;
  for (i = 0; i < nTables; ++i) {
    if (tables[i].len > 0) {
      newTables[j] = tables[i];
      newTables[j].origOffset = tables[i].offset;
      if (checkRegion(tables[i].offset, newTables[i].len)) {
        newTables[j].checksum =
            computeTableChecksum(file + tables[i].offset, tables[i].len);
        if (tables[i].tag == headTag) {
          // don't include the file checksum
          newTables[j].checksum -= getU32BE(tables[i].offset + 8, &ok);
        }
      }
      if (newTables[j].tag == cmapTag && codeToGID) {
        newTables[j].len = newCmapLen;
        newTables[j].checksum = computeTableChecksum((Guchar *)newCmapTab,
                                                     newCmapLen);
      } else if (newTables[j].tag == cmapTag && badCmapLen) {
        newTables[j].len = cmapLen;
      } else if (newTables[j].tag == locaTag && unsortedLoca) {
        newTables[j].len = (nGlyphs + 1) * (locaFmt ? 4 : 2);
        newTables[j].checksum = locaChecksum;
      } else if (newTables[j].tag == glyfTag && unsortedLoca) {
        newTables[j].len = glyfLen;
        newTables[j].checksum = glyfChecksum;
      } else if (newTables[j].tag == nameTag && name) {
        newTables[j].len = newNameLen;
        newTables[j].checksum = computeTableChecksum((Guchar *)newNameTab,
                                                     newNameLen);
      } else if (newTables[j].tag == hheaTag && abbrevHMTX) {
        newTables[j].len = newHHEALen;
        newTables[j].checksum = computeTableChecksum((Guchar *)newHHEATab,
                                                     newHHEALen);
      } else if (newTables[j].tag == hmtxTag && abbrevHMTX) {
        newTables[j].len = newHMTXLen;
        newTables[j].checksum = computeTableChecksum((Guchar *)newHMTXTab,
                                                     newHMTXLen);
      }
      ++j;
    }
  }
  if (missingCmap) {
    newTables[j].tag = cmapTag;
    if (codeToGID) {
      newTables[j].checksum = computeTableChecksum((Guchar *)newCmapTab,
                                                   newCmapLen);
      newTables[j].len = newCmapLen;
    } else {
      newTables[j].checksum = computeTableChecksum((Guchar *)cmapTab,
                                                   sizeof(cmapTab));
      newTables[j].len = sizeof(cmapTab);
    }
    ++j;
  }
  if (missingName) {
    newTables[j].tag = nameTag;
    if (name) {
      newTables[j].checksum = computeTableChecksum((Guchar *)newNameTab,
                                                   newNameLen);
      newTables[j].len = newNameLen;
    } else {
      newTables[j].checksum = computeTableChecksum((Guchar *)nameTab,
                                                   sizeof(nameTab));
      newTables[j].len = sizeof(nameTab);
    }
    ++j;
  }
  if (missingPost) {
    newTables[j].tag = postTag;
    newTables[j].checksum = computeTableChecksum((Guchar *)postTab,
                                                 sizeof(postTab));
    newTables[j].len = sizeof(postTab);
    ++j;
  }
  if (missingOS2) {
    newTables[j].tag = os2Tag;
    newTables[j].checksum = computeTableChecksum((Guchar *)os2Tab,
                                                 sizeof(os2Tab));
    newTables[j].len = sizeof(os2Tab);
    ++j;
  }
  qsort(newTables, nNewTables, sizeof(TrueTypeTable),
        &cmpTrueTypeTableTag);
  pos = 12 + nNewTables * 16;
  for (i = 0; i < nNewTables; ++i) {
    newTables[i].offset = pos;
    pos += newTables[i].len;
    if (pos & 3) {
      pos += 4 - (pos & 3);
    }
  }

  // write the table directory
  tableDir = (char *)gmalloc(12 + nNewTables * 16);
  tableDir[0] = 0x00;                                   // sfnt version
  tableDir[1] = 0x01;
  tableDir[2] = 0x00;
  tableDir[3] = 0x00;
  tableDir[4] = (char)((nNewTables >> 8) & 0xff);       // numTables
  tableDir[5] = (char)(nNewTables & 0xff);
  for (i = -1, t = (Guint)nNewTables; t; ++i, t >>= 1) ;
  t = 1 << (4 + i);
  tableDir[6] = (char)((t >> 8) & 0xff);                // searchRange
  tableDir[7] = (char)(t & 0xff);
  tableDir[8] = (char)((i >> 8) & 0xff);                // entrySelector
  tableDir[9] = (char)(i & 0xff);
  t = nNewTables * 16 - t;
  tableDir[10] = (char)((t >> 8) & 0xff);               // rangeShift
  tableDir[11] = (char)(t & 0xff);
  pos = 12;
  for (i = 0; i < nNewTables; ++i) {
    tableDir[pos   ] = (char)(newTables[i].tag >> 24);
    tableDir[pos+ 1] = (char)(newTables[i].tag >> 16);
    tableDir[pos+ 2] = (char)(newTables[i].tag >>  8);
    tableDir[pos+ 3] = (char) newTables[i].tag;
    tableDir[pos+ 4] = (char)(newTables[i].checksum >> 24);
    tableDir[pos+ 5] = (char)(newTables[i].checksum >> 16);
    tableDir[pos+ 6] = (char)(newTables[i].checksum >>  8);
    tableDir[pos+ 7] = (char) newTables[i].checksum;
    tableDir[pos+ 8] = (char)(newTables[i].offset >> 24);
    tableDir[pos+ 9] = (char)(newTables[i].offset >> 16);
    tableDir[pos+10] = (char)(newTables[i].offset >>  8);
    tableDir[pos+11] = (char) newTables[i].offset;
    tableDir[pos+12] = (char)(newTables[i].len >> 24);
    tableDir[pos+13] = (char)(newTables[i].len >> 16);
    tableDir[pos+14] = (char)(newTables[i].len >>  8);
    tableDir[pos+15] = (char) newTables[i].len;
    pos += 16;
  }
  (*outputFunc)(outputStream, tableDir, 12 + nNewTables * 16);

  // compute the file checksum
  fileChecksum = computeTableChecksum((Guchar *)tableDir,
                                      12 + nNewTables * 16);
  for (i = 0; i < nNewTables; ++i) {
    fileChecksum += newTables[i].checksum;
  }
  fileChecksum = 0xb1b0afba - fileChecksum;

  // write the tables
  for (i = 0; i < nNewTables; ++i) {
    if (newTables[i].tag == headTag) {
      if (checkRegion(newTables[i].origOffset, newTables[i].len)) {
        (*outputFunc)(outputStream, (char *)file + newTables[i].origOffset, 8);
        checksumBuf[0] = fileChecksum >> 24;
        checksumBuf[1] = fileChecksum >> 16;
        checksumBuf[2] = fileChecksum >> 8;
        checksumBuf[3] = fileChecksum;
        (*outputFunc)(outputStream, checksumBuf, 4);
        (*outputFunc)(outputStream,
                      (char *)file + newTables[i].origOffset + 12,
                      newTables[i].len - 12);
      } else {
        for (j = 0; j < newTables[i].len; ++j) {
          (*outputFunc)(outputStream, "\0", 1);
        }
      }
    } else if (newTables[i].tag == cmapTag && codeToGID) {
      (*outputFunc)(outputStream, newCmapTab, newTables[i].len);
    } else if (newTables[i].tag == cmapTag && missingCmap) {
      (*outputFunc)(outputStream, cmapTab, newTables[i].len);
    } else if (newTables[i].tag == nameTag && name) {
      (*outputFunc)(outputStream, newNameTab, newTables[i].len);
    } else if (newTables[i].tag == nameTag && missingName) {
      (*outputFunc)(outputStream, nameTab, newTables[i].len);
    } else if (newTables[i].tag == postTag && missingPost) {
      (*outputFunc)(outputStream, postTab, newTables[i].len);
    } else if (newTables[i].tag == os2Tag && missingOS2) {
      (*outputFunc)(outputStream, os2Tab, newTables[i].len);
    } else if (newTables[i].tag == hheaTag && abbrevHMTX) {
      (*outputFunc)(outputStream, newHHEATab, newTables[i].len);
    } else if (newTables[i].tag == hmtxTag && abbrevHMTX) {
      (*outputFunc)(outputStream, newHMTXTab, newTables[i].len);
    } else if (newTables[i].tag == locaTag && unsortedLoca) {
      for (j = 0; j <= nGlyphs; ++j) {
        if (locaFmt) {
          locaBuf[0] = (char)(locaTable[j].newOffset >> 24);
          locaBuf[1] = (char)(locaTable[j].newOffset >> 16);
          locaBuf[2] = (char)(locaTable[j].newOffset >>  8);
          locaBuf[3] = (char) locaTable[j].newOffset;
          (*outputFunc)(outputStream, locaBuf, 4);
        } else {
          locaBuf[0] = (char)(locaTable[j].newOffset >> 9);
          locaBuf[1] = (char)(locaTable[j].newOffset >> 1);
          (*outputFunc)(outputStream, locaBuf, 2);
        }
      }
    } else if (newTables[i].tag == glyfTag && unsortedLoca) {
      pos = tables[seekTable("glyf")].offset;
      for (j = 0; j < nGlyphs; ++j) {
        n = locaTable[j].len;
        if (n > 0) {
          k = locaTable[j].origOffset;
          if (checkRegion(pos + k, n)) {
            (*outputFunc)(outputStream, (char *)file + pos + k, n);
          } else {
            for (k = 0; k < n; ++k) {
              (*outputFunc)(outputStream, "\0", 1);
            }
          }
          if ((k = locaTable[j].len & 3)) {
            (*outputFunc)(outputStream, "\0\0\0\0", 4 - k);
          }
        }
      }
    } else {
      if (checkRegion(newTables[i].origOffset, newTables[i].len)) {
        (*outputFunc)(outputStream, (char *)file + newTables[i].origOffset,
                      newTables[i].len);
      } else {
        for (j = 0; j < newTables[i].len; ++j) {
          (*outputFunc)(outputStream, "\0", 1);
        }
      }
    }
    if (newTables[i].len & 3) {
      (*outputFunc)(outputStream, "\0\0\0", 4 - (newTables[i].len & 3));
    }
  }

  gfree(newHMTXTab);
  gfree(newHHEATab);
  gfree(newCmapTab);
  gfree(newNameTab);
  gfree(tableDir);
  gfree(newTables);
 done1:
  gfree(locaTable);
}

void FoFiTrueType::cvtEncoding(char **encoding,
                               FoFiOutputFunc outputFunc,
                               void *outputStream) {
  char *name;
  GString *buf;
  int i;

  (*outputFunc)(outputStream, "/Encoding 256 array\n", 20);
  if (encoding) {
    for (i = 0; i < 256; ++i) {
      if (!(name = encoding[i])) {
        name = ".notdef";
      }
      buf = GString::format("dup {0:d} /", i);
      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
      delete buf;
      (*outputFunc)(outputStream, name, strlen(name));
      (*outputFunc)(outputStream, " put\n", 5);
    }
  } else {
    for (i = 0; i < 256; ++i) {
      buf = GString::format("dup {0:d} /c{1:02x} put\n", i, i);
      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
      delete buf;
    }
  }
  (*outputFunc)(outputStream, "readonly def\n", 13);
}

void FoFiTrueType::cvtCharStrings(char **encoding,
                                  Gushort *codeToGID,
                                  FoFiOutputFunc outputFunc,
                                  void *outputStream) {
  char *name;
  GString *buf;
  char buf2[16];
  int i, k;

  // always define '.notdef'
  (*outputFunc)(outputStream, "/CharStrings 256 dict dup begin\n", 32);
  (*outputFunc)(outputStream, "/.notdef 0 def\n", 15);

  // if there's no 'cmap' table, punt
  if (nCmaps == 0) {
    goto err;
  }

  // map char name to glyph index:
  // 1. use encoding to map name to char code
  // 2. use codeToGID to map char code to glyph index
  // N.B. We do this in reverse order because font subsets can have
  //      weird encodings that use the same character name twice, and
  //      the first definition is probably the one we want.
  k = 0; // make gcc happy
  for (i = 255; i >= 0; --i) {
    if (encoding) {
      name = encoding[i];
    } else {
      sprintf(buf2, "c%02x", i);
      name = buf2;
    }
    if (name && strcmp(name, ".notdef")) {
      k = codeToGID[i];
      // note: Distiller (maybe Adobe's PS interpreter in general)
      // doesn't like TrueType fonts that have CharStrings entries
      // which point to nonexistent glyphs, hence the (k < nGlyphs)
      // test
      if (k > 0 && k < nGlyphs) {
        (*outputFunc)(outputStream, "/", 1);
        (*outputFunc)(outputStream, name, strlen(name));
        buf = GString::format(" {0:d} def\n", k);
        (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
        delete buf;
      }
    }
  }

 err:
  (*outputFunc)(outputStream, "end readonly def\n", 17);
}

void FoFiTrueType::cvtSfnts(FoFiOutputFunc outputFunc,
                            void *outputStream, GString *name,
                            GBool needVerticalMetrics) {
  Guchar headData[54];
  TrueTypeLoca *locaTable;
  Guchar *locaData;
  TrueTypeTable newTables[nT42Tables];
  Guchar tableDir[12 + nT42Tables*16];
  GBool ok;
  Guint checksum;
  int nNewTables;
  int length, pos, glyfPos, i, j, k;
  Guchar vheaTab[36] = {
    0, 1, 0, 0,                 // table version number
    0, 0,                       // ascent
    0, 0,                       // descent
    0, 0,                       // reserved
    0, 0,                       // max advance height
    0, 0,                       // min top side bearing
    0, 0,                       // min bottom side bearing
    0, 0,                       // y max extent
    0, 0,                       // caret slope rise
    0, 1,                       // caret slope run
    0, 0,                       // caret offset
    0, 0,                       // reserved
    0, 0,                       // reserved
    0, 0,                       // reserved
    0, 0,                       // reserved
    0, 0,                       // metric data format
    0, 1                        // number of advance heights in vmtx table
  };
  Guchar *vmtxTab;
  GBool needVhea, needVmtx;
  int advance;

  // construct the 'head' table, zero out the font checksum
  i = seekTable("head");
  pos = tables[i].offset;
  if (!checkRegion(pos, 54)) {
    return;
  }
  memcpy(headData, file + pos, 54);
  headData[8] = headData[9] = headData[10] = headData[11] = (Guchar)0;

  // read the original 'loca' table, pad entries out to 4 bytes, and
  // sort it into proper order -- some (non-compliant) fonts have
  // out-of-order loca tables; in order to correctly handle the case
  // where (compliant) fonts have empty entries in the middle of the
  // table, cmpTrueTypeLocaPos uses offset as its primary sort key,
  // and idx as its secondary key (ensuring that adjacent entries with
  // the same pos value remain in the same order)
  locaTable = (TrueTypeLoca *)gmallocn(nGlyphs + 1, sizeof(TrueTypeLoca));
  i = seekTable("loca");
  pos = tables[i].offset;
  ok = gTrue;
  for (i = 0; i <= nGlyphs; ++i) {
    locaTable[i].idx = i;
    if (locaFmt) {
      locaTable[i].origOffset = (int)getU32BE(pos + i*4, &ok);
    } else {
      locaTable[i].origOffset = 2 * getU16BE(pos + i*2, &ok);
    }
  }
  qsort(locaTable, nGlyphs + 1, sizeof(TrueTypeLoca),
        &cmpTrueTypeLocaOffset);
  for (i = 0; i < nGlyphs; ++i) {
    locaTable[i].len = locaTable[i+1].origOffset - locaTable[i].origOffset;
  }
  locaTable[nGlyphs].len = 0;
  qsort(locaTable, nGlyphs + 1, sizeof(TrueTypeLoca),
        &cmpTrueTypeLocaIdx);
  pos = 0;
  for (i = 0; i <= nGlyphs; ++i) {
    locaTable[i].newOffset = pos;
    pos += locaTable[i].len;
    if (pos & 3) {
      pos += 4 - (pos & 3);
    }
  }

  // construct the new 'loca' table
  locaData = (Guchar *)gmallocn(nGlyphs + 1, (locaFmt ? 4 : 2));
  for (i = 0; i <= nGlyphs; ++i) {
    pos = locaTable[i].newOffset;
    if (locaFmt) {
      locaData[4*i  ] = (Guchar)(pos >> 24);
      locaData[4*i+1] = (Guchar)(pos >> 16);
      locaData[4*i+2] = (Guchar)(pos >>  8);
      locaData[4*i+3] = (Guchar) pos;
    } else {
      locaData[2*i  ] = (Guchar)(pos >> 9);
      locaData[2*i+1] = (Guchar)(pos >> 1);
    }
  }

  // count the number of tables
  nNewTables = 0;
  for (i = 0; i < nT42Tables; ++i) {
    if (t42Tables[i].required ||
        seekTable(t42Tables[i].tag) >= 0) {
      ++nNewTables;
    }
  }
  vmtxTab = NULL; // make gcc happy
  advance = 0; // make gcc happy
  if (needVerticalMetrics) {
    needVhea = seekTable("vhea") < 0;
    needVmtx = seekTable("vmtx") < 0;
    if (needVhea || needVmtx) {
      i = seekTable("head");
      advance = getU16BE(tables[i].offset + 18, &ok); // units per em
      if (needVhea) {
        ++nNewTables;
      }
      if (needVmtx) {
        ++nNewTables;
      }
    }
  }

  // construct the new table headers, including table checksums
  // (pad each table out to a multiple of 4 bytes)
  pos = 12 + nNewTables*16;
  k = 0;
  for (i = 0; i < nT42Tables; ++i) {
    length = -1;
    checksum = 0; // make gcc happy
    if (i == t42HeadTable) {
      length = 54;
      checksum = computeTableChecksum(headData, 54);
    } else if (i == t42LocaTable) {
      length = (nGlyphs + 1) * (locaFmt ? 4 : 2);
      checksum = computeTableChecksum(locaData, length);
    } else if (i == t42GlyfTable) {
      length = 0;
      checksum = 0;
      glyfPos = tables[seekTable("glyf")].offset;
      for (j = 0; j < nGlyphs; ++j) {
        length += locaTable[j].len;
        if (length & 3) {
          length += 4 - (length & 3);
        }
        if (checkRegion(glyfPos + locaTable[j].origOffset, locaTable[j].len)) {
          checksum +=
              computeTableChecksum(file + glyfPos + locaTable[j].origOffset,
                                   locaTable[j].len);
        }
      }
    } else {
      if ((j = seekTable(t42Tables[i].tag)) >= 0) {
        length = tables[j].len;
        if (checkRegion(tables[j].offset, length)) {
          checksum = computeTableChecksum(file + tables[j].offset, length);
        }
      } else if (needVerticalMetrics && i == t42VheaTable) {
        vheaTab[10] = advance / 256;    // max advance height
        vheaTab[11] = advance % 256;
        length = sizeof(vheaTab);
        checksum = computeTableChecksum(vheaTab, length);
      } else if (needVerticalMetrics && i == t42VmtxTable) {
        length = 4 + (nGlyphs - 1) * 4;
        vmtxTab = (Guchar *)gmalloc(length);
        vmtxTab[0] = advance / 256;
        vmtxTab[1] = advance % 256;
        for (j = 2; j < length; j += 2) {
          vmtxTab[j] = 0;
          vmtxTab[j+1] = 0;
        }
        checksum = computeTableChecksum(vmtxTab, length);
      } else if (t42Tables[i].required) {
        //~ error(-1, "Embedded TrueType font is missing a required table ('%s')",
        //~       t42Tables[i].tag);
        length = 0;
        checksum = 0;
      }
    }
    if (length >= 0) {
      newTables[k].tag = ((t42Tables[i].tag[0] & 0xff) << 24) |
                         ((t42Tables[i].tag[1] & 0xff) << 16) |
                         ((t42Tables[i].tag[2] & 0xff) <<  8) |
                          (t42Tables[i].tag[3] & 0xff);
      newTables[k].checksum = checksum;
      newTables[k].offset = pos;
      newTables[k].len = length;
      pos += length;
      if (pos & 3) {
        pos += 4 - (length & 3);
      }
      ++k;
    }
  }

  // construct the table directory
  tableDir[0] = 0x00;           // sfnt version
  tableDir[1] = 0x01;
  tableDir[2] = 0x00;
  tableDir[3] = 0x00;
  tableDir[4] = 0;              // numTables
  tableDir[5] = nNewTables;
  tableDir[6] = 0;              // searchRange
  tableDir[7] = (Guchar)128;
  tableDir[8] = 0;              // entrySelector
  tableDir[9] = 3;
  tableDir[10] = 0;             // rangeShift
  tableDir[11] = (Guchar)(16 * nNewTables - 128);
  pos = 12;
  for (i = 0; i < nNewTables; ++i) {
    tableDir[pos   ] = (Guchar)(newTables[i].tag >> 24);
    tableDir[pos+ 1] = (Guchar)(newTables[i].tag >> 16);
    tableDir[pos+ 2] = (Guchar)(newTables[i].tag >>  8);
    tableDir[pos+ 3] = (Guchar) newTables[i].tag;
    tableDir[pos+ 4] = (Guchar)(newTables[i].checksum >> 24);
    tableDir[pos+ 5] = (Guchar)(newTables[i].checksum >> 16);
    tableDir[pos+ 6] = (Guchar)(newTables[i].checksum >>  8);
    tableDir[pos+ 7] = (Guchar) newTables[i].checksum;
    tableDir[pos+ 8] = (Guchar)(newTables[i].offset >> 24);
    tableDir[pos+ 9] = (Guchar)(newTables[i].offset >> 16);
    tableDir[pos+10] = (Guchar)(newTables[i].offset >>  8);
    tableDir[pos+11] = (Guchar) newTables[i].offset;
    tableDir[pos+12] = (Guchar)(newTables[i].len >> 24);
    tableDir[pos+13] = (Guchar)(newTables[i].len >> 16);
    tableDir[pos+14] = (Guchar)(newTables[i].len >>  8);
    tableDir[pos+15] = (Guchar) newTables[i].len;
    pos += 16;
  }

  // compute the font checksum and store it in the head table
  checksum = computeTableChecksum(tableDir, 12 + nNewTables*16);
  for (i = 0; i < nNewTables; ++i) {
    checksum += newTables[i].checksum;
  }
  checksum = 0xb1b0afba - checksum; // because the TrueType spec says so
  headData[ 8] = (Guchar)(checksum >> 24);
  headData[ 9] = (Guchar)(checksum >> 16);
  headData[10] = (Guchar)(checksum >>  8);
  headData[11] = (Guchar) checksum;

  // start the sfnts array
  if (name) {
    (*outputFunc)(outputStream, "/", 1);
    (*outputFunc)(outputStream, name->getCString(), name->getLength());
    (*outputFunc)(outputStream, " [\n", 3);
  } else {
    (*outputFunc)(outputStream, "/sfnts [\n", 9);
  }

  // write the table directory
  dumpString(tableDir, 12 + nNewTables*16, outputFunc, outputStream);

  // write the tables
  for (i = 0; i < nNewTables; ++i) {
    if (i == t42HeadTable) {
      dumpString(headData, 54, outputFunc, outputStream);
    } else if (i == t42LocaTable) {
      length = (nGlyphs + 1) * (locaFmt ? 4 : 2);
      dumpString(locaData, length, outputFunc, outputStream);
    } else if (i == t42GlyfTable) {
      glyfPos = tables[seekTable("glyf")].offset;
      for (j = 0; j < nGlyphs; ++j) {
        if (locaTable[j].len > 0 &&
            checkRegion(glyfPos + locaTable[j].origOffset, locaTable[j].len)) {
          dumpString(file + glyfPos + locaTable[j].origOffset,
                     locaTable[j].len, outputFunc, outputStream);
        }
      }
    } else {
      // length == 0 means the table is missing and the error was
      // already reported during the construction of the table
      // headers
      if ((length = newTables[i].len) > 0) {
        if ((j = seekTable(t42Tables[i].tag)) >= 0 &&
            checkRegion(tables[j].offset, tables[j].len)) {
          dumpString(file + tables[j].offset, tables[j].len,
                     outputFunc, outputStream);
        } else if (needVerticalMetrics && i == t42VheaTable) {
          dumpString(vheaTab, length, outputFunc, outputStream);
        } else if (needVerticalMetrics && i == t42VmtxTable) {
          dumpString(vmtxTab, length, outputFunc, outputStream);
          gfree(vmtxTab);
        }
      }
    }
  }

  // end the sfnts array
  (*outputFunc)(outputStream, "] def\n", 6);

  gfree(locaData);
  gfree(locaTable);
}

void FoFiTrueType::dumpString(Guchar *s, int length,
                              FoFiOutputFunc outputFunc,
                              void *outputStream) {
  GString *buf;
  int pad, i, j;

  (*outputFunc)(outputStream, "<", 1);
  for (i = 0; i < length; i += 32) {
    for (j = 0; j < 32 && i+j < length; ++j) {
      buf = GString::format("{0:02x}", s[i+j] & 0xff);
      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
      delete buf;
    }
    if (i % (65536 - 32) == 65536 - 64) {
      (*outputFunc)(outputStream, ">\n<", 3);
    } else if (i+32 < length) {
      (*outputFunc)(outputStream, "\n", 1);
    }
  }
  if (length & 3) {
    pad = 4 - (length & 3);
    for (i = 0; i < pad; ++i) {
      (*outputFunc)(outputStream, "00", 2);
    }
  }
  // add an extra zero byte because the Adobe Type 42 spec says so
  (*outputFunc)(outputStream, "00>\n", 4);
}

Guint FoFiTrueType::computeTableChecksum(Guchar *data, int length) {
  Guint checksum, word;
  int i;

  checksum = 0;
  for (i = 0; i+3 < length; i += 4) {
    word = ((data[i  ] & 0xff) << 24) +
           ((data[i+1] & 0xff) << 16) +
           ((data[i+2] & 0xff) <<  8) +
            (data[i+3] & 0xff);
    checksum += word;
  }
  if (length & 3) {
    word = 0;
    i = length & ~3;
    switch (length & 3) {
    case 3:
      word |= (data[i+2] & 0xff) <<  8;
    case 2:
      word |= (data[i+1] & 0xff) << 16;
    case 1:
      word |= (data[i  ] & 0xff) << 24;
      break;
    }
    checksum += word;
  }
  return checksum;
}

void FoFiTrueType::parse() {
  Guint topTag;
  int pos, ver, i, j;

  parsedOk = gTrue;

  // look for a collection (TTC)
  topTag = getU32BE(0, &parsedOk);
  if (!parsedOk) {
    return;
  }
  if (topTag == ttcfTag) {
    pos = getU32BE(12, &parsedOk);
    if (!parsedOk) {
      return;
    }
  } else {
    pos = 0;
  }

  // check the sfnt version
  ver = getU32BE(pos, &parsedOk);
  if (!parsedOk) {
    return;
  }
  openTypeCFF = ver == 0x4f54544f; // 'OTTO'

  // read the table directory
  nTables = getU16BE(pos + 4, &parsedOk);
  if (!parsedOk) {
    return;
  }
  tables = (TrueTypeTable *)gmallocn(nTables, sizeof(TrueTypeTable));
  pos += 12;
  for (i = 0; i < nTables; ++i) {
    tables[i].tag = getU32BE(pos, &parsedOk);
    tables[i].checksum = getU32BE(pos + 4, &parsedOk);
    tables[i].offset = (int)getU32BE(pos + 8, &parsedOk);
    tables[i].len = (int)getU32BE(pos + 12, &parsedOk);
    if (tables[i].offset + tables[i].len < tables[i].offset ||
        tables[i].offset + tables[i].len > len) {
      parsedOk = gFalse;
    }
    pos += 16;
  }
  if (!parsedOk) {
    return;
  }

  // check for tables that are required by both the TrueType spec and
  // the Type 42 spec
  if (seekTable("head") < 0 ||
      seekTable("hhea") < 0 ||
      seekTable("maxp") < 0 ||
      seekTable("hmtx") < 0 ||
      (!openTypeCFF && seekTable("loca") < 0) ||
      (!openTypeCFF && seekTable("glyf") < 0) ||
      (openTypeCFF && seekTable("CFF ") < 0)) {
    parsedOk = gFalse;
    return;
  }

  // read the cmaps
  if ((i = seekTable("cmap")) >= 0) {
    pos = tables[i].offset + 2;
    nCmaps = getU16BE(pos, &parsedOk);
    pos += 2;
    if (!parsedOk) {
      return;
    }
    cmaps = (TrueTypeCmap *)gmallocn(nCmaps, sizeof(TrueTypeCmap));
    for (j = 0; j < nCmaps; ++j) {
      cmaps[j].platform = getU16BE(pos, &parsedOk);
      cmaps[j].encoding = getU16BE(pos + 2, &parsedOk);
      cmaps[j].offset = tables[i].offset + getU32BE(pos + 4, &parsedOk);
      pos += 8;
      cmaps[j].fmt = getU16BE(cmaps[j].offset, &parsedOk);
      cmaps[j].len = getU16BE(cmaps[j].offset + 2, &parsedOk);
    }
    if (!parsedOk) {
      return;
    }
  } else {
    nCmaps = 0;
  }

  // get the number of glyphs from the maxp table
  i = seekTable("maxp");
  nGlyphs = getU16BE(tables[i].offset + 4, &parsedOk);
  if (!parsedOk) {
    return;
  }

  // get the bbox and loca table format from the head table
  i = seekTable("head");
  bbox[0] = getS16BE(tables[i].offset + 36, &parsedOk);
  bbox[1] = getS16BE(tables[i].offset + 38, &parsedOk);
  bbox[2] = getS16BE(tables[i].offset + 40, &parsedOk);
  bbox[3] = getS16BE(tables[i].offset + 42, &parsedOk);
  locaFmt = getS16BE(tables[i].offset + 50, &parsedOk);
  if (!parsedOk) {
    return;
  }

  // make sure the loca table is sane (correct length and entries are
  // in bounds)
  if (!openTypeCFF) {
    i = seekTable("loca");
    if (tables[i].len < 0) {
      parsedOk = gFalse;
      return;
    }
    if (tables[i].len < (nGlyphs + 1) * (locaFmt ? 4 : 2)) {
      nGlyphs = tables[i].len / (locaFmt ? 4 : 2) - 1;
    }
    for (j = 0; j <= nGlyphs; ++j) {
      if (locaFmt) {
        pos = (int)getU32BE(tables[i].offset + j*4, &parsedOk);
      } else {
        pos = getU16BE(tables[i].offset + j*2, &parsedOk);
      }
      if (pos < 0 || pos > len) {
        parsedOk = gFalse;
      }
    }
    if (!parsedOk) {
      return;
    }
  }

  // read the post table
  readPostTable();
}

void FoFiTrueType::readPostTable() {
  GString *name;
  int tablePos, postFmt, stringIdx, stringPos;
  GBool ok;
  int i, j, n, m;

  ok = gTrue;
  if ((i = seekTable("post")) < 0) {
    return;
  }
  tablePos = tables[i].offset;
  postFmt = getU32BE(tablePos, &ok);
  if (!ok) {
    goto err;
  }
  if (postFmt == 0x00010000) {
    nameToGID = new GHash(gTrue);
    for (i = 0; i < 258; ++i) {
      nameToGID->add(new GString(macGlyphNames[i]), i);
    }
  } else if (postFmt == 0x00020000) {
    nameToGID = new GHash(gTrue);
    n = getU16BE(tablePos + 32, &ok);
    if (!ok) {
      goto err;
    }
    if (n > nGlyphs) {
      n = nGlyphs;
    }
    stringIdx = 0;
    stringPos = tablePos + 34 + 2*n;
    for (i = 0; i < n; ++i) {
      j = getU16BE(tablePos + 34 + 2*i, &ok);
      if (j < 258) {
        nameToGID->removeInt(macGlyphNames[j]);
        nameToGID->add(new GString(macGlyphNames[j]), i);
      } else {
        j -= 258;
        if (j != stringIdx) {
          for (stringIdx = 0, stringPos = tablePos + 34 + 2*n;
               stringIdx < j;
               ++stringIdx, stringPos += 1 + getU8(stringPos, &ok)) ;
          if (!ok) {
            goto err;
          }
        }
        m = getU8(stringPos, &ok);
        if (!ok || !checkRegion(stringPos + 1, m)) {
          goto err;
        }
        name = new GString((char *)&file[stringPos + 1], m);
        nameToGID->removeInt(name);
        nameToGID->add(name, i);
        ++stringIdx;
        stringPos += 1 + m;
      }
    }
  } else if (postFmt == 0x00028000) {
    nameToGID = new GHash(gTrue);
    for (i = 0; i < nGlyphs; ++i) {
      j = getU8(tablePos + 32 + i, &ok);
      if (!ok) {
        goto err;
      }
      if (j < 258) {
        nameToGID->removeInt(macGlyphNames[j]);
        nameToGID->add(new GString(macGlyphNames[j]), i);
      }
    }
  }

  return;

 err:
  if (nameToGID) {
    delete nameToGID;
    nameToGID = NULL;
  }
}

int FoFiTrueType::seekTable(char *tag) {
  Guint tagI;
  int i;

  tagI = ((tag[0] & 0xff) << 24) |
         ((tag[1] & 0xff) << 16) |
         ((tag[2] & 0xff) << 8) |
          (tag[3] & 0xff);
  for (i = 0; i < nTables; ++i) {
    if (tables[i].tag == tagI) {
      return i;
    }
  }
  return -1;
}

/* [<][>][^][v][top][bottom][index][help] */