/*
 * Copyright 2008 Department of Mathematical Sciences, New Mexico State University
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * DEPARTMENT OF MATHEMATICAL SCIENCES OR NEW MEXICO STATE UNIVERSITY BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <stdio.h>

#ifdef WIN32
#include <windows.h>
#else
#include <stdlib.h>
#include <unistd.h>
#endif

#include <string.h>

#include <ft2build.h>
#include FT_GLYPH_H
#include FT_SFNT_NAMES_H
#include FT_TRUETYPE_TABLES_H

/*
 * Include the remapping support.
 */
#include "remap.h"

/**************************************************************************
 *
 * Macros.
 *
 **************************************************************************/

/*
 * The version of otf2bdf.
 */
#define OTF2BDF_VERSION "3.0"

/*
 * Set the default values used to generate a BDF font.
 */
#ifndef DEFAULT_PLATFORM_ID
#define DEFAULT_PLATFORM_ID 3
#endif

#ifndef DEFAULT_ENCODING_ID
#define DEFAULT_ENCODING_ID 1
#endif

#ifndef DEFAULT_POINT_SIZE
#define DEFAULT_POINT_SIZE 12
#endif

#ifndef DEFAULT_RESOLUTION
#define DEFAULT_RESOLUTION 100
#endif

/*
 * Used as a fallback for XLFD names where the character set/encoding can not
 * be determined.
 */
#ifndef DEFAULT_XLFD_CSET
#define DEFAULT_XLFD_CSET "-FontSpecific-0"
#endif

/*
 * nameID macros for getting strings from the OT font.
 */
#define BDFOTF_COPYRIGHT_STRING  0
#define BDFOTF_FAMILY_STRING     1
#define BDFOTF_SUBFAMILY_STRING  2
#define BDFOTF_UNIQUEID_STRING   3
#define BDFOTF_FULLNAME_STRING   4
#define BDFOTF_VENDOR_STRING     5
#define BDFOTF_POSTSCRIPT_STRING 6
#define BDFOTF_TRADEMARK_STRING  7

/*
 * String names for the string indexes. Used for error messages.
 */
static char *string_names[] = {
    "\"Copyright\"",
    "\"Family\"",
    "\"SubFamily\"",
    "\"Unique ID\"",
    "\"Full Name\"",
    "\"Vendor\"",
    "\"Postscript Name\"",
    "\"Trademark\""
};

#if 0
#define TTF_COPYRIGHT 0
#define TTF_TYPEFACE  1
#define TTF_PSNAME    6
#endif

#ifndef MAX
#define MAX(h,i) ((h) > (i) ? (h) : (i))
#endif

#ifndef MIN
#define MIN(l,o) ((l) < (o) ? (l) : (o))
#endif

/**************************************************************************
 *
 * General globals set from command line.
 *
 **************************************************************************/

/*
 * The program name.
 */
static char *prog;

/*
 * The flag indicating whether messages should be printed or not.
 */
static int verbose = 0;

static int alternate_y_offset_calculation = 0;  // u8g2 issue 2406 https://github.com/olikraus/u8g2/issues/2406 

/*
 * Flags used when loading glyphs.
 */
static int load_flags = FT_LOAD_DEFAULT;

/*
 * The default platform and encoding ID's.
 */
static int pid = DEFAULT_PLATFORM_ID;
static int eid = DEFAULT_ENCODING_ID;

/*
 * Default point size and resolutions.
 */
static int point_size = DEFAULT_POINT_SIZE;
static int hres = DEFAULT_RESOLUTION;
static int vres = DEFAULT_RESOLUTION;

/*
 * The user supplied foundry name to use in the XLFD name.
 */
static char *foundry_name = 0;

/*
 * The user supplied typeface name to use in the XLFD name.
 */
static char *face_name = 0;

/*
 * The user supplied weight name to use in the XLFD name.
 */
static char *weight_name = 0;

/*
 * The user supplied slant name to use in the XLFD name.
 */
static char *slant_name = 0;

/*
 * The user supplied width name to use in the XLFD name.
 */
static char *width_name = 0;

/*
 * The user supplied additional style name to use in the XLFD name.
 */
static char *style_name = 0;

/*
 * The user supplied spacing (p = proportional, c = character cell,
 * m = monospace).
 */
static int spacing = 0;

/*
 * The dash character to use in the names retrieved from the font.  Default is
 * the space.
 */
static int dashchar = ' ';

/*
 * Flag, bitmask, and max code for generating a subset of the glyphs in a font.
 */
static int do_subset = 0;
static unsigned short maxcode;
static unsigned long subset[2048];

/*
 * The flag that indicates the remapping table should be used to
 * reencode the font.
 */
static int do_remap = 0;

/**************************************************************************
 *
 * Internal globals.
 *
 **************************************************************************/

/*
 * Structure used for calculating the font bounding box as the glyphs are
 * generated.
 */
typedef struct {
    short minlb;
    short maxlb;
    short maxrb;
    short maxas;
    short maxds;
    short rbearing;
} bbx_t;

static bbx_t bbx;

/*
 * The buffer used to transfer the temporary file to the actual output file.
 */
#define OTF2BDF_IOBUFSIZ 8192
static char iobuf[OTF2BDF_IOBUFSIZ];

/*
 * The Units Per Em value used in numerous places.
 */
static FT_UShort upm;

/*
 * A flag indicating if a CMap was found or not.
 */
static FT_UShort nocmap;

static FT_UShort forcenocmap;

/*
 * The scaling factor needed to compute the SWIDTH (scalable width) value
 * for BDF glyphs.
 */
static double swscale;

/**************************************************************************
 *
 * Platform and encoding table names.
 *
 **************************************************************************/

