/* * Support for sfnt-housed fonts for Halibut * * sfnt-housed fonts include TrueType, OpenType, sfnt-housed Type 1 * fonts and a couple of bitmap formats. * * The various tables that can appear in sfnt-housed fonts are defined * in several places. These include: * * The OpenType Specification: * * * The TrueType Reference Manual: * * * Microsoft typography specifications: * */ #include #include #include #include #include "halibut.h" #include "paper.h" typedef struct sfnt_decode_Tag sfnt_decode; struct sfnt_decode_Tag { void (*decoder)(void *src, void *dest); size_t src_len; size_t dest_offset; }; #if 0 /* unused */ static void decode_uint8(void *src, void *dest) { *(unsigned int *)dest = *(unsigned char *)src; } #define d_uint8 decode_uint8, 1 #endif #if 0 /* unused */ static void decode_int8(void *src, void *dest) { *(int *)dest = *(signed char *)src; } #define d_int8 decode_int8, 1 #endif static void decode_uint16(void *src, void *dest) { unsigned char *cp = src; *(unsigned int *)dest = (cp[0] << 8) + cp[1]; } #define d_uint16 decode_uint16, 2 static void decode_int16(void *src, void *dest) { signed char *cp = src; unsigned char *ucp = src; *(int *)dest = (cp[0] << 8) + ucp[1]; } #define d_int16 decode_int16, 2 static void decode_uint32(void *src, void *dest) { unsigned char *cp = src; *(unsigned int *)dest = (cp[0] << 24) + (cp[1] << 16) + (cp[2] << 8) + cp[3]; } #define d_uint32 decode_uint32, 4 static void decode_int32(void *src, void *dest) { signed char *cp = src; unsigned char *ucp = src; *(int *)dest = (cp[0] << 24) + (ucp[1] << 16) + (ucp[2] << 8) + ucp[3]; } #define d_int32 decode_int32, 4 static void decode_skip(void *src, void *dest) { IGNORE(src); IGNORE(dest); /* do nothing */ } #define d_skip(n) decode_skip, (n), 0 static void decode_end(void *src, void *dest) { IGNORE(src); IGNORE(dest); /* never called */ } #define d_end decode_end, 0, 0 static void *decode(sfnt_decode *dec, void *src, void *end, void *dest) { while (dec->decoder != decode_end) { if ((char *)src + dec->src_len > (char *)end) return NULL; dec->decoder(src, (char *)dest + dec->dest_offset); src = (char *)src + dec->src_len; dec++; } return src; } static void *decoden(sfnt_decode *dec, void *src, void *end, void *dest, size_t size, size_t n) { while (n-- && src) { src = decode(dec, src, end, dest); dest = (char *)dest + size; } return src; } /* Decoding specs for simple data types */ sfnt_decode uint16_decode[] = { { d_uint16, 0 }, { d_end } }; sfnt_decode int16_decode[] = { { d_int16, 0 }, { d_end } }; sfnt_decode uint32_decode[] = { { d_uint32, 0 }, { d_end } }; /* Offset subdirectory -- the start of the file */ typedef struct offsubdir_Tag offsubdir; struct offsubdir_Tag { unsigned scaler_type; unsigned numTables; }; sfnt_decode offsubdir_decode[] = { { d_uint32, offsetof(offsubdir, scaler_type) }, { d_uint16, offsetof(offsubdir, numTables) }, { d_skip(6) }, { d_end } }; #define sfnt_00010000 0x00010000 #define TAG_OS_2 0x4f532f32 #define TAG_cmap 0x636d6170 #define TAG_glyf 0x676c7966 #define TAG_head 0x68656164 #define TAG_hhea 0x68686561 #define TAG_hmtx 0x686d7478 #define TAG_kern 0x6b65726e #define TAG_loca 0x6c6f6361 #define TAG_maxp 0x6d617870 #define TAG_name 0x6e616d65 #define TAG_post 0x706f7374 #define sfnt_true 0x74727565 /* Table directory */ typedef struct tabledir_Tag tabledir; struct tabledir_Tag { unsigned tag; unsigned checkSum; unsigned offset; unsigned length; }; sfnt_decode tabledir_decode[] = { { d_uint32, offsetof(tabledir, tag) }, { d_uint32, offsetof(tabledir, checkSum) }, { d_uint32, offsetof(tabledir, offset) }, { d_uint32, offsetof(tabledir, length) }, { d_end } }; /* OS/2 and Windows compatibility table */ typedef struct t_OS_2_Tag t_OS_2; struct t_OS_2_Tag { unsigned version; int sTypoAscender, sTypoDescender; int sxHeight, sCapHeight; }; sfnt_decode t_OS_2_v0_decode[] = { { d_uint16, offsetof(t_OS_2, version) }, { d_skip(66) }, /* xAvgCharWidth, usWeightClass, usWidthClass, fsType, */ /* ySubscriptXSize, ySubscriptYSize, ySubscriptXOffset, */ /* ySubscriptYOffset, ySuperscriptXSize, ySuperscriptYSize, */ /* ySuperscriptXOffset, ySupercriptYOffset, sFamilyClass, panose, */ /* ulUnicodeRange1, ulUnicodeRange2, ulUnicodeRange3, ulUnicodeRange4, */ /* achVendID, fsSelection, usFirstCharIndex, usLastCharIndex */ { d_end } }; sfnt_decode t_OS_2_v1_decode[] = { { d_uint16, offsetof(t_OS_2, version) }, { d_skip(66) }, /* xAvgCharWidth, usWeightClass, usWidthClass, fsType, */ /* ySubscriptXSize, ySubscriptYSize, ySubscriptXOffset, */ /* ySubscriptYOffset, ySuperscriptXSize, ySuperscriptYSize, */ /* ySuperscriptXOffset, ySupercriptYOffset, sFamilyClass, panose, */ /* ulUnicodeRange1, ulUnicodeRange2, ulUnicodeRange3, ulUnicodeRange4, */ /* achVendID, fsSelection, usFirstCharIndex, usLastCharIndex */ { d_int16, offsetof(t_OS_2, sTypoAscender) }, { d_int16, offsetof(t_OS_2, sTypoDescender) }, { d_skip(14) }, /* sTypoLineGap, usWinAscent, usWinDescent, */ /* ulCodePageRange1, ulCodePageRange2 */ { d_end } }; sfnt_decode t_OS_2_v2_decode[] = { { d_uint16, offsetof(t_OS_2, version) }, { d_skip(66) }, /* xAvgCharWidth, usWeightClass, usWidthClass, fsType, */ /* ySubscriptXSize, ySubscriptYSize, ySubscriptXOffset, */ /* ySubscriptYOffset, ySuperscriptXSize, ySuperscriptYSize, */ /* ySuperscriptXOffset, ySupercriptYOffset, sFamilyClass, panose, */ /* ulUnicodeRange1, ulUnicodeRange2, ulUnicodeRange3, ulUnicodeRange4, */ /* achVendID, fsSelection, usFirstCharIndex, usLastCharIndex */ { d_int16, offsetof(t_OS_2, sTypoAscender) }, { d_int16, offsetof(t_OS_2, sTypoDescender) }, { d_skip(14) }, /* sTypoLineGap, usWinAscent, usWinDescent, */ /* ulCodePageRange1, ulCodePageRange2 */ { d_int16, offsetof(t_OS_2, sxHeight) }, { d_int16, offsetof(t_OS_2, sCapHeight) }, { d_skip(6) }, /* usDefaultChar, usBreakChar, usMaxContext */ { d_end } }; /* Character to Glyph ('cmap') table */ typedef struct t_cmap_Tag t_cmap; struct t_cmap_Tag { unsigned numTables; }; sfnt_decode t_cmap_decode[] = { { d_skip(2) }, { d_uint16, offsetof(t_cmap, numTables) }, { d_end } }; typedef struct encodingrec_Tag encodingrec; struct encodingrec_Tag { unsigned platformID; unsigned encodingID; unsigned offset; }; sfnt_decode encodingrec_decode[] = { { d_uint16, offsetof(encodingrec, platformID) }, { d_uint16, offsetof(encodingrec, encodingID) }, { d_uint32, offsetof(encodingrec, offset) }, { d_end } }; typedef struct cmap4_Tag cmap4; struct cmap4_Tag { unsigned length; unsigned segCountX2; }; sfnt_decode cmap4_decode[] = { { d_skip(2) }, /* format */ { d_uint16, offsetof(cmap4, length) }, { d_skip(2) }, /* language */ { d_uint16, offsetof(cmap4, segCountX2) }, { d_skip(6) }, /* searchRange, entrySelector, rangeShift */ { d_end } }; /* Font Header ('head') table */ typedef struct t_head_Tag t_head; struct t_head_Tag { unsigned version; unsigned fontRevision; unsigned flags; unsigned unitsPerEm; int xMin, yMin, xMax, yMax; int indexToLocFormat; }; sfnt_decode t_head_decode[] = { { d_uint32, offsetof(t_head, version) }, { d_uint32, offsetof(t_head, fontRevision) }, { d_skip(8) }, /* checkSumAdjustment, magicNumber, flags */ { d_uint16, offsetof(t_head, flags) }, { d_uint16, offsetof(t_head, unitsPerEm) }, { d_skip(16) }, /* created, modified */ { d_int16, offsetof(t_head, xMin) }, { d_int16, offsetof(t_head, yMin) }, { d_int16, offsetof(t_head, xMax) }, { d_int16, offsetof(t_head, yMax) }, { d_skip(6) }, /* macStyle, lowestRecPPEM, fontDirectionHint */ { d_int16, offsetof(t_head, indexToLocFormat) }, { d_skip(2) }, { d_end } }; /* Horizontal Header ('hhea') table */ typedef struct t_hhea_Tag t_hhea; struct t_hhea_Tag { unsigned version; int ascent; int descent; int lineGap; int metricDataFormat; unsigned numOfLongHorMetrics; }; sfnt_decode t_hhea_decode[] = { { d_uint32, offsetof(t_hhea, version) }, { d_int16, offsetof(t_hhea, ascent) }, { d_int16, offsetof(t_hhea, descent) }, { d_int16, offsetof(t_hhea, lineGap) }, { d_skip(22) }, { d_int16, offsetof(t_hhea, metricDataFormat) }, { d_uint16, offsetof(t_hhea, numOfLongHorMetrics) }, { d_end } }; /* Horizontal Metrics ('hmtx') table */ sfnt_decode longhormetric_decode[] = { { d_uint16, 0 }, { d_skip(2) }, { d_end } }; /* Kerning ('kern') table */ typedef struct t_kern_Tag t_kern; struct t_kern_Tag { unsigned version; unsigned nTables; }; sfnt_decode t_kern_v0_decode[] = { { d_uint16, offsetof(t_kern, version) }, { d_uint16, offsetof(t_kern, nTables) }, { d_end } }; typedef struct kern_v0_subhdr_Tag kern_v0_subhdr; struct kern_v0_subhdr_Tag { unsigned version; unsigned length; unsigned coverage; }; sfnt_decode kern_v0_subhdr_decode[] = { { d_uint16, offsetof(kern_v0_subhdr, version) }, { d_uint16, offsetof(kern_v0_subhdr, length) }, { d_uint16, offsetof(kern_v0_subhdr, coverage) }, { d_end } }; #define KERN_V0_HORIZ 0x0001 #define KERN_V0_MINIMUM 0x0002 #define KERN_V0_CROSSSTREAM 0x0004 #define KERN_V0_OVERRIDE 0x0008 #define KERN_V0_FORMAT 0xff00 #define KERN_V0_FORMAT_0 0x0000 sfnt_decode t_kern_v1_decode[] = { { d_uint32, offsetof(t_kern, version) }, { d_uint32, offsetof(t_kern, nTables) }, { d_end } }; typedef struct kern_v1_subhdr_Tag kern_v1_subhdr; struct kern_v1_subhdr_Tag { unsigned length; unsigned coverage; }; sfnt_decode kern_v1_subhdr_decode[] = { { d_uint32, offsetof(kern_v1_subhdr, length) }, { d_uint16, offsetof(kern_v1_subhdr, coverage) }, { d_skip(2) }, /* tupleIndex */ { d_end } }; #define KERN_V1_VERTICAL 0x8000 #define KERN_V1_CROSSSTREAM 0x4000 #define KERN_V1_VARIATION 0x2000 #define KERN_V1_FORMAT 0x00ff #define KERN_V1_FORMAT_0 0x0000 typedef struct kern_f0_Tag kern_f0; struct kern_f0_Tag { unsigned nPairs; }; sfnt_decode kern_f0_decode[] = { { d_uint16, offsetof(kern_f0, nPairs) }, { d_skip(6) }, /* searchRange, entrySelector, rangeShift */ { d_end } }; typedef struct kern_f0_pair_Tag kern_f0_pair; struct kern_f0_pair_Tag { unsigned left; unsigned right; int value; }; sfnt_decode kern_f0_pair_decode[] = { { d_uint16, offsetof(kern_f0_pair, left) }, { d_uint16, offsetof(kern_f0_pair, right) }, { d_int16, offsetof(kern_f0_pair, value) }, { d_end } }; /* Maximum profile ('maxp') table */ typedef struct t_maxp_Tag t_maxp; struct t_maxp_Tag { unsigned version; unsigned numGlyphs; }; sfnt_decode t_maxp_decode[] = { { d_uint32, offsetof(t_maxp, version) }, { d_uint16, offsetof(t_maxp, numGlyphs) }, { d_end } }; /* Naming ('name') table */ typedef struct t_name_Tag t_name; typedef struct namerecord_Tag namerecord; struct t_name_Tag { unsigned format; unsigned count; unsigned stringOffset; namerecord *nameRecord; }; sfnt_decode t_name_decode[] = { { d_uint16, offsetof(t_name, format) }, { d_uint16, offsetof(t_name, count) }, { d_uint16, offsetof(t_name, stringOffset) }, { d_end } }; struct namerecord_Tag { unsigned platformID; unsigned encodingID; unsigned languageID; unsigned nameID; unsigned length; unsigned offset; }; sfnt_decode namerecord_decode[] = { { d_uint16, offsetof(namerecord, platformID) }, { d_uint16, offsetof(namerecord, encodingID) }, { d_uint16, offsetof(namerecord, languageID) }, { d_uint16, offsetof(namerecord, nameID) }, { d_uint16, offsetof(namerecord, length) }, { d_uint16, offsetof(namerecord, offset) }, { d_end } }; /* PostScript compatibility ('post') table */ typedef struct t_post_Tag t_post; struct t_post_Tag { unsigned format; int italicAngle; int underlinePosition; int underlineThickness; unsigned isFixedPitch; unsigned minMemType42; unsigned maxMemType42; }; sfnt_decode t_post_decode[] = { { d_uint32, offsetof(t_post, format) }, { d_int32, offsetof(t_post, italicAngle) }, { d_int16, offsetof(t_post, underlinePosition) }, { d_int16, offsetof(t_post, underlineThickness) }, { d_uint32, offsetof(t_post, isFixedPitch) }, { d_uint32, offsetof(t_post, minMemType42) }, { d_uint32, offsetof(t_post, maxMemType42) }, { d_skip(8) }, /* minMemType1, maxMemType1 */ { d_end } }; typedef struct { glyph name; unsigned short index; } glyphmap; struct sfnt_Tag { void *data; size_t len; void *end; filepos pos; offsubdir osd; tabledir *td; t_head head; unsigned nglyphs; glyph *glyphsbyindex; unsigned short *glyphsbyname; unsigned minmem, maxmem; }; static int sfnt_findtable(sfnt *sf, unsigned tag, void **startp, void **endp) { size_t i; for (i = 0; i < sf->osd.numTables; i++) { if (sf->td[i].tag == tag) { *startp = (char *)sf->data + sf->td[i].offset; *endp = (char *)*startp + sf->td[i].length; return TRUE; } } return FALSE; } static char *sfnt_psname(font_info *fi) { sfnt *sf = fi->fontfile; t_name name; void *ptr, *end; size_t i; char *psname; namerecord *nr; if (!sfnt_findtable(sf, TAG_name, &ptr, &end)) { err_sfntnotable(&sf->pos, "name"); return NULL; } ptr = decode(t_name_decode, ptr, end, &name); name.nameRecord = snewn(name.count, namerecord); ptr = decoden(namerecord_decode, ptr, sf->end, name.nameRecord, sizeof(*name.nameRecord), name.count); for (i = 0; i < name.count; i++) { nr = name.nameRecord + i; if (nr->nameID == 6) { /* PostScript name, but can we make sense of it? */ if (nr->platformID == 1 && nr->encodingID == 0) { /* Mac Roman, which is ASCII for our purposes */ psname = snewn(nr->length + 1, char); memcpy(psname, (char *)ptr + nr->offset, nr->length); psname[nr->length] = 0; sfree(name.nameRecord); return psname; } } } err_sfntnopsname(&sf->pos); return NULL; } static unsigned short *cmp_glyphsbyindex; static int glyphsbyname_cmp(void const *a, void const *b) { glyph ga = cmp_glyphsbyindex[*(unsigned short *)a]; glyph gb = cmp_glyphsbyindex[*(unsigned short *)b]; if (ga < gb) return -1; if (ga > gb) return 1; /* For de-duping, we'd prefer to have the first glyph stay first */ if (*(unsigned short *)a < *(unsigned short *)b) return -1; if (*(unsigned short *)a > *(unsigned short *)b) return 1; return 0; } static int glyphsbyname_cmp_search(void const *a, void const *b) { glyph ga = *(glyph *)a; glyph gb = cmp_glyphsbyindex[*(unsigned short *)b]; if (ga < gb) return -1; if (ga > gb) return 1; return 0; } /* Generate an name for a glyph that doesn't have one. */ static glyph genglyph(unsigned idx) { char buf[11]; if (idx == 0) return glyph_intern(".notdef"); sprintf(buf, "glyph%u", idx); return glyph_intern(buf); } /* * Extract data from the 'post' table (mostly glyph mappings) * * TODO: cope better with duplicated glyph names (usually .notdef) * TODO: when presented with format 3.0, try to use 'CFF' if present. */ static void sfnt_mapglyphs(font_info *fi) { sfnt *sf = fi->fontfile; t_post post; void *ptr, *end; unsigned char *sptr; char tmp[256]; glyph *extraglyphs, prev, this; unsigned nextras, i, g, suflen; sf->glyphsbyname = sf->glyphsbyindex = NULL; if (sfnt_findtable(sf, TAG_post, &ptr, &end)) { ptr = decode(t_post_decode, ptr, end, &post); if (ptr == NULL) { err_sfntbadtable(&sf->pos, "post"); goto noglyphs; } sf->minmem = post.minMemType42; sf->maxmem = post.maxMemType42; fi->italicangle = post.italicAngle / 65536.0; switch (post.format) { case 0x00010000: if (sf->nglyphs != 258) { err_sfntbadtable(&sf->pos, "post"); break; } sf->glyphsbyindex = (glyph *)tt_std_glyphs; break; case 0x00020000: if ((char *)ptr + 2 > (char *)end) { err_sfntbadtable(&sf->pos, "post"); break; } ptr = (char *)ptr + 2; if ((char *)ptr + 2*sf->nglyphs > (char *)end) { err_sfntbadtable(&sf->pos, "post"); break; } nextras = 0; for (sptr = (unsigned char *)ptr + 2*sf->nglyphs; sptr < (unsigned char *)end; sptr += *sptr+1) nextras++; extraglyphs = snewn(nextras, glyph); i = 0; for (sptr = (unsigned char *)ptr + 2*sf->nglyphs; sptr < (unsigned char *)end; sptr += *sptr+1) { memcpy(tmp, sptr + 1, *sptr); tmp[*sptr] = 0; assert(i < nextras); extraglyphs[i++] = glyph_intern(tmp); } sf->glyphsbyindex = snewn(sf->nglyphs, glyph); for (i = 0; i < sf->nglyphs; i++) { decode_uint16((char *)ptr + 2*i, &g); if (g <= 257) sf->glyphsbyindex[i] = tt_std_glyphs[g]; else if (g < 258 + nextras) sf->glyphsbyindex[i] = extraglyphs[g - 258]; else { err_sfntbadtable(&sf->pos, "post"); sf->glyphsbyindex[i] = genglyph(i); } } sfree(extraglyphs); break; case 0x00030000: break; default: err_sfnttablevers(&sf->pos, "post"); break; } } noglyphs: if (!sf->glyphsbyindex) { sf->glyphsbyindex = snewn(sf->nglyphs, glyph); for (i = 0; i < sf->nglyphs; i++) sf->glyphsbyindex[i] = genglyph(i); } /* Construct glyphsbyname */ sf->glyphsbyname = snewn(sf->nglyphs, unsigned short); for (i = 0; i < sf->nglyphs; i++) sf->glyphsbyname[i] = i; cmp_glyphsbyindex = sf->glyphsbyindex; qsort(sf->glyphsbyname, sf->nglyphs, sizeof(*sf->glyphsbyname), glyphsbyname_cmp); /* * It's possible for fonts to specify the same name for multiple * glyphs, which would make one of them inaccessible. Check for * that, and rename all but one of each set. * * To ensure that we don't clash with any existing glyph names, * our renaming involves appending the glyph number formatted with * enough leading zeroes to make it longer than any all-digit * suffix that already exists in the font. */ suflen = 4; for (i = 0; i < sf->nglyphs; i++) { char const *p; p = strrchr(glyph_extern(sfnt_indextoglyph(sf, i)), '.'); if (p && !(p+1)[strspn(p+1, "0123456789")] && strlen(p+1) > suflen) suflen = strlen(p+1); } suflen++; prev = sfnt_indextoglyph(sf, sf->glyphsbyname[0]); for (i = 1; i < sf->nglyphs; i++) { if (prev == (this = sfnt_indextoglyph(sf, sf->glyphsbyname[i]))) { char const *basename; char *buf; basename = glyph_extern(this); buf = snewn(strlen(basename) + 2 + suflen, char); strcpy(buf, basename); sprintf(buf + strlen(basename), ".%0*hu", suflen, sf->glyphsbyname[i]); sf->glyphsbyindex[sf->glyphsbyname[i]] = glyph_intern(buf); sfree(buf); } prev = this; } /* We may have renamed some glyphs, so re-sort the array. */ qsort(sf->glyphsbyname, sf->nglyphs, sizeof(*sf->glyphsbyname), glyphsbyname_cmp); } glyph sfnt_indextoglyph(sfnt *sf, unsigned idx) { return sf->glyphsbyindex[idx]; } unsigned sfnt_nglyphs(sfnt *sf) { return sf->nglyphs; } unsigned sfnt_glyphtoindex(sfnt *sf, glyph g) { cmp_glyphsbyindex = sf->glyphsbyindex; return *(unsigned short *)bsearch(&g, sf->glyphsbyname, sf->nglyphs, sizeof(*sf->glyphsbyname), glyphsbyname_cmp_search); } /* * Get data from 'hhea', 'hmtx', and 'OS/2' tables */ void sfnt_getmetrics(font_info *fi) { sfnt *sf = fi->fontfile; t_hhea hhea; t_OS_2 OS_2; void *ptr, *end; unsigned i, j; unsigned *hmtx; /* First, the bounding box from the 'head' table. */ fi->fontbbox[0] = sf->head.xMin * FUNITS_PER_PT / sf->head.unitsPerEm; fi->fontbbox[1] = sf->head.yMin * FUNITS_PER_PT / sf->head.unitsPerEm; fi->fontbbox[2] = sf->head.xMax * FUNITS_PER_PT / sf->head.unitsPerEm; fi->fontbbox[3] = sf->head.yMax * FUNITS_PER_PT / sf->head.unitsPerEm; if (!sfnt_findtable(sf, TAG_hhea, &ptr, &end)) { err_sfntnotable(&sf->pos, "hhea"); return; } if (decode(t_hhea_decode, ptr, end, &hhea) == NULL) { err_sfntbadtable(&sf->pos, "hhea"); return; } if ((hhea.version & 0xffff0000) != 0x00010000) { err_sfnttablevers(&sf->pos, "hhea"); return; } fi->ascent = hhea.ascent; fi->descent = hhea.descent; if (hhea.metricDataFormat != 0) { err_sfnttablevers(&sf->pos, "hmtx"); return; } if (!sfnt_findtable(sf, TAG_hmtx, &ptr, &end)) { err_sfntnotable(&sf->pos, "hmtx"); return; } hmtx = snewn(hhea.numOfLongHorMetrics, unsigned); if (decoden(longhormetric_decode, ptr, end, hmtx, sizeof(*hmtx), hhea.numOfLongHorMetrics) == NULL) { err_sfntbadtable(&sf->pos, "hmtx"); return; } for (i = 0; i < sf->nglyphs; i++) { glyph_width *w = snew(glyph_width); w->glyph = sfnt_indextoglyph(sf, i); j = i < hhea.numOfLongHorMetrics ? i : hhea.numOfLongHorMetrics - 1; w->width = hmtx[j] * UNITS_PER_PT / sf->head.unitsPerEm; add234(fi->widths, w); } /* Now see if the 'OS/2' table has any useful metrics */ if (!sfnt_findtable(sf, TAG_OS_2, &ptr, &end)) return; if (decode(uint16_decode, ptr, end, &OS_2.version) == NULL) goto bados2; if (OS_2.version >= 2) { if (decode(t_OS_2_v2_decode, ptr, end, &OS_2) == NULL) goto bados2; fi->xheight = OS_2.sxHeight * FUNITS_PER_PT / sf->head.unitsPerEm; fi->capheight = OS_2.sCapHeight * FUNITS_PER_PT / sf->head.unitsPerEm; } else if (OS_2.version == 1) { if (decode(t_OS_2_v1_decode, ptr, end, &OS_2) == NULL) goto bados2; } else return; fi->ascent = OS_2.sTypoAscender * FUNITS_PER_PT / sf->head.unitsPerEm; fi->descent = OS_2.sTypoDescender * FUNITS_PER_PT / sf->head.unitsPerEm; return; bados2: err_sfntbadtable(&sf->pos, "OS/2"); } /* * Get kerning data from a 'kern' table * * 'kern' tables have two gratuitously different header formats, one * used by Apple and one by Microsoft. Happily, the kerning tables * themselves use the same formats. Halibut only supports simple kern * pairs for horizontal kerning of horizontal text, and ignores * everything else. */ static void sfnt_getkern(font_info *fi) { sfnt *sf = fi->fontfile; t_kern kern; unsigned version, i, j; void *ptr, *end; if (!sfnt_findtable(sf, TAG_kern, &ptr, &end)) return; if (!decode(uint16_decode, ptr, end, &version)) goto bad; if (version == 0) ptr = decode(t_kern_v0_decode, ptr, end, &kern); else if (version == 1) ptr = decode(t_kern_v1_decode, ptr, end, &kern); else return; if (ptr == NULL) goto bad; for (i = 0; i < kern.nTables; i++) { kern_f0 f0; kern_pair *kerns; if (version == 0) { kern_v0_subhdr sub; ptr = decode(kern_v0_subhdr_decode, ptr, end, &sub); if (ptr == NULL) goto bad; if (sub.version != 0 || (sub.coverage & (KERN_V0_HORIZ | KERN_V0_MINIMUM | KERN_V0_CROSSSTREAM | KERN_V0_FORMAT)) != (KERN_V0_HORIZ | KERN_V0_FORMAT_0)) { ptr = (char *)ptr + sub.length - 6; continue; } } else { kern_v1_subhdr sub; ptr = decode(kern_v1_subhdr_decode, ptr, end, &sub); if (ptr == NULL) goto bad; if ((sub.coverage & (KERN_V1_VERTICAL | KERN_V1_CROSSSTREAM | KERN_V1_VARIATION | KERN_V1_FORMAT)) != KERN_V0_FORMAT_0) { ptr = (char *)ptr + sub.length - 8; continue; } } ptr = decode(kern_f0_decode, ptr, end, &f0); if (ptr == NULL) goto bad; kerns = snewn(f0.nPairs, kern_pair); for (j = 0; j < f0.nPairs; j++) { kern_f0_pair p; kern_pair *kp = kerns + j; ptr = decode(kern_f0_pair_decode, ptr, end, &p); if (ptr == NULL) goto bad; if (p.left >= sf->nglyphs || p.right >= sf->nglyphs) goto bad; kp->left = sfnt_indextoglyph(sf, p.left); kp->right = sfnt_indextoglyph(sf, p.right); kp->kern = p.value * UNITS_PER_PT / (int)sf->head.unitsPerEm; add234(fi->kerns, kp); } } return; bad: err_sfntbadtable(&sf->pos, "kern"); return; } /* * Get mapping data from 'cmap' table * * We look for either a (0, 0), (0, 2), (0, 3), or (3, 1) table, all * of these being versions of UCS-2. We ignore (0, 1), since it's * Unicode 1.1 with precomposed Hangul syllables. We only handle * format 4 of this table, since that seems to be the only one in use. */ void sfnt_getmap(font_info *fi) { sfnt *sf = fi->fontfile; t_cmap cmap; encodingrec *esd; void *base, *ptr, *end; unsigned i; unsigned format; for (i = 0; i < lenof(fi->bmp); i++) fi->bmp[i] = 0xFFFF; if (!sfnt_findtable(sf, TAG_cmap, &ptr, &end)) { err_sfntnotable(&sf->pos, "cmap"); } base = ptr; ptr = decode(t_cmap_decode, ptr, end, &cmap); if (ptr == NULL) goto bad; esd = snewn(cmap.numTables, encodingrec); ptr = decoden(encodingrec_decode, ptr, end, esd, sizeof(*esd), cmap.numTables); if (ptr == NULL) goto bad; for (i = 0; i < cmap.numTables; i++) { if (!decode(uint16_decode, (char *)base + esd[i].offset, end, &format)) goto bad; if ((esd[i].platformID == 0 && esd[i].encodingID == 0) || (esd[i].platformID == 0 && esd[i].encodingID == 2) || (esd[i].platformID == 0 && esd[i].encodingID == 3) || (esd[i].platformID == 3 && esd[i].encodingID == 1)) { /* UCS-2 encoding */ if (!decode(uint16_decode, (char *)base + esd[i].offset, end, &format)) goto bad; if (format == 4) { unsigned *data, *endCode, *startCode, *idDelta, *idRangeOffset; unsigned *glyphIndexArray; unsigned segcount, nword, nglyphindex, j; cmap4 cmap4; ptr = decode(cmap4_decode, (char *)base + esd[i].offset, end, &cmap4); if (!ptr) goto bad; segcount = cmap4.segCountX2 / 2; nword = cmap4.length / 2 - 7; data = snewn(nword, unsigned); if (!decoden(uint16_decode, ptr, (char *)ptr + nword * 2, data, sizeof(*data), nword)) goto bad; endCode = data; startCode = data + segcount + 1; idDelta = startCode + segcount; idRangeOffset = idDelta + segcount; glyphIndexArray = idRangeOffset + segcount; nglyphindex = nword - segcount * 4 - 1; for (j = 0; j < segcount; j++) { unsigned k, idx; if (idRangeOffset[j] == 0) { for (k = startCode[j]; k <= endCode[j]; k++) { idx = (k + idDelta[j]) & 0xffff; if (idx != 0) { if (idx > sf->nglyphs) { err_sfntbadglyph(&sf->pos, k); continue; } fi->bmp[k] = sfnt_indextoglyph(sf, idx); } } } else { unsigned startidx = idRangeOffset[j]/2 - segcount + j; for (k = startCode[j]; k <= endCode[j]; k++) { if (startidx + k - startCode[j] >= nglyphindex) { err_sfntbadglyph(&sf->pos, k); continue; } idx = glyphIndexArray[startidx + k - startCode[j]]; if (idx != 0) { idx = (idx + idDelta[j]) & 0xffff; if (idx > sf->nglyphs) { err_sfntbadglyph(&sf->pos, k); continue; } fi->bmp[k] = sfnt_indextoglyph(sf, idx); } } } } sfree(data); return; } } } err_sfntnounicmap(&sf->pos); return; bad: err_sfntbadtable(&sf->pos, "cmap"); } void read_sfnt_file(input *in) { sfnt *sf = snew(sfnt); size_t off = 0, got; FILE *fp = in->currfp; font_info *fi = snew(font_info); void *ptr, *end; t_maxp maxp; fi->name = NULL; fi->widths = newtree234(width_cmp); fi->kerns = newtree234(kern_cmp); fi->ligs = newtree234(lig_cmp); fi->fontbbox[0] = fi->fontbbox[1] = fi->fontbbox[2] = fi->fontbbox[3] = 0; fi->capheight = fi->xheight = fi->ascent = fi->descent = 0; fi->stemh = fi->stemv = fi->italicangle = 0; fi->fontfile = sf; fi->filetype = TRUETYPE; sf->len = 32768; sf->data = snewn(sf->len, unsigned char); for (;;) { got = fread((char *)sf->data + off, 1, sf->len - off, fp); off += got; if (off != sf->len) break; sf->len *= 2; sf->data = sresize(sf->data, sf->len, unsigned char); } fclose(in->currfp); sf->len = off; sf->data = sresize(sf->data, sf->len, unsigned char); sf->end = (char *)sf->data + sf->len; sf->pos = in->pos; sf->pos.line = 0; sf->nglyphs = 0; ptr = decode(offsubdir_decode, sf->data, sf->end, &sf->osd); if (ptr == NULL) { err_sfntbadhdr(&sf->pos); return; } sf->td = snewn(sf->osd.numTables, tabledir); ptr = decoden(tabledir_decode, ptr, sf->end, sf->td, sizeof(*sf->td), sf->osd.numTables); if (ptr == NULL) { err_sfntbadhdr(&sf->pos); return; } if (!sfnt_findtable(sf, TAG_head, &ptr, &end)) { err_sfntnotable(&sf->pos, "head"); return; } if (decode(t_head_decode, ptr, end, &sf->head) == NULL) { err_sfntbadtable(&sf->pos, "head"); return; } if ((sf->head.version & 0xffff0000) != 0x00010000) { err_sfnttablevers(&sf->pos, "head"); return; } if (!sfnt_findtable(sf, TAG_maxp, &ptr, &end)) { err_sfntnotable(&sf->pos, "maxp"); return; } if (decode(t_maxp_decode, ptr, end, &maxp) == NULL) { err_sfntbadtable(&sf->pos, "maxp"); return; } if (maxp.version < 0x00005000 || maxp.version > 0x0001ffff) { err_sfnttablevers(&sf->pos, "maxp"); return; } sf->nglyphs = maxp.numGlyphs; fi->name = sfnt_psname(fi); if (fi->name == NULL) return; sfnt_mapglyphs(fi); sfnt_getmetrics(fi); sfnt_getkern(fi); sfnt_getmap(fi); fi->next = all_fonts; all_fonts = fi; } static int sizecmp(const void *a, const void *b) { if (*(size_t *)a < *(size_t *)b) return -1; if (*(size_t *)a > *(size_t *)b) return 1; return 0; } /* * The format for embedding TrueType fonts in Postscript is defined in * Adobe Technical Note #5012: The Type 42 Font Format Specification. * */ void sfnt_writeps(font_info const *fi, FILE *ofp) { unsigned i, j, lastbreak; sfnt *sf = fi->fontfile; size_t *breaks, glyfoff, glyflen; void *glyfptr, *glyfend, *locaptr, *locaend; unsigned *loca; int cc = 0; /* XXX Unclear that this is the correct format. */ fprintf(ofp, "%%!PS-TrueTypeFont-%u-%u\n", sf->osd.scaler_type, sf->head.fontRevision); if (sf->minmem) fprintf(ofp, "%%%%VMUsage: %u %u\n", sf->minmem, sf->maxmem); fprintf(ofp, "9 dict dup begin\n"); fprintf(ofp, "/FontType 42 def\n"); fprintf(ofp, "/FontMatrix [1 0 0 1 0 0] def\n"); fprintf(ofp, "/FontName /%s def\n", fi->name); fprintf(ofp, "/Encoding StandardEncoding def\n"); if ((sf->head.flags & 0x0003) == 0x0003) { /* * Sensible font with the origin in the right place, such that * the bounding box is meaningful. */ fprintf(ofp, "/FontBBox [%g %g %g %g] readonly def\n", (double)sf->head.xMin / sf->head.unitsPerEm, (double)sf->head.yMin / sf->head.unitsPerEm, (double)sf->head.xMax / sf->head.unitsPerEm, (double)sf->head.yMax / sf->head.unitsPerEm); } else { /* Non-sensible font. */ fprintf(ofp, "/FontBBox [0 0 0 0] readonly def\n"); } fprintf(ofp, "/PaintType 0 def\n"); fprintf(ofp, "/CharStrings %u dict dup begin\n", sf->nglyphs); fprintf(ofp, "0 1 %u{currentfile token pop exch def}bind for\n", sf->nglyphs - 1); for (i = 0; i < sf->nglyphs; i++) ps_token(ofp, &cc, "/%s", glyph_extern(sfnt_indextoglyph(sf, i))); fprintf(ofp, "\nend readonly def\n"); fprintf(ofp, "/sfnts [<"); breaks = snewn(sf->osd.numTables + sf->nglyphs, size_t); for (i = 0; i < sf->osd.numTables; i++) { breaks[i] = sf->td[i].offset; } if (!sfnt_findtable(sf, TAG_glyf, &glyfptr, &glyfend)) { err_sfntnotable(&sf->pos, "glyf"); return; } glyfoff = (char *)glyfptr - (char *)sf->data; glyflen = (char *)glyfend - (char *)glyfptr; if (!sfnt_findtable(sf, TAG_loca, &locaptr, &locaend)) { err_sfntnotable(&sf->pos, "loca"); return; } loca = snewn(sf->nglyphs, unsigned); if (sf->head.indexToLocFormat == 0) { if (!decoden(uint16_decode, locaptr, locaend, loca, sizeof(*loca), sf->nglyphs)) goto badloca; for (i = 0; i < sf->nglyphs; i++) loca[i] *= 2; } else { if (!decoden(uint32_decode, locaptr, locaend, loca, sizeof(*loca), sf->nglyphs)) goto badloca; } for (i = 1; i < sf->nglyphs; i++) { if (loca[i] > glyflen) goto badloca; breaks[sf->osd.numTables + i - 1] = loca[i] + glyfoff; } breaks[sf->osd.numTables + sf->nglyphs - 1] = sf->len; qsort(breaks, sf->osd.numTables + sf->nglyphs, sizeof(*breaks), sizecmp); j = lastbreak = 0; for (i = 0; i < sf->len; i++) { if ((i - lastbreak) % 38 == 0) fprintf(ofp, "\n"); if (i == breaks[j]) { while (i == breaks[j]) j++; lastbreak = i; fprintf(ofp, "00><\n"); } fprintf(ofp, "%02x", *((unsigned char *)sf->data + i)); } fprintf(ofp, "00>] readonly def\n"); sfree(breaks); fprintf(ofp, "end /%s exch definefont\n", fi->name); return; badloca: err_sfntbadtable(&sf->pos, "loca"); } void sfnt_data(font_info *fi, char **bufp, size_t *lenp) { sfnt *sf = fi->fontfile; *bufp = sf->data; *lenp = sf->len; }