Revamp of the Halibut error handling mechanism.
[sgt/halibut] / in_sfnt.c
index 9e21bf2..218bcbf 100644 (file)
--- a/in_sfnt.c
+++ b/in_sfnt.c
@@ -12,6 +12,9 @@
  *
  * The TrueType Reference Manual:
  * <http://developer.apple.com/textfonts/TTRefMan/>
+ *
+ * Microsoft typography specifications:
+ * <http://www.microsoft.com/typography/SpecificationsOverview.mspx>
  */
 
 #include <assert.h>
@@ -127,7 +130,9 @@ sfnt_decode offsubdir_decode[] = {
 #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
@@ -288,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;
@@ -349,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;
@@ -385,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)) {
+       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,
@@ -405,6 +496,7 @@ static char *sfnt_psname(font_info *fi) {
            }
        }
     }
+    err_sfntnopsname(&sf->pos);
     return NULL;
 }
 
@@ -414,6 +506,9 @@ static int glyphsbyname_cmp(void const *a, void const *b) {
     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) {
@@ -424,6 +519,14 @@ static int glyphsbyname_cmp_search(void const *a, void const *b) {
     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)
  *
@@ -436,57 +539,79 @@ static void sfnt_mapglyphs(font_info *fi) {
     void *ptr, *end;
     unsigned char *sptr;
     char tmp[256];
-    glyph *extraglyphs;
-    unsigned nextras, i, g;
+    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);
+    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;
-    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 > (char *)end) return;
-       decode_uint16(ptr, &sf->nglyphs);
-       ptr = (char *)ptr + 2;
-       if ((char *)ptr + 2*sf->nglyphs > (char *)end) return;
-       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->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++) {
-           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;
-       }
-       sfree(extraglyphs);
-       break;
-      default:
-       abort();
+       for (i = 0; i < sf->nglyphs; i++)
+           sf->glyphsbyindex[i] = genglyph(i);
     }
     /* Construct glyphsbyname */
     sf->glyphsbyname = snewn(sf->nglyphs, unsigned short);
@@ -495,13 +620,53 @@ static void sfnt_mapglyphs(font_info *fi) {
     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);
 }
 
-static glyph sfnt_indextoglyph(sfnt *sf, unsigned short idx) {
+glyph sfnt_indextoglyph(sfnt *sf, unsigned idx) {
     return sf->glyphsbyindex[idx];
 }
 
-static unsigned short sfnt_glyphtoindex(sfnt *sf, glyph g) {
+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),
@@ -524,22 +689,34 @@ void sfnt_getmetrics(font_info *fi) {
     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))
-       abort();
-    if (decode(t_hhea_decode, ptr, end, &hhea) == NULL)
-       abort();
-    if ((hhea.version & 0xffff0000) != 0x00010000)
-       abort();
+    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)
-       abort();
-    if (!sfnt_findtable(sf, TAG_hmtx, &ptr, &end))
-       abort();
+    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)
-       abort();
+               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);
@@ -551,27 +728,102 @@ void sfnt_getmetrics(font_info *fi) {
     if (!sfnt_findtable(sf, TAG_OS_2, &ptr, &end))
        return;
     if (decode(uint16_decode, ptr, end, &OS_2.version) == NULL)
-       return;
+       goto bados2;
     if (OS_2.version >= 2) {
        if (decode(t_OS_2_v2_decode, ptr, end, &OS_2) == NULL)
-           return;
+           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)
-           return;
+           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, 3), or (3, 1) table, both 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;
@@ -584,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)) {
+       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;
@@ -610,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;
@@ -630,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();
+                               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) abort();
+                               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) {
@@ -658,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);
@@ -680,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) {
+       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) {
+       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;
-    fclose(in->currfp);
 }
 
 static int sizecmp(const void *a, const void *b) {
@@ -767,23 +1060,27 @@ void sfnt_writeps(font_info const *fi, FILE *ofp) {
     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)) {
+       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)) {
+       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;
@@ -801,46 +1098,9 @@ 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);
-}
-
-void sfnt_cmap(font_encoding *fe, object *cmap) {
-    unsigned i;
-
-    objtext(cmap, "<</Type/CMap\n/CMapName/");
-    objtext(cmap, fe->name);
-    objtext(cmap, "\n/CIDSystemInfo<</Registry(Adobe)/Ordering(Identity)"
-           "/Supplement 0>>\n");
-    objstream(cmap, "%!PS-Adobe-3.0 Resource-CMap\n"
-             "%%DocumentNeededResources: procset CIDInit\n"
-             "%%IncludeResource: procset CIDInit\n"
-             "%%BeginResource: CMap ");
-    objstream(cmap, fe->name);
-    objstream(cmap, "\n%%Title (");
-    objstream(cmap, fe->name);
-    objstream(cmap, " Adobe Identity 0)\n%%Version: 1\n%%EndComments\n");
-    objstream(cmap, "/CIDInit/ProcSet findresource begin\n");
-    objstream(cmap, "12 dict begin begincmap\n");
-    objstream(cmap, "/CIDSystemInfo 3 dict dup begin\n"
-             "/Registry(Adobe)def/Ordering(Identity)def/Supplement 0 def "
-             "end def\n");
-    objstream(cmap, "/CMapName/");
-    objstream(cmap, fe->name);
-    objstream(cmap, " def/CMapType 0 def/WMode 0 def\n");
-    objstream(cmap, "1 begincodespacerange<00><FF>endcodespacerange\n");
-    for (i = 0; i < 256; i++) {
-       char buf[20];
-       if (fe->vector[i] == NOGLYPH)
-           continue;
-       objstream(cmap, "1 begincidchar");
-       sprintf(buf, "<%02X>", i);
-       objstream(cmap, buf);
-       sprintf(buf, "%hu", sfnt_glyphtoindex(fe->font->info->fontfile,
-                                             fe->vector[i]));
-       objstream(cmap, buf);
-       objstream(cmap, " endcidchar\n");
-    }
-    objstream(cmap, "endcmap CMapName currentdict /CMap defineresource pop "
-             "end end\n%%EndResource\n%%EOF\n");
+    return;
+  badloca:
+    err_sfntbadtable(&sf->pos, "loca");
 }
 
 void sfnt_data(font_info *fi, char **bufp, size_t *lenp) {