static char *platform_names[] = {
    "Apple Unicode", "Macintosh", "ISO", "Microsoft", "Unknown"
};
static int nplatform_names = sizeof(platform_names)/sizeof(platform_names[0]);

/*
 * Mac encoding names used when creating the BDF XLFD font name.
 */
static char *mac_encodings[] = {
    "-MacRoman-0",    "-MacJapanese-0",   "-MacChinese-0",   "-MacKorean-0",
    "-MacArabic-0",   "-MacHebrew-0",     "-MacGreek-0",     "-MacRussian-0",
    "-MacRSymbol-0",  "-MacDevanagari-0", "-MacGurmukhi-0",  "-MacGujarati-0",
    "-MacOriya-0",    "-MacBengali-0",    "-MacTamil-0",     "-MacTelugu-0",
    "-MacKannada-0",  "-MacMalayalam-0",  "-MacSinhalese-0", "-MacBurmese-0",
    "-MacKhmer-0",    "-MacThai-0",       "-MacLaotian-0",   "-MacGeorgian-0",
    "-MacArmenian-0", "-MacMaldivian-0",  "-MacTibetan-0",   "-MacMongolian-0",
    "-MacGeez-0",     "-MacSlavic-0",     "-MacVietnamese-0","-MacSindhi-0",
    "-MacUninterp-0"
};
static int nmac_encodings = sizeof(mac_encodings)/sizeof(mac_encodings[0]);

/*
 * ISO encoding names used when creating the BDF XLFD font name.
 */
static char *iso_encodings[] = {
    "-ASCII-0", "-ISO10646-1", "-ISO8859-1"
};
static int niso_encodings = sizeof(iso_encodings)/sizeof(iso_encodings[0]);

/*
 * Microsoft encoding names used when creating the BDF XLFD font name.
 */
static char *ms_encodings[] = {
    "-Symbol-0", "-ISO10646-1", "-ShiftJIS-0", "-GB2312.1980-0", "-Big5-0",
    "-KSC5601.1987-0", "-KSC5601.1992-0"
};
static int nms_encodings = sizeof(ms_encodings)/sizeof(ms_encodings[0]);

/*
 * The propery names for all the XLFD properties.
 */
static char *xlfd_props[] = {
    "FOUNDRY",
    "FAMILY_NAME",
    "WEIGHT_NAME",
    "SLANT",
    "SETWIDTH_NAME",
    "ADD_STYLE_NAME",
    "PIXEL_SIZE",
    "POINT_SIZE",
    "RESOLUTION_X",
    "RESOLUTION_Y",
    "SPACING",
    "AVERAGE_WIDTH",
    "CHARSET_REGISTRY",
    "CHARSET_ENCODING",
};

/**************************************************************************
 *
 * Freetype globals.
 *
 **************************************************************************/

static FT_Library library;
static FT_Face face;
static FT_Size_Metrics imetrics;
static TT_HoriHeader *horizontal;

/**************************************************************************
 *
 * Freetype related code.
 *
 **************************************************************************/

/*
 * A generic routine to get a name from the OT name table.  This routine
 * always looks for English language names and checks three possibilities:
 * 1. English names with the MS Unicode encoding ID.
 * 2. English names with the MS unknown encoding ID.
 * 3. English names with the Apple Unicode encoding ID.
 *
 * The particular name ID mut be provided (e.g. nameID = 0 for copyright
 * string, nameID = 6 for Postscript name, nameID = 1 for typeface name.
 *
 * If the `dash_to_space' flag is non-zero, all dashes (-) in the name will be
 * replaced with the character passed.
 *
 * Returns the number of bytes added.
 */
static int
otf_get_english_string(FT_Face face, int nameID, int dash_to_space,
                       char *name, int name_size)
{
    int j, encid;
    FT_UInt i, nrec;
    FT_SfntName sfntName;
    unsigned char *s;
    unsigned short slen;

    nrec = FT_Get_Sfnt_Name_Count(face);

    for (encid = 1, j = 0; j < 2; j++, encid--) {
        /*
         * Locate one of the MS English font names.
         */
        for (i = 0; i < nrec; i++) {
           FT_Get_Sfnt_Name(face, i, &sfntName);
           if (sfntName.platform_id == 3 &&
               sfntName.encoding_id == encid &&
               sfntName.name_id == nameID &&
               (sfntName.language_id == 0x0409 ||
                sfntName.language_id == 0x0809 ||
                sfntName.language_id == 0x0c09 ||
                sfntName.language_id == 0x1009 ||
                sfntName.language_id == 0x1409 ||
                sfntName.language_id == 0x1809)) {
               s = sfntName.string;
               slen = sfntName.string_len;
               break;
           }
        }

        if (i < nrec) {
            if (slen >> 1 >= name_size) {
                fprintf(stderr, "%s: warning: %s string longer than buffer. Truncating to %d bytes.\n", prog, string_names[nameID], name_size);
                slen = name_size << 1;
            }

            /*
             * Found one of the MS English font names.  The name is by
             * definition encoded in Unicode, so copy every second byte into
             * the `name' parameter, assuming there is enough space.
             */
            for (i = 1; i < slen; i += 2) {
                if (dash_to_space)
                  *name++ = (s[i] != '-') ? s[i] : ' ';
                else if (s[i] == '\r' || s[i] == '\n') {
                    if (s[i] == '\r' && i + 2 < slen && s[i + 2] == '\n')
                      i += 2;
                    *name++ = ' ';
                    *name++ = ' ';
                } else
                  *name++ = s[i];
            }
            *name = 0;
            return (slen >> 1);
        }
    }

    /*
     * No MS English name found, attempt to find an Apple Unicode English
     * name.
     */
    for (i = 0; i < nrec; i++) {
        FT_Get_Sfnt_Name(face, i, &sfntName);
        if (sfntName.platform_id == 0 && sfntName.language_id == 0 &&
            sfntName.name_id == nameID) {
            s = sfntName.string;
            slen = sfntName.string_len;
            break;
        }
    }

    if (i < nrec) {
        if (slen >> 1 >= name_size) {
            fprintf(stderr, "%s: warning: %s string longer than buffer. Truncating to %d bytes.\n", prog, string_names[nameID], name_size);
            slen = name_size << 1;
        }

        /*
         * Found the Apple Unicode English name.  The name is by definition
         * encoded in Unicode, so copy every second byte into the `name'
         * parameter, assuming there is enough space.
         */
        for (i = 1; i < slen; i += 2) {
            if (dash_to_space)
              *name++ = (s[i] != '-') ? s[i] : ' ';
            else if (s[i] == '\r' || s[i] == '\n') {
                if (s[i] == '\r' && i + 2 < slen && s[i + 2] == '\n')
                  i += 2;
                *name++ = ' ';
                *name++ = ' ';
            } else
              *name++ = s[i];
        }
        *name = 0;
        return (slen >> 1);
    }

    return 0;
}

