Add support for extracting some global font metrics from the 'OS/2' table
[sgt/halibut] / in_sfnt.c
index b8e51da..fe16941 100644 (file)
--- a/in_sfnt.c
+++ b/in_sfnt.c
@@ -62,14 +62,12 @@ static void decode_uint32(void *src, void *dest) {
 }
 #define d_uint32 decode_uint32, 4
 
-#if 0 /* unused */
 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
-#endif
 
 static void decode_skip(void *src, void *dest) {
     IGNORE(src);
@@ -123,6 +121,7 @@ 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
@@ -149,6 +148,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 {
@@ -277,7 +325,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;
@@ -286,7 +334,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) },
@@ -360,13 +408,30 @@ static char *sfnt_psname(font_info *fi) {
     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;
+    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;
+}
+
 /*
  * 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;
@@ -378,8 +443,10 @@ static void sfnt_mapglyphs(sfnt *sf) {
     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;
+    fi->italicangle = post.italicAngle / 65536.0;
     if (ptr == NULL) abort();
     switch (post.format) {
       case 0x00010000:
@@ -421,18 +488,42 @@ static void sfnt_mapglyphs(sfnt *sf) {
       default:
        abort();
     }
+    /* 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);
+}
+
+static glyph sfnt_indextoglyph(sfnt *sf, unsigned short idx) {
+    return sf->glyphsbyindex[idx];
+}
+
+static unsigned short 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;
 
+    /* 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))
        abort();
     if (decode(t_hhea_decode, ptr, end, &hhea) == NULL)
@@ -451,19 +542,36 @@ void sfnt_getmetrics(font_info *fi) {
        abort();
     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)
+       return;
+    if (OS_2.version >= 2) {
+       if (decode(t_OS_2_v2_decode, ptr, end, &OS_2) == NULL)
+           return;
+       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;
+    } else
+       return;
+    fi->ascent = OS_2.sTypoAscender * FUNITS_PER_PT / sf->head.unitsPerEm;
+    fi->descent = OS_2.sTypoDescender * FUNITS_PER_PT / sf->head.unitsPerEm;
 }
 
 /*
  * 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, 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.
  */
 void sfnt_getmap(font_info *fi) {
     sfnt *sf = fi->fontfile;
@@ -523,7 +631,7 @@ void sfnt_getmap(font_info *fi) {
                            idx = (k + idDelta[j]) & 0xffff;
                            if (idx != 0) {
                                if (idx > sf->nglyphs) abort();
-                               fi->bmp[k] = sf->glyphsbyindex[idx];
+                               fi->bmp[k] = sfnt_indextoglyph(sf, idx);
                            }
                        }
                    } else {
@@ -533,7 +641,7 @@ void sfnt_getmap(font_info *fi) {
                            if (idx != 0) {
                                idx = (idx + idDelta[j]) & 0xffff;
                                if (idx > sf->nglyphs) abort();
-                               fi->bmp[k] = sf->glyphsbyindex[idx];
+                               fi->bmp[k] = sfnt_indextoglyph(sf, idx);
                            }
                        }
                    }
@@ -595,7 +703,7 @@ void read_sfnt_file(input *in) {
     if ((sf->head.version & 0xffff0000) != 0x00010000)
        abort();
     fi->name = sfnt_psname(fi);
-    sfnt_mapglyphs(sf);
+    sfnt_mapglyphs(fi);
     sfnt_getmetrics(fi);
     sfnt_getmap(fi);
     fi->next = all_fonts;
@@ -621,6 +729,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,
@@ -644,13 +753,15 @@ 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++) {