//======================================================================== // // FoFiIdentifier.cc // // Copyright 2009 Glyph & Cog, LLC // //======================================================================== #include #ifdef USE_GCC_PRAGMAS #pragma implementation #endif #include #include #include #include "gtypes.h" #include "gmem.h" #include "gmempp.h" #include "GString.h" #include "GList.h" #include "FoFiIdentifier.h" //------------------------------------------------------------------------ class Reader { public: virtual ~Reader() {} // Read one byte. Returns -1 if past EOF. virtual int getByte(int pos) = 0; // Read a big-endian unsigned 16-bit integer. Fills in *val and // returns true if successful. virtual GBool getU16BE(int pos, int *val) = 0; // Read a big-endian unsigned 32-bit integer. Fills in *val and // returns true if successful. virtual GBool getU32BE(int pos, Guint *val) = 0; // Read a little-endian unsigned 32-bit integer. Fills in *val and // returns true if successful. virtual GBool getU32LE(int pos, Guint *val) = 0; // Read a big-endian unsigned -byte integer, where 1 <= size // <= 4. Fills in *val and returns true if successful. virtual GBool getUVarBE(int pos, int size, Guint *val) = 0; // Compare against a string. Returns true if equal. virtual GBool cmp(int pos, const char *s) = 0; }; //------------------------------------------------------------------------ class MemReader: public Reader { public: static MemReader *make(char *bufA, int lenA); virtual ~MemReader(); virtual int getByte(int pos); virtual GBool getU16BE(int pos, int *val); virtual GBool getU32BE(int pos, Guint *val); virtual GBool getU32LE(int pos, Guint *val); virtual GBool getUVarBE(int pos, int size, Guint *val); virtual GBool cmp(int pos, const char *s); private: MemReader(char *bufA, int lenA); char *buf; int len; }; MemReader *MemReader::make(char *bufA, int lenA) { return new MemReader(bufA, lenA); } MemReader::MemReader(char *bufA, int lenA) { buf = bufA; len = lenA; } MemReader::~MemReader() { } int MemReader::getByte(int pos) { if (pos < 0 || pos >= len) { return -1; } return buf[pos] & 0xff; } GBool MemReader::getU16BE(int pos, int *val) { if (pos < 0 || pos > len - 2) { return gFalse; } *val = ((buf[pos] & 0xff) << 8) + (buf[pos+1] & 0xff); return gTrue; } GBool MemReader::getU32BE(int pos, Guint *val) { if (pos < 0 || pos > len - 4) { return gFalse; } *val = ((buf[pos] & 0xff) << 24) + ((buf[pos+1] & 0xff) << 16) + ((buf[pos+2] & 0xff) << 8) + (buf[pos+3] & 0xff); return gTrue; } GBool MemReader::getU32LE(int pos, Guint *val) { if (pos < 0 || pos > len - 4) { return gFalse; } *val = (buf[pos] & 0xff) + ((buf[pos+1] & 0xff) << 8) + ((buf[pos+2] & 0xff) << 16) + ((buf[pos+3] & 0xff) << 24); return gTrue; } GBool MemReader::getUVarBE(int pos, int size, Guint *val) { int i; if (size < 1 || size > 4 || pos < 0 || pos > len - size) { return gFalse; } *val = 0; for (i = 0; i < size; ++i) { *val = (*val << 8) + (buf[pos + i] & 0xff); } return gTrue; } GBool MemReader::cmp(int pos, const char *s) { int n; n = (int)strlen(s); if (pos < 0 || len < n || pos > len - n) { return gFalse; } return !memcmp(buf + pos, s, n); } //------------------------------------------------------------------------ class FileReader: public Reader { public: static FileReader *make(char *fileName); virtual ~FileReader(); virtual int getByte(int pos); virtual GBool getU16BE(int pos, int *val); virtual GBool getU32BE(int pos, Guint *val); virtual GBool getU32LE(int pos, Guint *val); virtual GBool getUVarBE(int pos, int size, Guint *val); virtual GBool cmp(int pos, const char *s); private: FileReader(FILE *fA); GBool fillBuf(int pos, int len); FILE *f; char buf[1024]; int bufPos, bufLen; }; FileReader *FileReader::make(char *fileName) { FILE *fA; if (!(fA = fopen(fileName, "rb"))) { return NULL; } return new FileReader(fA); } FileReader::FileReader(FILE *fA) { f = fA; bufPos = 0; bufLen = 0; } FileReader::~FileReader() { fclose(f); } int FileReader::getByte(int pos) { if (!fillBuf(pos, 1)) { return -1; } return buf[pos - bufPos] & 0xff; } GBool FileReader::getU16BE(int pos, int *val) { if (!fillBuf(pos, 2)) { return gFalse; } *val = ((buf[pos - bufPos] & 0xff) << 8) + (buf[pos - bufPos + 1] & 0xff); return gTrue; } GBool FileReader::getU32BE(int pos, Guint *val) { if (!fillBuf(pos, 4)) { return gFalse; } *val = ((buf[pos - bufPos] & 0xff) << 24) + ((buf[pos - bufPos + 1] & 0xff) << 16) + ((buf[pos - bufPos + 2] & 0xff) << 8) + (buf[pos - bufPos + 3] & 0xff); return gTrue; } GBool FileReader::getU32LE(int pos, Guint *val) { if (!fillBuf(pos, 4)) { return gFalse; } *val = (buf[pos - bufPos] & 0xff) + ((buf[pos - bufPos + 1] & 0xff) << 8) + ((buf[pos - bufPos + 2] & 0xff) << 16) + ((buf[pos - bufPos + 3] & 0xff) << 24); return gTrue; } GBool FileReader::getUVarBE(int pos, int size, Guint *val) { int i; if (size < 1 || size > 4 || !fillBuf(pos, size)) { return gFalse; } *val = 0; for (i = 0; i < size; ++i) { *val = (*val << 8) + (buf[pos - bufPos + i] & 0xff); } return gTrue; } GBool FileReader::cmp(int pos, const char *s) { int n; n = (int)strlen(s); if (!fillBuf(pos, n)) { return gFalse; } return !memcmp(buf - bufPos + pos, s, n); } GBool FileReader::fillBuf(int pos, int len) { if (pos < 0 || len < 0 || len > (int)sizeof(buf) || pos > INT_MAX - (int)sizeof(buf)) { return gFalse; } if (pos >= bufPos && pos + len <= bufPos + bufLen) { return gTrue; } if (fseek(f, pos, SEEK_SET)) { return gFalse; } bufPos = pos; bufLen = (int)fread(buf, 1, sizeof(buf), f); if (bufLen < len) { return gFalse; } return gTrue; } //------------------------------------------------------------------------ class StreamReader: public Reader { public: static StreamReader *make(int (*getCharA)(void *data), void *dataA); virtual ~StreamReader(); virtual int getByte(int pos); virtual GBool getU16BE(int pos, int *val); virtual GBool getU32BE(int pos, Guint *val); virtual GBool getU32LE(int pos, Guint *val); virtual GBool getUVarBE(int pos, int size, Guint *val); virtual GBool cmp(int pos, const char *s); private: StreamReader(int (*getCharA)(void *data), void *dataA); GBool fillBuf(int pos, int len); int (*getChar)(void *data); void *data; int streamPos; char buf[1024]; int bufPos, bufLen; }; StreamReader *StreamReader::make(int (*getCharA)(void *data), void *dataA) { return new StreamReader(getCharA, dataA); } StreamReader::StreamReader(int (*getCharA)(void *data), void *dataA) { getChar = getCharA; data = dataA; streamPos = 0; bufPos = 0; bufLen = 0; } StreamReader::~StreamReader() { } int StreamReader::getByte(int pos) { if (!fillBuf(pos, 1)) { return -1; } return buf[pos - bufPos] & 0xff; } GBool StreamReader::getU16BE(int pos, int *val) { if (!fillBuf(pos, 2)) { return gFalse; } *val = ((buf[pos - bufPos] & 0xff) << 8) + (buf[pos - bufPos + 1] & 0xff); return gTrue; } GBool StreamReader::getU32BE(int pos, Guint *val) { if (!fillBuf(pos, 4)) { return gFalse; } *val = ((buf[pos - bufPos] & 0xff) << 24) + ((buf[pos - bufPos + 1] & 0xff) << 16) + ((buf[pos - bufPos + 2] & 0xff) << 8) + (buf[pos - bufPos + 3] & 0xff); return gTrue; } GBool StreamReader::getU32LE(int pos, Guint *val) { if (!fillBuf(pos, 4)) { return gFalse; } *val = (buf[pos - bufPos] & 0xff) + ((buf[pos - bufPos + 1] & 0xff) << 8) + ((buf[pos - bufPos + 2] & 0xff) << 16) + ((buf[pos - bufPos + 3] & 0xff) << 24); return gTrue; } GBool StreamReader::getUVarBE(int pos, int size, Guint *val) { int i; if (size < 1 || size > 4 || !fillBuf(pos, size)) { return gFalse; } *val = 0; for (i = 0; i < size; ++i) { *val = (*val << 8) + (buf[pos - bufPos + i] & 0xff); } return gTrue; } GBool StreamReader::cmp(int pos, const char *s) { int n; n = (int)strlen(s); if (!fillBuf(pos, n)) { return gFalse; } return !memcmp(buf - bufPos + pos, s, n); } GBool StreamReader::fillBuf(int pos, int len) { int c; if (pos < 0 || len < 0 || len > (int)sizeof(buf) || pos > INT_MAX - (int)sizeof(buf)) { return gFalse; } if (pos < bufPos) { return gFalse; } // if requested region will not fit in the current buffer... if (pos + len > bufPos + (int)sizeof(buf)) { // if the start of the requested data is already in the buffer, move // it to the start of the buffer if (pos < bufPos + bufLen) { bufLen -= pos - bufPos; memmove(buf, buf + (pos - bufPos), bufLen); bufPos = pos; // otherwise discard data from the // stream until we get to the requested position } else { bufPos += bufLen; bufLen = 0; while (bufPos < pos) { if ((c = (*getChar)(data)) < 0) { return gFalse; } ++bufPos; } } } // read the rest of the requested data while (bufPos + bufLen < pos + len) { if ((c = (*getChar)(data)) < 0) { return gFalse; } buf[bufLen++] = (char)c; } return gTrue; } //------------------------------------------------------------------------ static FoFiIdentifierType identify(Reader *reader); static FoFiIdentifierType identifyOpenType(Reader *reader); static FoFiIdentifierType identifyCFF(Reader *reader, int start); FoFiIdentifierType FoFiIdentifier::identifyMem(char *file, int len) { MemReader *reader; FoFiIdentifierType type; if (!(reader = MemReader::make(file, len))) { return fofiIdError; } type = identify(reader); delete reader; return type; } FoFiIdentifierType FoFiIdentifier::identifyFile(char *fileName) { FileReader *reader; FoFiIdentifierType type; int n; if (!(reader = FileReader::make(fileName))) { return fofiIdError; } type = identify(reader); delete reader; // Mac OS X dfont files don't have any sort of header or magic number, // so look at the file name extension if (type == fofiIdUnknown) { n = (int)strlen(fileName); if (n >= 6 && !strcmp(fileName + n - 6, ".dfont")) { type = fofiIdDfont; } } return type; } FoFiIdentifierType FoFiIdentifier::identifyStream(int (*getChar)(void *data), void *data) { StreamReader *reader; FoFiIdentifierType type; if (!(reader = StreamReader::make(getChar, data))) { return fofiIdError; } type = identify(reader); delete reader; return type; } static FoFiIdentifierType identify(Reader *reader) { Guint n; //----- PFA if (reader->cmp(0, "%!PS-AdobeFont-1") || reader->cmp(0, "%!FontType1")) { return fofiIdType1PFA; } //----- PFB if (reader->getByte(0) == 0x80 && reader->getByte(1) == 0x01 && reader->getU32LE(2, &n)) { if ((n >= 16 && reader->cmp(6, "%!PS-AdobeFont-1")) || (n >= 11 && reader->cmp(6, "%!FontType1"))) { return fofiIdType1PFB; } } //----- TrueType if ((reader->getByte(0) == 0x00 && reader->getByte(1) == 0x01 && reader->getByte(2) == 0x00 && reader->getByte(3) == 0x00) || (reader->getByte(0) == 0x74 && // 'true' reader->getByte(1) == 0x72 && reader->getByte(2) == 0x75 && reader->getByte(3) == 0x65)) { return fofiIdTrueType; } if (reader->getByte(0) == 0x74 && // 'ttcf' reader->getByte(1) == 0x74 && reader->getByte(2) == 0x63 && reader->getByte(3) == 0x66) { return fofiIdTrueTypeCollection; } //----- OpenType if (reader->getByte(0) == 0x4f && // 'OTTO reader->getByte(1) == 0x54 && reader->getByte(2) == 0x54 && reader->getByte(3) == 0x4f) { return identifyOpenType(reader); } //----- CFF if (reader->getByte(0) == 0x01 && reader->getByte(1) == 0x00) { return identifyCFF(reader, 0); } // some tools embed CFF fonts with an extra whitespace char at the // beginning if (reader->getByte(1) == 0x01 && reader->getByte(2) == 0x00) { return identifyCFF(reader, 1); } return fofiIdUnknown; } static FoFiIdentifierType identifyOpenType(Reader *reader) { FoFiIdentifierType type; Guint offset; int nTables, i; if (!reader->getU16BE(4, &nTables)) { return fofiIdUnknown; } for (i = 0; i < nTables; ++i) { if (reader->cmp(12 + i*16, "CFF ")) { if (reader->getU32BE(12 + i*16 + 8, &offset) && offset < (Guint)INT_MAX) { type = identifyCFF(reader, (int)offset); if (type == fofiIdCFF8Bit) { type = fofiIdOpenTypeCFF8Bit; } else if (type == fofiIdCFFCID) { type = fofiIdOpenTypeCFFCID; } return type; } return fofiIdUnknown; } } return fofiIdUnknown; } static FoFiIdentifierType identifyCFF(Reader *reader, int start) { Guint offset0, offset1; int hdrSize, offSize0, offSize1, pos, endPos, b0, n; //----- read the header if (reader->getByte(start) != 0x01 || reader->getByte(start + 1) != 0x00) { return fofiIdUnknown; } if ((hdrSize = reader->getByte(start + 2)) < 0) { return fofiIdUnknown; } if ((offSize0 = reader->getByte(start + 3)) < 1 || offSize0 > 4) { return fofiIdUnknown; } pos = start + hdrSize; if (pos < 0) { return fofiIdUnknown; } //----- skip the name index if (!reader->getU16BE(pos, &n)) { return fofiIdUnknown; } if (n == 0) { pos += 2; } else { if ((offSize1 = reader->getByte(pos + 2)) < 1 || offSize1 > 4) { return fofiIdUnknown; } if (!reader->getUVarBE(pos + 3 + n * offSize1, offSize1, &offset1) || offset1 > (Guint)INT_MAX) { return fofiIdUnknown; } pos += 3 + (n + 1) * offSize1 + (int)offset1 - 1; } if (pos < 0) { return fofiIdUnknown; } //----- parse the top dict index if (!reader->getU16BE(pos, &n) || n < 1) { return fofiIdUnknown; } if ((offSize1 = reader->getByte(pos + 2)) < 1 || offSize1 > 4) { return fofiIdUnknown; } if (!reader->getUVarBE(pos + 3, offSize1, &offset0) || offset0 > (Guint)INT_MAX || !reader->getUVarBE(pos + 3 + offSize1, offSize1, &offset1) || offset1 > (Guint)INT_MAX || offset0 > offset1) { return fofiIdUnknown; } pos = pos + 3 + (n + 1) * offSize1 + (int)offset0 - 1; endPos = pos + 3 + (n + 1) * offSize1 + (int)offset1 - 1; if (pos < 0 || endPos < 0 || pos > endPos) { return fofiIdUnknown; } //----- parse the top dict, look for ROS as first entry // for a CID font, the top dict starts with: // ROS while (pos >= 0 && pos < endPos) { b0 = reader->getByte(pos); if (b0 == 0x1c) { pos += 3; } else if (b0 == 0x1d) { pos += 5; } else if (b0 >= 0xf7 && b0 <= 0xfe) { pos += 2; } else if (b0 >= 0x20 && b0 <= 0xf6) { pos += 1; } else { break; } } if (pos + 1 < endPos && reader->getByte(pos) == 12 && reader->getByte(pos + 1) == 30) { return fofiIdCFFCID; } else { return fofiIdCFF8Bit; } } //------------------------------------------------------------------------ static GList *getTTCFontList(FILE *f); static GList *getDfontFontList(FILE *f); GList *FoFiIdentifier::getFontList(char *fileName) { FILE *f; char buf[4]; GList *ret; if (!(f = fopen(fileName, "rb"))) { return NULL; } if (fread(buf, 1, 4, f) == 4 && buf[0] == 0x74 && // 'ttcf' buf[1] == 0x74 && buf[2] == 0x63 && buf[3] == 0x66) { ret = getTTCFontList(f); } else { ret = getDfontFontList(f); } fclose(f); return ret; } static GList *getTTCFontList(FILE *f) { Guchar buf[12]; Guchar *buf2; int fileLength, nFonts; int tabDirOffset, nTables, nameTabOffset, nNames, stringsOffset; int stringPlatform, stringLength, stringOffset; GBool stringUnicode; int i, j; GList *ret; fseek(f, 0, SEEK_END); fileLength = (int)ftell(f); if (fileLength < 0) { goto err1; } fseek(f, 8, SEEK_SET); if (fread(buf, 1, 4, f) != 4) { goto err1; } nFonts = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; if (nFonts < 0 || 12 + 4 * nFonts > fileLength) { goto err1; } ret = new GList(); for (i = 0; i < nFonts; ++i) { fseek(f, 12 + 4 * i, SEEK_SET); if (fread(buf, 1, 4, f) != 4) { goto err2; } tabDirOffset = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; if (tabDirOffset < 0 || tabDirOffset + 12 < 0 || tabDirOffset + 12 > fileLength) { goto err2; } fseek(f, tabDirOffset, SEEK_SET); if (fread(buf, 1, 12, f) != 12) { goto err2; } nTables = (buf[4] << 8) | buf[5]; if (tabDirOffset + 12 + 16 * nTables < 0 || tabDirOffset + 12 + 16 * nTables > fileLength) { goto err2; } buf2 = (Guchar *)gmallocn(nTables, 16); if ((int)fread(buf2, 1, 16 * nTables, f) != 16 * nTables) { goto err3; } nameTabOffset = 0; // make gcc happy for (j = 0; j < nTables; ++j) { if (buf2[16*j + 0] == 'n' && buf2[16*j + 1] == 'a' && buf2[16*j + 2] == 'm' && buf2[16*j + 3] == 'e') { nameTabOffset = (buf2[16*j + 8] << 24) | (buf2[16*j + 9] << 16) | (buf2[16*j + 10] << 8) | buf2[16*j + 11]; break; } } gfree(buf2); if (j >= nTables) { goto err2; } if (nameTabOffset < 0 || nameTabOffset + 6 < 0 || nameTabOffset + 6 > fileLength) { goto err2; } fseek(f, nameTabOffset, SEEK_SET); if (fread(buf, 1, 6, f) != 6) { goto err2; } nNames = (buf[2] << 8) | buf[3]; stringsOffset = (buf[4] << 8) | buf[5]; if (nameTabOffset + 6 + 12 * nNames < 0 || nameTabOffset + 6 + 12 * nNames > fileLength || nameTabOffset + stringsOffset < 0) { goto err2; } buf2 = (Guchar *)gmallocn(nNames, 12); if ((int)fread(buf2, 1, 12 * nNames, f) != 12 * nNames) { goto err3; } for (j = 0; j < nNames; ++j) { if (buf2[12*j + 6] == 0 && // 0x0004 = full name buf2[12*j + 7] == 4) { break; } } if (j >= nNames) { goto err3; } stringPlatform = (buf2[12*j] << 8) | buf2[12*j + 1]; // stringEncoding = (buf2[12*j + 2] << 8) | buf2[12*j + 3]; stringUnicode = stringPlatform == 0 || stringPlatform == 3; stringLength = (buf2[12*j + 8] << 8) | buf2[12*j + 9]; stringOffset = nameTabOffset + stringsOffset + ((buf2[12*j + 10] << 8) | buf2[12*j + 11]); gfree(buf2); if (stringOffset < 0 || stringOffset + stringLength < 0 || stringOffset + stringLength > fileLength) { goto err2; } buf2 = (Guchar *)gmalloc(stringLength); fseek(f, stringOffset, SEEK_SET); if ((int)fread(buf2, 1, stringLength, f) != stringLength) { goto err3; } if (stringUnicode) { stringLength /= 2; for (j = 0; j < stringLength; ++j) { buf2[j] = buf2[2*j + 1]; } } ret->append(new GString((char *)buf2, stringLength)); gfree(buf2); } return ret; err3: gfree(buf2); err2: deleteGList(ret, GString); err1: return NULL; } static GList *getDfontFontList(FILE *f) { Guchar buf[16]; Guchar *resMap; int fileLength, resMapOffset, resMapLength; int resTypeListOffset, resNameListOffset, nTypes; int refListOffset, nFonts, nameOffset, nameLen; int offset, i; GList *ret; fseek(f, 0, SEEK_END); fileLength = (int)ftell(f); if (fileLength < 0) { goto err1; } fseek(f, 0, SEEK_SET); if (fread((char *)buf, 1, 16, f) != 16) { goto err1; } resMapOffset = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7]; resMapLength = (buf[12] << 24) | (buf[13] << 16) | (buf[14] << 8) | buf[15]; if (resMapOffset < 0 || resMapOffset >= fileLength || resMapLength < 0 || resMapOffset + resMapLength > fileLength || resMapOffset + resMapLength < 0) { goto err1; } if (resMapLength > 32768) { // sanity check - this probably isn't a dfont file goto err1; } resMap = (Guchar *)gmalloc(resMapLength); fseek(f, resMapOffset, SEEK_SET); if ((int)fread((char *)resMap, 1, resMapLength, f) != resMapLength) { goto err2; } resTypeListOffset = (resMap[24] << 8) | resMap[25]; resNameListOffset = (resMap[26] << 8) | resMap[27]; nTypes = ((resMap[28] << 8) | resMap[29]) + 1; if (resTypeListOffset + 2 + nTypes * 8 > resMapLength || resNameListOffset >= resMapLength) { goto err2; } for (i = 0; i < nTypes; ++i) { offset = resTypeListOffset + 2 + 8 * i; if (resMap[offset] == 0x73 && // 'sfnt' resMap[offset+1] == 0x66 && resMap[offset+2] == 0x6e && resMap[offset+3] == 0x74) { nFonts = ((resMap[offset+4] << 8) | resMap[offset+5]) + 1; refListOffset = (resMap[offset+6] << 8) | resMap[offset+7]; break; } } if (i >= nTypes) { goto err2; } if (resTypeListOffset + refListOffset >= resMapLength || resTypeListOffset + refListOffset + nFonts * 12 > resMapLength) { goto err2; } ret = new GList(); for (i = 0; i < nFonts; ++i) { offset = resTypeListOffset + refListOffset + 12 * i; nameOffset = (resMap[offset+2] << 8) | resMap[offset+3]; offset = resNameListOffset + nameOffset; if (offset >= resMapLength) { goto err3; } nameLen = resMap[offset]; if (offset + 1 + nameLen > resMapLength) { goto err3; } ret->append(new GString((char *)resMap + offset + 1, nameLen)); } gfree(resMap); return ret; err3: deleteGList(ret, GString); err2: gfree(resMap); err1: return NULL; }