/**************************************************************************
 *
 * Encoding table related functions.
 *
 **************************************************************************/

static char *
platform_name(short pid)
{
    return (pid < nplatform_names) ?
        platform_names[pid] : platform_names[nplatform_names - 1];
}

static char *
encoding_name(short pid, short eid)
{
    int nnames;
    char **names;

    switch (pid) {
      case 0: return "-ISO10646-1";
      case 1:
        nnames = nmac_encodings;
        names = mac_encodings;
        break;
      case 2:
        nnames = niso_encodings;
        names = iso_encodings;
        break;
      case 3:
        nnames = nms_encodings;
        names = ms_encodings;
        break;
      default: return "-Unknown-0";
    }

    return (eid < nnames) ? names[eid] : "-Unknown-0";
}

static char *spaces = "              ";

static void
print_encoding_table(void)
{
    int ncmaps, i, j;
    short pid, eid, lasteid;
    char *np, *platform, encoding[64];

    printf("Encoding tables available in the font:\n\n");
    printf("Platform%.*sEncoding\n", 6, spaces);
    printf("-------------------------------------------\n");
    printf("Default%.*sDefault%.*s(-pid %d -eid %d)\n",
           7, spaces, 7, spaces, DEFAULT_PLATFORM_ID, DEFAULT_ENCODING_ID);
    ncmaps = face->num_charmaps;
    for (lasteid = -1, i = 0; i < ncmaps; i++) {
        pid = face->charmaps[i]->platform_id;
        eid = face->charmaps[i]->encoding_id;
        platform = platform_name(pid);
        np = encoding_name(pid, eid);
        np++;
        for (j = 0; j < 63 && *np != '-'; np++, j++)
          encoding[j] = *np;
        encoding[j] = 0;
        /*
         * Typecast the result of 14-strlen(). This returns a size_t on
         * some platforms and causes a compilation warning.
         */
        printf("%s%.*s%s%.*s(-pid %hd -eid %hd)\n",
               platform, (int) (14 - strlen(platform)), spaces,
               encoding, (int) (14 - strlen(encoding)), spaces, pid, eid);
    }
}

/**************************************************************************
 *
 * General code.
 *
 **************************************************************************/

/*
 * Create an XLFD name.  Assumes there is enough space in the string passed
 * to fit a reasonably long XLFD name into, up to the 256 byte maximum.
 */
