X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/halibut/blobdiff_plain/2729eafc11bc11588764711335273a15d8cc226c..0041ffdbc8d0dc2db4a7c9dadccfc1a8f518d1d3:/in_sfnt.c diff --git a/in_sfnt.c b/in_sfnt.c index 43580e6..0d2c7d5 100644 --- a/in_sfnt.c +++ b/in_sfnt.c @@ -12,6 +12,9 @@ * * The TrueType Reference Manual: * + * + * Microsoft typography specifications: + * */ #include @@ -28,15 +31,19 @@ struct sfnt_decode_Tag { 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; @@ -66,11 +73,15 @@ static void decode_int32(void *src, void *dest) { #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 @@ -113,12 +124,15 @@ sfnt_decode offsubdir_decode[] = { }; #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 @@ -139,6 +153,55 @@ sfnt_decode tabledir_decode[] = { { 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 { @@ -230,6 +293,90 @@ sfnt_decode longhormetric_decode[] = { { 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; @@ -267,7 +414,7 @@ sfnt_decode namerecord_decode[] = { typedef struct t_post_Tag t_post; struct t_post_Tag { unsigned format; - unsigned italicAngle; + int italicAngle; int underlinePosition; int underlineThickness; unsigned isFixedPitch; @@ -276,7 +423,7 @@ struct t_post_Tag { }; sfnt_decode t_post_decode[] = { { d_uint32, offsetof(t_post, format) }, - { d_uint32, offsetof(t_post, italicAngle) }, + { d_int32, offsetof(t_post, italicAngle) }, { d_int16, offsetof(t_post, underlinePosition) }, { d_int16, offsetof(t_post, underlineThickness) }, { d_uint32, offsetof(t_post, isFixedPitch) }, @@ -291,11 +438,11 @@ typedef struct { unsigned short index; } glyphmap; -typedef struct sfnt_Tag sfnt; struct sfnt_Tag { void *data; size_t len; void *end; + filepos pos; offsubdir osd; tabledir *td; t_head head; @@ -327,8 +474,10 @@ static char *sfnt_psname(font_info *fi) { char *psname; namerecord *nr; - if (!sfnt_findtable(sf, TAG_name, &ptr, &end)) - abort(); + if (!sfnt_findtable(sf, TAG_name, &ptr, &end)) { + error(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, @@ -347,108 +496,334 @@ static char *sfnt_psname(font_info *fi) { } } } + error(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(sfnt *sf) { +static void sfnt_mapglyphs(font_info *fi) { + sfnt *sf = fi->fontfile; t_post post; void *ptr, *end; - unsigned char *sptr, tmp[256]; - glyph *extraglyphs; - unsigned nextras, i, g; + 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)) - abort(); - ptr = decode(t_post_decode, ptr, end, &post); - sf->minmem = post.minMemType42; - sf->maxmem = post.maxMemType42; - if (ptr == NULL) abort(); - switch (post.format) { - case 0x00010000: - sf->nglyphs = 258; - sf->glyphsbyindex = (glyph *)tt_std_glyphs; - break; - case 0x00020000: - if ((char *)ptr + 2 > end) return; - decode_uint16(ptr, &sf->nglyphs); - ptr = (char *)ptr + 2; - if ((char *)ptr + 2*sf->nglyphs > end) return; - nextras = 0; - for (sptr = (char *)ptr + 2*sf->nglyphs; sptr < end; sptr += *sptr+1) - nextras++; - extraglyphs = snewn(nextras, glyph); - i = 0; - for (sptr = (char *)ptr + 2*sf->nglyphs; sptr < end; sptr += *sptr+1) { - memcpy(tmp, sptr + 1, *sptr); - tmp[*sptr] = 0; - assert(i < nextras); - extraglyphs[i++] = glyph_intern(tmp); + if (sfnt_findtable(sf, TAG_post, &ptr, &end)) { + ptr = decode(t_post_decode, ptr, end, &post); + if (ptr == NULL) { + error(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) { + error(err_sfntbadtable, &sf->pos, "post"); + break; + } + sf->glyphsbyindex = (glyph *)tt_std_glyphs; + break; + case 0x00020000: + if ((char *)ptr + 2 > (char *)end) { + error(err_sfntbadtable, &sf->pos, "post"); + break; + } + ptr = (char *)ptr + 2; + if ((char *)ptr + 2*sf->nglyphs > (char *)end) { + error(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 { + error(err_sfntbadtable, &sf->pos, "post"); + sf->glyphsbyindex[i] = genglyph(i); + } + } + sfree(extraglyphs); + break; + case 0x00030000: + break; + default: + error(err_sfnttablevers, &sf->pos, "post"); + break; + } + } + noglyphs: + if (!sf->glyphsbyindex) { 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 - sf->glyphsbyindex[i] = NOGLYPH; + 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); } - sfree(extraglyphs); - break; - default: - abort(); + 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' and 'hmtx' tables + * 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; - if (!sfnt_findtable(sf, TAG_hhea, &ptr, &end)) - abort(); - if (decode(t_hhea_decode, ptr, end, &hhea) == NULL) - abort(); - if ((hhea.version & 0xffff0000) != 0x00010000) - abort(); + /* 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)) { + error(err_sfntnotable, &sf->pos, "hhea"); + return; + } + if (decode(t_hhea_decode, ptr, end, &hhea) == NULL) { + error(err_sfntbadtable, &sf->pos, "hhea"); + return; + } + if ((hhea.version & 0xffff0000) != 0x00010000) { + error(err_sfnttablevers, &sf->pos, "hhea"); + return; + } fi->ascent = hhea.ascent; fi->descent = hhea.descent; - if (hhea.metricDataFormat != 0) - abort(); - if (!sfnt_findtable(sf, TAG_hmtx, &ptr, &end)) - abort(); + if (hhea.metricDataFormat != 0) { + error(err_sfnttablevers, &sf->pos, "hmtx"); + return; + } + if (!sfnt_findtable(sf, TAG_hmtx, &ptr, &end)) { + error(err_sfntnotable, &sf->pos, "hmtx"); + return; + } hmtx = snewn(hhea.numOfLongHorMetrics, unsigned); if (decoden(longhormetric_decode, ptr, end, hmtx, sizeof(*hmtx), - hhea.numOfLongHorMetrics) == NULL) - abort(); + hhea.numOfLongHorMetrics) == NULL) { + error(err_sfntbadtable, &sf->pos, "hmtx"); + return; + } for (i = 0; i < sf->nglyphs; i++) { glyph_width *w = snew(glyph_width); - w->glyph = sf->glyphsbyindex[i]; + 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: + error(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: + error(err_sfntbadtable, &sf->pos, "kern"); + return; } /* * Get mapping data from 'cmap' table * - * We look for either a (0, 0), (0, 1), (0, 2), (0, 3), or (3, 1) table, - * all of these being versions of UCS-2. We only handle format 4 of this - * table, since that seems to be the only one in use. + * 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; @@ -461,24 +836,27 @@ void sfnt_getmap(font_info *fi) { for (i = 0; i < lenof(fi->bmp); i++) fi->bmp[i] = 0xFFFF; - if (!sfnt_findtable(sf, TAG_cmap, &ptr, &end)) - abort(); + if (!sfnt_findtable(sf, TAG_cmap, &ptr, &end)) { + error(err_sfntnotable, &sf->pos, "cmap"); + } base = ptr; ptr = decode(t_cmap_decode, ptr, end, &cmap); - if (ptr == NULL) abort(); + if (ptr == NULL) goto bad; esd = snewn(cmap.numTables, encodingrec); ptr = decoden(encodingrec_decode, ptr, end, esd, sizeof(*esd), cmap.numTables); - if (ptr == NULL) abort(); + if (ptr == NULL) goto bad; for (i = 0; i < cmap.numTables; i++) { if (!decode(uint16_decode, (char *)base + esd[i].offset, end, &format)) - abort(); - if ((esd[i].platformID == 0 && esd[i].encodingID == 3) || + 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)) - abort(); + goto bad; if (format == 4) { unsigned *data, *endCode, *startCode, *idDelta, *idRangeOffset; unsigned *glyphIndexArray; @@ -487,12 +865,12 @@ void sfnt_getmap(font_info *fi) { ptr = decode(cmap4_decode, (char *)base + esd[i].offset, end, &cmap4); - if (!ptr) abort(); + 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)) abort(); + data, sizeof(*data), nword)) goto bad; endCode = data; startCode = data + segcount + 1; idDelta = startCode + segcount; @@ -507,27 +885,42 @@ void sfnt_getmap(font_info *fi) { for (k = startCode[j]; k <= endCode[j]; k++) { idx = (k + idDelta[j]) & 0xffff; if (idx != 0) { - if (idx > sf->nglyphs) abort(); - fi->bmp[k] = sf->glyphsbyindex[idx]; + if (idx > sf->nglyphs) { + error(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) { + error(err_sfntbadglyph, &sf->pos, k); + continue; + } idx = glyphIndexArray[startidx + k - startCode[j]]; if (idx != 0) { idx = (idx + idDelta[j]) & 0xffff; - if (idx > sf->nglyphs) abort(); - fi->bmp[k] = sf->glyphsbyindex[idx]; + if (idx > sf->nglyphs) { + error(err_sfntbadglyph, &sf->pos, k); + continue; + } + fi->bmp[k] = sfnt_indextoglyph(sf, idx); } } } } - sfree(data); + return; } } } + error(err_sfntnounicmap, &sf->pos); + return; + bad: + error(err_sfntbadtable, &sf->pos, "cmap"); } void read_sfnt_file(input *in) { @@ -535,8 +928,8 @@ void read_sfnt_file(input *in) { size_t off = 0, got; FILE *fp = in->currfp; font_info *fi = snew(font_info); - size_t i; void *ptr, *end; + t_maxp maxp; fi->name = NULL; fi->widths = newtree234(width_cmp); @@ -557,35 +950,58 @@ void read_sfnt_file(input *in) { 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; - decode(offsubdir_decode, sf->data, sf->end, &sf->osd); -/* - fprintf(stderr, "scaler type = 0x%x; numTables = %u\n", - sf->osd.scaler_type, sf->osd.numTables); -*/ + sf->pos = in->pos; + sf->pos.line = 0; + sf->nglyphs = 0; + ptr = decode(offsubdir_decode, sf->data, sf->end, &sf->osd); + if (ptr == NULL) { + error(err_sfntbadhdr, &sf->pos); + return; + } sf->td = snewn(sf->osd.numTables, tabledir); - decoden(tabledir_decode, (char *)sf->data + 12, sf->end, - sf->td, sizeof(*sf->td), sf->osd.numTables); -/* - for (i = 0; i < sf->osd.numTables; i++) - fprintf(stderr, "table tag = '%c%c%c%c'; offset = %#10x; length = %#10x\n", - sf->td[i].tag >> 24, sf->td[i].tag >> 16, sf->td[i].tag >> 8, sf->td[i].tag, sf->td[i].offset, sf->td[i].length); -*/ - if (!sfnt_findtable(sf, TAG_head, &ptr, &end)) - abort(); - if (decode(t_head_decode, ptr, end, &sf->head) == NULL) - abort(); - if ((sf->head.version & 0xffff0000) != 0x00010000) - abort(); + ptr = decoden(tabledir_decode, ptr, sf->end, sf->td, sizeof(*sf->td), + sf->osd.numTables); + if (ptr == NULL) { + error(err_sfntbadhdr, &sf->pos); + return; + } + if (!sfnt_findtable(sf, TAG_head, &ptr, &end)) { + error(err_sfntnotable, &sf->pos, "head"); + return; + } + if (decode(t_head_decode, ptr, end, &sf->head) == NULL) { + error(err_sfntbadtable, &sf->pos, "head"); + return; + } + if ((sf->head.version & 0xffff0000) != 0x00010000) { + error(err_sfnttablevers, &sf->pos, "head"); + return; + } + if (!sfnt_findtable(sf, TAG_maxp, &ptr, &end)) { + error(err_sfntnotable, &sf->pos, "maxp"); + return; + } + if (decode(t_maxp_decode, ptr, end, &maxp) == NULL) { + error(err_sfntbadtable, &sf->pos, "maxp"); + return; + } + if (maxp.version < 0x00005000 || maxp.version > 0x0001ffff) { + error(err_sfnttablevers, &sf->pos, "maxp"); + return; + } + sf->nglyphs = maxp.numGlyphs; fi->name = sfnt_psname(fi); - sfnt_mapglyphs(sf); + if (fi->name == NULL) return; + sfnt_mapglyphs(fi); sfnt_getmetrics(fi); + sfnt_getkern(fi); sfnt_getmap(fi); fi->next = all_fonts; all_fonts = fi; - fclose(in->currfp); } static int sizecmp(const void *a, const void *b) { @@ -606,6 +1022,7 @@ void sfnt_writeps(font_info const *fi, FILE *ofp) { 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, @@ -629,35 +1046,41 @@ void sfnt_writeps(font_info const *fi, FILE *ofp) { (double)sf->head.yMax / sf->head.unitsPerEm); } else { /* Non-sensible font. */ - fprintf(ofp, "/FontBBox [0 0 0 0] readonly def"); + 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++) - fprintf(ofp, "/%s %u def\n", glyph_extern(sf->glyphsbyindex[i]), i); - fprintf(ofp, "end readonly def\n"); + 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)) - abort(); + if (!sfnt_findtable(sf, TAG_glyf, &glyfptr, &glyfend)) { + error(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)) - abort(); + if (!sfnt_findtable(sf, TAG_loca, &locaptr, &locaend)) { + error(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)) abort(); + 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)) abort(); + sf->nglyphs)) goto badloca; } for (i = 1; i < sf->nglyphs; i++) { - if (loca[i] > glyflen) abort(); + if (loca[i] > glyflen) goto badloca; breaks[sf->osd.numTables + i - 1] = loca[i] + glyfoff; } breaks[sf->osd.numTables + sf->nglyphs - 1] = sf->len; @@ -675,4 +1098,13 @@ void sfnt_writeps(font_info const *fi, FILE *ofp) { fprintf(ofp, "00>] readonly def\n"); sfree(breaks); fprintf(ofp, "end /%s exch definefont\n", fi->name); + return; + badloca: + error(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; }