static void
make_xlfd_name(char *name, int name_size, FT_Long awidth, int ismono)
{
    FT_Long i;
    FT_ULong val;
    char *r, *e;
    double dr, dp;
    TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);

    /*
     * Default the foundry name to "FreeType" in honor of the project and
     * because the foundry name is too difficult to automatically determine
     * from the names in TT fonts. But the user can provide his own.
     */
    if (foundry_name == 0) {
        (void) strcpy(name, "-FreeType");
        name += 9;
    } else {
        *(name++)='-';
        strcpy(name,foundry_name);
        name+=strlen(foundry_name);
    }

    /*
     * Add the typeface name from the font.  The fallback default will be
     * "Unknown".
     */
    *name++ = '-';
    if (face_name == 0) {
        if((i = otf_get_english_string(face, BDFOTF_FAMILY_STRING, dashchar,
                                       name, name_size)))
          name += i;
        else {
            (void) strcpy(name, "Unknown");
            name += 7;
        }
    } else {
        (void) strcpy(name, face_name);
        name += strlen(face_name);
    }

    /*
     * Add the weight name.  The default will be "Medium".
     */
    if (weight_name != 0) {
        sprintf(name, "-%s", weight_name);
        name += strlen(weight_name) + 1;
    } else {
        if (os2->fsSelection & 0x20) {
            (void) strcpy(name, "-Bold");
            name += 5;
        } else {
            (void) strcpy(name, "-Medium");
            name += 7;
        }
    }

    /*
     * Add the slant name.  The default will be 'R'.
     */
    if (slant_name) {
        sprintf(name, "-%s", slant_name);
        name += strlen(slant_name) + 1;
    } else {
        *name++ = '-';
        if (os2->fsSelection & 0x01)
          *name++ = 'I';
        else
          *name++ = 'R';
    }

    /*
     * Default the setwidth name to "Normal" but user can specify one.
     */
    if (width_name == 0) {
        (void) strcpy(name, "-Normal");
        name += 7;
    } else {
        *(name++)='-';
        (void) strcpy(name, width_name);
        name += strlen(width_name);
    }

    /*
     * Default the additional style name to NULL but user can specify one.
     */
    *name++ = '-';
    if (style_name != 0) {
        (void) strcpy(name, style_name);
        name += strlen(style_name);
    }

    /*
     * Determine the pixel size from the point size and resolution.
     */
    dr = (double) vres;
    dp = (double) (point_size * 10);
    val = (unsigned long) (((dp * dr) / 722.7) + 0.5);

    /*
     * Set the pixel size, point size, and resolution.
     */
    sprintf(name, "-%ld-%d-%d-%d", val, point_size * 10, hres, vres);
    name += strlen(name);

    switch (spacing) {
      case 'p': case 'P': spacing = 'P'; break;
      case 'm': case 'M': spacing = 'M'; break;
      case 'c': case 'C': spacing = 'C'; break;
      default: spacing = 0; break;
    }

    /*
     * Set the spacing.
     */
    if (!spacing)
      spacing = (ismono) ? 'M' : 'P';
    *name++ = '-';
    *name++ = spacing;

    /*
     * Add the average width.
     */
    sprintf(name, "-%ld", awidth);
    name += strlen(name);

    /*
     * Check to see if the remapping table specified a registry and encoding
     * and use those if they both exist.
     */
    otf2bdf_remap_charset(&r, &e);
    if (r != 0 && e != 0) {
        sprintf(name, "-%s-%s", r, e);
        return;
    }

    /*
     * If the cmap for the platform and encoding id was not found, or the
     * platform id is unknown, assume the character set registry and encoding
     * are the XLFD default.
     */
    if (nocmap || pid > 3)
      (void) strcpy(name, DEFAULT_XLFD_CSET);
    else {
        /*
         * Finally, determine the character set registry and encoding from the
         * platform and encoding ID.
         */
        switch (pid) {
          case 0:
            /*
             * Apple Unicode platform, so "-Apple-Unicode" is the default.
             */
            (void) strcpy(name, "-Apple-Unicode");
            break;
          case 1:
            /*
             * Macintosh platform, so choose from the Macintosh encoding
             * strings.
             */
            if (eid < 0 || eid >= nmac_encodings)
              (void) strcpy(name, DEFAULT_XLFD_CSET);
            else
              (void) strcpy(name, mac_encodings[eid]);
            break;
          case 2:
            /*
             * ISO platform, so choose from the ISO encoding strings.
             */
            if (eid < 0 || eid >= niso_encodings)
              (void) strcpy(name, DEFAULT_XLFD_CSET);
            else
              (void) strcpy(name, iso_encodings[eid]);
            break;
          case 3:
            /*
             * Microsoft platform, so choose from the MS encoding strings.
             */
            if (eid < 0 || eid >= nms_encodings)
              (void) strcpy(name, DEFAULT_XLFD_CSET);
            else
              (void) strcpy(name, ms_encodings[eid]);
            break;
        }
    }
}

static int
generate_font(FILE *out, char *iname, char *oname)
{
    int eof, ismono, i;
    FILE *tmp;
    FT_Short x, y, dwidth, swidth;
    FT_Short y_off, x_off;
    FT_Long sx, sy, ex, ey, wd, ht;
    FT_Long code, idx, ng, aw;
    FT_UShort remapped_code;
    unsigned char *bp;
    double dw;
    char *xp, xlfd[BUFSIZ];
    char *tmpdir, tmpfile[BUFSIZ];

    imetrics = face->size->metrics;
    horizontal = FT_Get_Sfnt_Table(face, ft_sfnt_hhea);

    /*
     * Clear the BBX.
     */
    memset((char *) &bbx, 0, sizeof(bbx_t));

    /*
     * Open a temporary file to store the bitmaps in until the exact number
     * of bitmaps are known.
     */
    if ((tmpdir = getenv("TMPDIR")) == 0)
      tmpdir = "/tmp";
    sprintf(tmpfile, "%s/otf2bdf%ld", tmpdir, (long) getpid());
    if ((tmp = fopen(tmpfile, "w")) == 0) {
        fprintf(stderr, "%s: unable to open temporary file '%s'.\n",
                prog, tmpfile);
        return -1;
    }

    /*
     * Calculate the scale factor for the SWIDTH field.
     */
    swscale = ((double) vres) * ((double) point_size);

    /*
     * Initialize the flag that tracks if the font is monowidth or not and
     * initialize the glyph width variable that is used for testing for a
     * monowidth font.
     */
    wd = 0xffff;
    ismono = 1;

    for (ng = code = 0, eof = 0, aw = 0; eof != EOF && code < 0xffff; code++) {

        /*
         * If a remap is indicated, attempt to remap the code.  If a remapped
         * code is not found, then skip generating the glyph.
         */
        remapped_code = (FT_UShort) code;
        if (do_remap && !otf2bdf_remap(&remapped_code))
          continue;

        /*
         * If a subset is being generated and the code is greater than the max
         * code of the subset, break out of the loop to avoid doing any more
         * work.
         */
        if (do_subset && remapped_code > maxcode)
          break;

        /*
         * If a subset is being generated and the index is not in the subset
         * bitmap, just continue.
         */
        if (do_subset &&
            !(subset[remapped_code >> 5] & (1 << (remapped_code & 31))))
          continue;

        if (nocmap) {
            if (code >= face->num_glyphs)

              /*
               * At this point, all the glyphs are done.
               */
              break;
            idx = code;
        } else
          idx = FT_Get_Char_Index(face, code);

        /*
         * If the glyph could not be loaded for some reason, or a subset is
         * being generated and the index is not in the subset bitmap, just
         * continue.
         */

        if (idx <= 0 || FT_Load_Glyph(face, idx, load_flags))
          continue;

        if (FT_Render_Glyph(face->glyph, FT_RENDER_MODE_MONO))
          continue;

        /*
         * Determine the DWIDTH (device width, or advance width in TT terms)
         * and the SWIDTH (scalable width) values.
         */
        dwidth = face->glyph->metrics.horiAdvance >> 6;
        dw = (double) dwidth;
        swidth = (FT_Short) ((dw * 72000.0) / swscale);

        /*
         * Determine the actual bounding box of the glyph bitmap.  Do not
         * forget that the glyph is rendered upside down!
         */
        sx = sy = 0xffff;
        ex = ey = 0;
        bp = face->glyph->bitmap.buffer;
        for (y = 0; y < face->glyph->bitmap.rows; y++) {
            for (x = 0; x < face->glyph->bitmap.width; x++) {
                if (bp[x >> 3] & (0x80 >> (x & 7))) {
                    if (x < sx) sx = x;
                    if (x > ex) ex = x;
                    if (y < sy) sy = y;
                    if (y > ey) ey = y;
                }
            }
            bp += face->glyph->bitmap.pitch;
        }

        /*
         * If the glyph is actually an empty bitmap, set the size to 0 all
         * around.
         */
        if (sx == 0xffff && sy == 0xffff && ex == 0 && ey == 0)
          sx = ex = sy = ey = 0;
        else {
            /*
             * Adjust the end points.
             */
            ex++;
            ey++;
        }

        /*
         * Increment the number of glyphs generated.
         */
        ng++;

        /*
         * Test to see if the font is going to be monowidth or not by
         * comparing the current glyph width against the last one.
         */
        if (wd != 0xffff && ismono && (ex - sx) + 1 != wd)
          ismono = 0;

        /*
         * Adjust the font bounding box.
         */
        wd = ex - sx;
        ht = ey - sy;
        x_off = sx + face->glyph->bitmap_left;
        y_off = sy + face->glyph->bitmap_top - face->glyph->bitmap.rows;
        // u8g2 issue 2406
        // the above calculation seems to be wrong, this should be: 
        if ( alternate_y_offset_calculation )
          y_off = face->glyph->bitmap_top - ey;

        bbx.maxas = MAX(bbx.maxas, ht + y_off);
        bbx.maxds = MAX(bbx.maxds, -y_off);
        bbx.rbearing = wd + x_off;
        bbx.maxrb = MAX(bbx.maxrb, bbx.rbearing);
        bbx.minlb = MIN(bbx.minlb, x_off);
        bbx.maxlb = MAX(bbx.maxlb, x_off);

        /*
         * Add to the average width accumulator.
         */
        aw += wd;

        /*
         * Print the bitmap header.
         */
        fprintf(tmp, "STARTCHAR %04lX\nENCODING %ld\n", code,
                (long) remapped_code);
        fprintf(tmp, "SWIDTH %hd 0\n", swidth);
        fprintf(tmp, "DWIDTH %hd 0\n", dwidth);
        fprintf(tmp, "BBX %ld %ld %hd %hd\n", wd, ht, x_off, y_off);

        // debug output (u8g2 issue 2406)
        //if ( remapped_code >= 96 && remapped_code <= 103 )
        //   fprintf(stderr, "%05ld BBX %ld %ld %hd %hd sy=%d, ey=%d bitmap_top=%d bitmap.rows=%d\n", (long) remapped_code, wd, ht, x_off, y_off, (int)sy, (int)ey, face->glyph->bitmap_top, face->glyph->bitmap.rows);

        /*
         * Check for an error return here in case the temporary file system
         * fills up or the file is deleted while it is being used.
         */
        eof = fprintf(tmp, "BITMAP\n");

        bp = face->glyph->bitmap.buffer + (sy * face->glyph->bitmap.pitch);
        for (y = 0; eof != EOF && y < ey - sy; y++) {
            for (idx = 0, x = 0; eof != EOF && x < ex - sx; x++) {
                if (x > 0 && (x & 7) == 0) {
                    /*
                     * Print the next byte.
                     */
                    eof = fprintf(tmp, "%02lX", idx & 0xff);
                    idx = 0;
                }
                if (bp[(x+sx) >> 3] & (0x80 >> ((x+sx) & 7)))
                  idx |= (0x80 >> (x & 7));
            }
            bp += face->glyph->bitmap.pitch;
            if (eof != EOF)
              /*
               * Because of the structure of the loop, the last byte should
               * always be printed.
               */
              fprintf(tmp, "%02lX\n", idx & 0xff);
        }
        if (eof != EOF)
          fprintf(tmp, "ENDCHAR\n");
    }

    fclose(tmp);

    /*
     * If a write error occured, delete the temporary file and issue an error
     * message.
     */
    if (eof == EOF) {
        (void) unlink(tmpfile);
        fprintf(stderr, "%s: problem writing to temporary file '%s'.\n",
                prog, tmpfile);
        return -1;
    }

    /*
     * If no characters were generated, just unlink the temp file and issue a
     * warning.
     */
    if (ng == 0) {
        (void) unlink(tmpfile);
        fprintf(stderr, "%s: no glyphs generated from '%s'.\n", prog, iname);
        return -1;
    }

    /*
     * Reopen the temporary file so it can be copied to the actual output
     * file.
     */
    if ((tmp = fopen(tmpfile, "r")) == 0) {
        /*
         * Unable to open the file for read, so attempt to delete it and issue
         * an error message.
         */
        (void) unlink(tmpfile);
        fprintf(stderr, "%s: unable to open temporary file '%s' for read.\n",
                prog, tmpfile);
        return -1;
    }

    /*
     * Calculate the average width.
     */
    aw = (FT_Long) ((((double) aw / (double) ng) + 0.5) * 10.0);

    /*
     * Generate the XLFD font name.
     */
    make_xlfd_name(xlfd, sizeof(xlfd), aw, ismono);

    /*
     * Start writing the font out.
     */
    fprintf(out, "STARTFONT 2.1\n");

    /*
     * Add the vanity comments.
     */
    fprintf(out, "COMMENT\n");
    fprintf(out, "COMMENT Converted from OpenType font \"%s\" by \"%s %s\".\n",
            iname, prog, OTF2BDF_VERSION);
    fprintf(out, "COMMENT\n");

    fprintf(out, "FONT %s\n", xlfd);
    fprintf(out, "SIZE %d %d %d\n", point_size, hres, vres);

    /*
     * Generate the font bounding box.
     */
    fprintf(out, "FONTBOUNDINGBOX %hd %hd %hd %hd\n",
            bbx.maxrb - bbx.minlb, bbx.maxas + bbx.maxds,
            bbx.minlb, -bbx.maxds);

    /*
     * Print the properties.
     */
    fprintf(out, "STARTPROPERTIES %hd\n", 19);

    /*
     * Print the font properties from the XLFD name.
     */
    for (i = 0, xp = xlfd; i < 14; i++) {
        /*
         * Print the XLFD property name.
         */
        fprintf(out, "%s ", xlfd_props[i]);

        /*
         * Make sure the ATOM properties are wrapped in double quotes.
         */
        if (i < 6 || i == 10 || i > 11)
          putc('"', out);

        /*
         * Skip the leading '-' in the XLFD name.
         */
        xp++;

        /*
         * Skip until the next '-' or NULL.
         */
        for (; *xp && *xp != '-'; xp++)
          putc(*xp, out);

        /*
         * Make sure the ATOM properties are wrapped in double quotes.
         */
        if (i < 6 || i == 10 || i > 11)
          putc('"', out);

        putc('\n', out);
    }

    /*
     * Make sure to add the FONT_ASCENT and FONT_DESCENT properties
     * because X11 can not live without them.
     */
    fprintf(out, "FONT_ASCENT %hd\nFONT_DESCENT %hd\n",
            (horizontal->Ascender * imetrics.y_ppem) / upm,
            -((horizontal->Descender * imetrics.y_ppem) / upm));

    /*
     * Get the copyright string from the font.
     */
    (void) otf_get_english_string(face, BDFOTF_COPYRIGHT_STRING, 0, xlfd,
                                  sizeof(xlfd));
    fprintf(out, "COPYRIGHT \"%s\"\n", xlfd);

    /*
     * Last, print the two user-defined properties _OTF_FONTFILE and
     * _OTF_PSNAME.  _OTF_FONTFILE provides a reference to the original OT
     * font file which some systems can take advantage of, and _OTF_PSNAME
     * provides the Postscript name of the font if it exists.
     */
    (void) otf_get_english_string(face, BDFOTF_POSTSCRIPT_STRING, 0, xlfd,
                                  sizeof(xlfd));
    fprintf(out, "_OTF_FONTFILE \"%s\"\n_OTF_PSNAME \"%s\"\n", iname, xlfd);

    fprintf(out, "ENDPROPERTIES\n");

    /*
     * Print the actual number of glyphs to the output file.
     */
    eof = fprintf(out, "CHARS %ld\n", ng);

    /*
     * Copy the temporary file to the output file.
     */
    while (eof != EOF && (ng = fread(iobuf, 1, OTF2BDF_IOBUFSIZ, tmp))) {
        if (fwrite(iobuf, 1, ng, out) == 0)
          eof = EOF;
    }
        
    /*
     * Close the temporary file and delete it.
     */
    fclose(tmp);
    (void) unlink(tmpfile);

    /*
     * If an error occured when writing to the output file, issue a warning
     * and return.
     */
    if (eof == EOF) {
        fprintf(stderr, "%s: problem writing to output file '%s'.\n",
                prog, oname);
        return -1;
    }

    /*
     * End the font and do memory cleanup on the glyph and raster structures.
     */
    eof = fprintf(out, "ENDFONT\n");

    return eof;
}

static int
generate_bdf(FILE *out, char *iname, char *oname)
{
    FT_Long i;

    /*
     * Get the requested cmap.
     */
    for (i = 0; i < face->num_charmaps; i++) {
        if (face->charmaps[i]->platform_id == pid &&
            face->charmaps[i]->encoding_id == eid)
          break;
    }
    if (i == face->num_charmaps && pid == 3 && eid == 1) {
        /*
         * Make a special case when this fails with pid == 3 and eid == 1.
         * Change to eid == 0 and try again.  This captures the two possible
         * cases for MS fonts.  Some other method should be used to cycle
         * through all the alternatives later.
         */
        for (i = 0; i < face->num_charmaps; i++) {
        if (face->charmaps[i]->platform_id == pid &&
            face->charmaps[i]->encoding_id == 0)
              break;
        }
        if (i < face->num_charmaps) {
            pid = 3;
            eid = 1;
            FT_Set_Charmap(face, face->charmaps[i]);
        } else {
            /*
             * No CMAP was found.
             */
            nocmap = 1;
            pid = eid = -1;
        }
    } else {
        if ( forcenocmap )
        {
            nocmap = 1;
            pid = eid = -1;
        }
        else
        {
          FT_Set_Charmap(face, face->charmaps[i]);
          nocmap = 0;
        }
    }

    if (nocmap && verbose) {
        fprintf(stderr,
                    "%s: no character map for platform %d encoding %d.  ",
                    prog, pid, eid);
        fprintf(stderr, "Generating all glyphs.\n");
    }

    /*
     * Now go through and generate the glyph bitmaps themselves.
     */
    return generate_font(out, iname, oname);
}

#define isdig(cc) ((cc) >= '0' && (cc) <= '9')

/*
 * Routine to parse a subset specification supplied on the command line.
 * The syntax for this specification is the same as the syntax used for
 * the XLFD font names (XLFD documentation, page 9).
 *
 * Example:
 *
 *  "60 70 80_90" means the glyphs at codes 60, 70, and between 80 and
 *  90 inclusive.
 */
static void
parse_subset(char *s)
{
    long l, r;

    /*
     * Make sure to clear the flag and bitmap in case more than one subset is
     * specified on the command line.
     */
    maxcode = 0;
    do_subset = 0;
    (void) memset((char *) subset, 0, sizeof(unsigned long) * 2048);

    while (*s) {
        /*
         * Collect the next code value.
         */
        for (l = r = 0; *s && isdig(*s); s++)
          l = (l * 10) + (*s - '0');

        /*
         * If the next character is an '_', advance and collect the end of the
         * specified range.
         */
        if (*s == '_') {
            s++;
            for (; *s && isdig(*s); s++)
              r = (r * 10) + (*s - '0');
        } else
          r = l;

        /*
         * Add the range just collected to the subset bitmap and set the flag
         * that indicates a subset is wanted.
         */
        for (; l <= r; l++) {
            do_subset = 1;
            subset[l >> 5] |= (1 << (l & 31));
            if (l > maxcode)
              maxcode = l;
        }

        /*
         * Skip all non-digit characters.
         */
        while (*s && !isdig(*s))
          s++;
    }
}

static void
usage(int eval)
{
    printf("Usage: %s [options below] font.ttf\n", prog);
    printf("-h\t\tThis message.\n");
    printf("-v\t\tPrint warning messages during conversion.\n");
    printf("-l \"subset\"\tSpecify a subset of glyphs to generate.\n");
    printf("-m mapfile\tGlyph reencoding file.\n");
    printf("-n\t\tTurn off glyph hinting.\n");
    printf("-a\t\tForce auto hinting.\n");
    printf("-y\t\tApply bugfix for the bbx y offset calculation (u8g2 issue 2406).\n");
    printf("-g\t\tOutput raw glyphs instead of unicode chars.\n");
    printf("-et\t\tDisplay the encoding tables available in the font.\n");
    printf("-c c\t\tSet the character spacing (default: from font).\n");
    printf("-f name\t\tSet the foundry name (default: freetype).\n");
    printf("-t name\t\tSet the typeface name (default: from font).\n");
    printf("-w name\t\tSet the weight name (default: Medium).\n");
    printf("-s name\t\tSet the slant name (default: R).\n");
    printf("-k name\t\tSet the width name (default: Normal).\n");
    printf("-d name\t\tSet the additional style name (default: empty).\n");
    printf("-u char\t\tSet the character to replace '-' in names ");
    printf("(default: space).\n");
    printf("-pid id\t\tSet the platform ID for encoding (default: %d).\n",
            DEFAULT_PLATFORM_ID);
    printf("-eid id\t\tSet the encoding ID for encoding (default: %d).\n",
            DEFAULT_ENCODING_ID);
    printf("-p n\t\tSet the point size (default: %dpt).\n",
           DEFAULT_POINT_SIZE);
    printf("-r n\t\tSet the horizontal and vertical resolution ");
    printf("(default: %ddpi).\n", DEFAULT_RESOLUTION);
    printf("-rh n\t\tSet the horizontal resolution ");
    printf("(default: %ddpi)\n", DEFAULT_RESOLUTION);
    printf("-rv n\t\tSet the vertical resolution ");
    printf("(default: %ddpi)\n", DEFAULT_RESOLUTION);
    printf("-o outfile\tSet the output filename (default: stdout).\n");
    exit(eval);
}

int
main(int argc, char *argv[])
{
    int res, pet;
    char *infile, *outfile, *iname, *oname;
    FILE *out, *mapin;

    if ((prog = strrchr(argv[0], '/')))
      prog++;
    else
      prog = argv[0];

    /*
     * Flag indicating whether the encoding tables are supposed to be printed
     * or not.
     */
    pet = 0;

    out = stdout;
    infile = outfile = 0;

    argc--;
    argv++;

    while (argc > 0) {
        if (argv[0][0] == '-') {
            switch (argv[0][1]) {
              case 'v': case 'V':
                verbose = 1;
                break;
              case 'l': case 'L':
                argc--;
                argv++;
                parse_subset(argv[0]);
                break;
              case 'n': case 'N':
                load_flags |= FT_LOAD_NO_HINTING;
                break;
              case 'a': case 'A':
                load_flags |= FT_LOAD_FORCE_AUTOHINT;
                break;
              case 'y': case 'Y':
                alternate_y_offset_calculation = 1;
                break;
              case 'g': case 'G':              
                forcenocmap = 1;
                break;
              case 'c': case 'C':
                argc--;
                argv++;
                spacing = argv[0][0];
                break;
              case 't': case 'T':
                argc--;
                argv++;
                face_name = argv[0];
                break;
              case 'w': case 'W':
                argc--;
                argv++;
                weight_name = argv[0];
                break;
              case 's': case 'S':
                argc--;
                argv++;
                slant_name = argv[0];
                break;
              case 'k': case 'K':
                argc--;
                argv++;
                width_name = argv[0];
                break;
              case 'd': case 'D':
                argc--;
                argv++;
                style_name = argv[0];
                break;
              case 'f': case 'F':
                argc--;
                argv++;
                foundry_name = argv[0];
                break;
              case 'u': case 'U':
                argc--;
                argv++;
                dashchar = argv[0][0];
                break;
              case 'p': case 'P':
                res = argv[0][2];
                argc--;
                argv++;
                if (res == 'i' || res == 'I') {
                    /*
                     * No need to print the encoding tables if the user
                     * is supplying a platform ID.
                     */
                    pet = 0;

                    /*
                     * Set the platform ID.
                     */
                    pid = atoi(argv[0]);
                } else
                  /*
                   * Set the point size.
                   */
                  point_size = atoi(argv[0]);
                break;
              case 'e': case 'E':
                if (argv[0][2] == 't' || argv[0][2] == 'T')
                  pet = 1;
                else {
                    /*
                     * No need to print the encoding tables if the user
                     * is supplying a platform ID.
                     */
                    pet = 0;

                    /*
                     * Set the encoding ID.
                     */
                    argc--;
                    argv++;
                    eid = atoi(argv[0]);
                }
                break;
              case 'r':
                /*
                 * Set the horizontal and vertical resolutions.
                 */
                if (argv[0][2] == 'h')
                  hres = atoi(argv[1]);
                else if (argv[0][2] == 'v')
                  vres = atoi(argv[1]);
                else
                  hres = vres = atoi(argv[1]);
                argc--;
                argv++;
                break;
              case 'm': case 'M':
                /*
                 * Try to load a remap table.
                 */
                argc--;
                argv++;

                /*
                 * Always reset the `do_remap' variable here in case more than
                 * one map file appears on the command line.
                 */
                do_remap = 0;
                if ((mapin = fopen(argv[0], "r")) == 0)
                  fprintf(stderr, "%s: unable to open the remap table '%s'.\n",
                          prog, argv[0]);
                else {
                    if (otf2bdf_load_map(mapin) < 0) {
                        fprintf(stderr,
                                "%s: problem loading remap table '%s'.\n",
                                prog, argv[0]);
                        do_remap = 0;
                    } else
                      do_remap = 1;
                    fclose(mapin);
                }
                break;
              case 'o': case 'O':
                /*
                 * Set the output file name.
                 */
                argc--;
                argv++;
                outfile = argv[0];
                break;
              default:
                usage(1);
            }
        } else
          /*
           * Set the input file name.
           */
          infile = argv[0];

        argc--;
        argv++;
    }

    /*
     * Validate the values passed on the command line.
     */
    if (infile == 0) {
        fprintf(stderr, "%s: no input file provided.\n", prog);
        usage(1);
    }
    /*
     * Set the input filename that will be passed to the generator
     * routine.
     */
    if ((iname = strrchr(infile, '/')))
      iname++;
    else
      iname = infile;

    /*
     * Check the platform and encoding IDs.
     */
    if (pid < 0 || pid > 255) {
        fprintf(stderr, "%s: invalid platform ID '%d'.\n", prog, pid);
        exit(1);
    }
    if (eid < 0 || eid > 65535) {
        fprintf(stderr, "%s: invalid encoding ID '%d'.\n", prog, eid);
        exit(1);
    }

    /*
     * Arbitrarily limit the point size to a minimum of 2pt and maximum of
     * 256pt.
     */
    if (point_size < 2 || point_size > 256) {
        fprintf(stderr, "%s: invalid point size '%dpt'.\n", prog, point_size);
        exit(1);
    }

    /*
     * Arbitrarily limit the resolutions to a minimum of 10dpi and a maximum
     * of 1200dpi.
     */
    if (hres < 10 || hres > 1200) {
        fprintf(stderr, "%s: invalid horizontal resolution '%ddpi'.\n",
                prog, hres);
        exit(1);
    }
    if (vres < 10 || vres > 1200) {
        fprintf(stderr, "%s: invalid vertical resolution '%ddpi'.\n",
                prog, vres);
        exit(1);
    }

    /*
     * Open the output file if specified.
     */
    if (outfile != 0) {
        /*
         * Attempt to open the output file.
         */
        if ((out = fopen(outfile, "w")) == 0) {
            fprintf(stderr, "%s: unable to open the output file '%s'.\n",
                    prog, outfile);
            exit(1);
        }
        /*
         * Set the output filename to be passed to the generator routine.
         */
        if ((oname = strrchr(outfile, '/')))
          oname++;
        else
          oname = outfile;
    } else
      /*
       * Set the default output file name to <stdout>.
       */
      oname = "<stdout>";

    /*
     * Intialize Freetype.
     */
    if ((res = FT_Init_FreeType(&library))) {
        /*
         * Close the output file.
         */
        if (out != stdout) {
            fclose(out);
            (void) unlink(outfile);
        }
        fprintf(stderr, "%s[%d]: unable to initialize renderer.\n",
                prog, res);
        exit(1);
    }

    /*
     * Open the input file.
     */
    if ((res = FT_New_Face(library, infile, 0, &face))) {
        if (out != stdout) {
            fclose(out);
            (void) unlink(outfile);
        }
        fprintf(stderr, "%s[%d]: unable to open input file '%s'.\n",
                prog, res, infile);
        exit(1);
    }

    if (pet)
      /*
       * Simply print the encoding tables and do nothing else.
       */
      print_encoding_table();
    else {
        /*
         * Set the instance resolution and point size and the relevant
         * metrics.
         */
        FT_Set_Char_Size(face, 0, point_size * 64, hres, vres);

        /*
         * Set the global units per em value for convenience.
         */
        upm = face->units_per_EM;

        /*
         * Generate the BDF font from the TrueType font.
         */
        res = generate_bdf(out, iname, oname);
    }

    /*
     * Free up the mapping table if one was loaded.
     */
    otf2bdf_free_map();

    /*
     * Close the input and output files.
     */
    (void) FT_Done_Face(face);
    if (out != stdout) {
        fclose(out);
        if (res < 0)
          /*
           * An error occured when generating the font, so delete the
           * output file.
           */
          (void) unlink(outfile);
    }

    /*
     * Shut down the renderer.
     */
    FT_Done_FreeType(library);

    exit(res);

    return 0;
}
