view tools/fontconv.c @ 2294:7f6ba3b32f54

Cleanups.
author Matti Hamalainen <ccr@tnsp.org>
date Wed, 03 Jul 2019 10:28:43 +0300
parents 8ca515ab9c84
children b7cd5dd0b82e
line wrap: on
line source

/*
 * fontconv - Convert bitmap fonts
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2012-2015 Tecnic Software productions (TNSP)
 *
 * Please read file 'COPYING' for information on license and distribution.
 */
#include <stdio.h>
#include "dmlib.h"
#include "dmargs.h"
#include "dmfile.h"
#include "dmimage.h"
#include "dmtext.h"
#include "dmres.h"

char    *optInFilename = NULL, *optOutFilename = NULL;

int     optSplitWidth = 8,
        optSplitHeight = 8,
        optBPP = 32;

SDL_Color optColor = { 255, 255, 255, 100 };


static const DMOptArg optList[] =
{
    {  0, '?', "help",     "Show this help", OPT_NONE },
    {  1, 'v', "verbose",  "Be more verbose", OPT_NONE },
    {  2, 's', "size",     "Set glyph dimensions (-s W:H or -s N) for image->font conversion", OPT_ARGREQ },
#ifdef DM_GFX_TTF_TEXT
    {  3, 'c', "color",    "TTF font rendering color (def: 0xFFFFFF)", OPT_ARGREQ },
    {  4, 'b', "bpp",      "Render font in 8 or 32 bits per pixel (default 32)", OPT_ARGREQ },
#endif
};

const int optListN = sizeof(optList) / sizeof(optList[0]);


BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
{
    switch (optN)
    {
        case 0:
            dmPrintBanner(stdout, dmProgName,
                "[options] <sourcefile.(ttf|fnt|dmf|png)> <outputfile.dmf>");

            dmArgsPrintHelp(stdout, optList, optListN, 0);
            printf(
            "\n"
            "This utility can be used to convert TSFONT files to bitmap DMFONT (DMF)\n"
            "files, render TrueType TTF to DMFONT at desired glyph resolution, or\n"
            "cut a PNG (or JPEG) image to glyphs of desired size.\n");

            exit(0);
            break;

        case 1:
            dmVerbosity++;
            break;

        case 2:
            {
                unsigned int fontW, fontH;
                char *sep = strchr(optArg, ':');
                if (sep != NULL)
                {
                    char *tmpStr = dm_strndup(optArg, sep - optArg);

                    if (!dmGetIntVal(tmpStr, &fontW, NULL) ||
                        !dmGetIntVal(sep + 1, &fontH, NULL))
                    {
                        dmErrorMsg("Invalid font width or height value ('%s')\n",
                            optArg);

                        dmFree(tmpStr);
                        return FALSE;
                    }

                    dmFree(tmpStr);
                }
                else
                {
                    if (!dmGetIntVal(optArg, &fontW, NULL))
                    {
                        dmErrorMsg("Invalid font size value ('%s')\n",
                            optArg);

                        return FALSE;
                    }
                    fontH = fontW;
                }

                if (fontW < DMFONT_MIN_WIDTH  || fontW > DMFONT_MAX_WIDTH ||
                    fontH < DMFONT_MIN_HEIGHT || fontH > DMFONT_MAX_HEIGHT)
                {
                    dmErrorMsg("Invalid font dimensions, must be %d < W %d, %d < H < %d.\n",
                        DMFONT_MIN_WIDTH  , DMFONT_MAX_WIDTH,
                        DMFONT_MIN_HEIGHT , DMFONT_MAX_HEIGHT);
                    return FALSE;
                }
                optSplitWidth = fontW;
                optSplitHeight = fontH;
            }
            break;

        case 3:
            {
                unsigned int colR, colG, colB, colA = 100;
                if (optArg[0] == '#' || optArg[0] == '$') optArg++;
                else
                if (optArg[0] == '0' && optArg[1] == 'x') optArg += 2;

                if (sscanf(optArg, "%02x%02x%02x", &colR, &colG, &colB) != 3 &&
                    sscanf(optArg, "%02x%02x%02x%02x", &colR, &colG, &colB, &colA) != 4)
                {
                    dmErrorMsg("Invalid RGB hex representation '%s'.\n",
                        optArg);
                    return FALSE;
                }

                optColor.r = colR;
                optColor.g = colG;
                optColor.b = colB;
                optColor.a = colA;
            }
            break;

        case 4:
            if (sscanf(optArg, "%d", &optBPP) != 1)
            {
                dmErrorMsg("Invalid argument for -b option, '%s'.\n",
                    optArg);
                return FALSE;
            }
            if (optBPP != 8 && optBPP != 32)
            {
                dmErrorMsg("Invalid bit depth %d, must be 8 or 32.\n",
                    optBPP);
                return FALSE;
            }
            break;

        default:
            dmErrorMsg("Unimplemented option argument '%s'.\n", currArg);
            return FALSE;
    }

    return TRUE;
}


BOOL argHandleFile(char *currArg)
{
    if (optInFilename == NULL)
        optInFilename = currArg;
    else
    if (optOutFilename == NULL)
        optOutFilename = currArg;
    else
    {
        dmErrorMsg("Too many filename arguments, '%s'\n", currArg);
        return FALSE;
    }

    return TRUE;
}


int dmCreateBitmapFontFromImage(SDL_Surface *image, int width, int height, DMBitmapFont **pfont)
{
    int nglyph, xglyphs, yglyphs;
    DMBitmapFont *font;

    if (image->w < width || width < 2 ||
        image->h < height || height < 2)
        return DMERR_INVALID_ARGS;

    xglyphs = image->w / width;
    yglyphs = image->h / height;

    if ((font = dmNewBitmapFont(
        xglyphs * yglyphs,
        xglyphs * yglyphs,
        width, height, image->format->BitsPerPixel)) == NULL)
        return DMERR_MALLOC;

    dmMsg(1, "%d x %d split as %d x %d blocks => %d x %d = %d glyphs, bpp=%d.\n",
        image->w, image->h,
        width, height,
        xglyphs, yglyphs,
        xglyphs * yglyphs, image->format->BitsPerPixel);

    nglyph = 0;
    for (int yc = 0; yc < yglyphs; yc++)
    for (int xc = 0; xc < xglyphs; xc++)
    {
        DMBitmapGlyph *glyph = &font->glyphMap[nglyph++];
        SDL_Rect src, dst;

        src.x = xc * width;
        src.y = yc * height;
        dst.w = src.w = width;
        dst.h = src.h = height;

        dst.x = 0;
        dst.y = nglyph * height;
        glyph->index = nglyph;

        SDL_BlitSurface(image, &src, font->glyphs, &dst);
    }

    *pfont = font;
    return DMERR_OK;
}


int dmSaveBitmapFont(DMResource *fp, DMBitmapFont *font)
{
    if (font == NULL)
        return DMERR_NULLPTR;

    if (font->nglyphs > font->maxglyph ||
        font->maxglyph > DMFONT_MAX_GLYPHS ||
        font->width > DMFONT_MAX_WIDTH ||
        font->height > DMFONT_MAX_HEIGHT ||
        font->width < DMFONT_MIN_WIDTH ||
        font->height < DMFONT_MIN_HEIGHT)
        return DMERR_INVALID_DATA;

    // Write the DMFONT header
    if (!dmf_write_str(fp, (Uint8 *) DMFONT_MAGIC, 6) ||
        !dmf_write_le16(fp, DMFONT_VERSION) ||
        !dmf_write_le16(fp, font->nglyphs) ||
        !dmf_write_le16(fp, font->maxglyph) ||
        !dmf_write_byte(fp, font->width) ||
        !dmf_write_byte(fp, font->height) ||
        !dmf_write_byte(fp, font->glyphs->format->BitsPerPixel))
        return DMERR_FWRITE;

    // Write the glyph data
    for (int index = 0; index < font->maxglyph; index++)
    {
        DMBitmapGlyph *glyph = &font->glyphMap[index];
        if (glyph->index >= 0)
        {
            Uint8 *pixels = font->glyphs->pixels + font->gsize * glyph->index;

            // Each glyph has its table index and w/h stored
            if (!dmf_write_le16(fp, index) ||
                !dmf_write_byte(fp, glyph->width) ||
                !dmf_write_byte(fp, glyph->height))
                return DMERR_FWRITE;

            // Write the pixel data
            for (int y = 0; y < glyph->height; y++)
            {
                if (dmfwrite(pixels, font->glyphs->format->BytesPerPixel,
                    glyph->width, fp) != (size_t) glyph->width)
                    return DMERR_FWRITE;

                pixels += font->glyphs->pitch;
            }
        }
    }

    return DMERR_OK;
}


int main(int argc, char *argv[])
{
    DMResource *inFile = NULL, *outFile = NULL;
    DMBitmapFont *font = NULL;
    SDL_Surface *fontbmap = NULL;
    int res;
#ifdef DM_GFX_TTF_TEXT
    BOOL initTTF = FALSE;
    TTF_Font *ttf = NULL;
#endif

    dmInitProg("fontconv", "Bitmap font converter", "0.4", NULL, NULL);

    // Parse arguments
    if (!dmArgsProcess(argc, argv, optList, optListN,
        argHandleOpt, argHandleFile, OPTH_BAILOUT))
        exit(1);

    // Check arguments
    if (optInFilename == NULL || optOutFilename == NULL)
    {
        dmErrorMsg("Input or output file not specified!\n");
        return 1;
    }

#ifdef DM_GFX_TTF_TEXT
    if (TTF_Init() < 0)
    {
        dmErrorMsg("Could not initialize FreeType/TTF: %s\n", SDL_GetError());
        goto exit;
    }
    initTTF = TRUE;
#endif

    // Open the source file
    if ((res = dmf_open_stdio(optInFilename, "rb", &inFile)) != DMERR_OK)
    {
        dmErrorMsg("Error opening input file '%s', %d: %s\n",
            optInFilename, res, dmErrorStr(res));
        goto exit;
    }


    if ((res = dmLoadBitmapFont(inFile, &font)) == DMERR_OK)
    {
        dmMsg(1, "Input is a TSFONT/DMFONT font file, %d x %d, %d glyphs (%d max).\n",
            font->width, font->height, font->nglyphs, font->maxglyph);
    }
    else
    if (res != DMERR_INVALID)
    {
        dmErrorMsg("Input is a TSFONT/DMFONT font file, but there is an error: %s\n",
            dmErrorStr(res));
        goto exit;
    }
#ifdef DM_GFX_TTF_TEXT
    else
    if ((ttf = TTF_OpenFont(optInFilename, optSplitWidth - 1)) != NULL)
    {
        int gmin = 34, gmax = 127;

        dmMsg(1, "Input is a TTF TrueType font, rendering at %d x %d, %d bpp.\n",
            optSplitWidth, optSplitHeight, optBPP);

        dmMsg(1, "Rendering glyph range %d to %d inclusive.\n",
            gmin, gmax);

        TTF_SetFontStyle(ttf, TTF_STYLE_NORMAL);

        // Create the bitmap font
        if ((font = dmNewBitmapFont(gmax - gmin + 1, 256,
            optSplitWidth - 6, optSplitHeight + 2, optBPP)) == NULL)
        {
            dmErrorMsg("Could not allocate bitmap font!\n");
            goto exit;
        }

        // Render glyphs from the normal ASCII range only
        for (int index = 0, nglyph = gmin; nglyph <= gmax; nglyph++)
        {
            SDL_Surface *tmp;
            char str[2];
            str[0] = nglyph;
            str[1] = 0;

            // Render the glyph from TTF to surface
            if (optBPP == 8)
                tmp = TTF_RenderText_Solid(ttf, str, optColor);
            else
                tmp = TTF_RenderText_Blended(ttf, str, optColor);

            if (tmp != NULL)
            {
                DMBitmapGlyph *glyph = &font->glyphMap[nglyph];
                int minx, miny, advance;
                SDL_Rect dst;

                if (TTF_GlyphMetrics(ttf, nglyph, &minx, NULL, &miny, NULL, &advance) == -1)
                {
                    dmErrorMsg("Could not get TTF glyph metrics for character '%c' (%d).\n",
                        nglyph, nglyph);
                    goto exit;
                }

                dst.x = 0;
                dst.y = index * font->height;
                dst.w = font->width;
                dst.h = font->height;

                // Set glyph data
                glyph->width  = font->width;
                glyph->height = font->height;
                glyph->index  = index;

                SDL_BlitSurface(tmp, NULL, font->glyphs, &dst);
                SDL_FreeSurface(tmp);
                index++;
            }
        }
    }
#endif
    else
    {
        dmfreset(inFile);

        if ((fontbmap = dmLoadImage(inFile)) == NULL)
        {
            dmErrorMsg("Could not load image file '%s'.\n", optInFilename);
            goto exit;
        }

        dmMsg(1, "Input is a bitmap image (%d x %d, %d bpp), splitting to %d x %d.\n",
            fontbmap->w, fontbmap->h, fontbmap->format->BitsPerPixel,
            optSplitWidth, optSplitHeight);

        if ((res = dmCreateBitmapFontFromImage(fontbmap, optSplitWidth, optSplitHeight, &font)) != DMERR_OK)
        {
            dmErrorMsg("Could not create a font from image, %d: %s\n",
                res, dmErrorStr(res));
            goto exit;
        }
    }

    if (font == NULL)
    {
        dmErrorMsg("No font loaded.\n");
        goto exit;
    }

    // Count number of actually existing glyphs despite that we should have
    // that information in font->nglyphs. Also sanity check the glyphs.
    font->nglyphs = 0;
    for (int n = 0; n < font->maxglyph; n++)
    {
        DMBitmapGlyph *glyph = &font->glyphMap[n];

        if (glyph->index >= 0)
        {
            font->nglyphs++;
            if (glyph->width < DMFONT_MIN_WIDTH ||
                glyph->height < DMFONT_MIN_HEIGHT ||
                glyph->width > DMFONT_MAX_WIDTH ||
                glyph->height > DMFONT_MAX_HEIGHT ||
                glyph->width > font->width ||
                glyph->height > font->height)
            {
                dmErrorMsg("Invalid glyph #%d: %d x %d (font %d x %d)\n",
                    n,
                    glyph->width, glyph->height,
                    font->width, font->height);
                goto exit;
            }
        }
    }

    dmMsg(1, "Outputting a DMFONT format bitmap font, %d x %d with %d glyphs (%d max), %d bpp.\n",
        font->width, font->height,
        font->nglyphs, font->maxglyph,
        font->glyphs->format->BitsPerPixel);

    if ((res = dmf_open_stdio(optOutFilename, "wb", &outFile)) != DMERR_OK)
    {
        dmErrorMsg("Error creating file '%s', %d: %s\n",
            optInFilename, res, dmErrorStr(res));
        goto exit;
    }

    res = dmSaveBitmapFont(outFile, font);
    dmf_close(outFile);

    if (res != DMERR_OK)
    {
        dmErrorMsg("Error saving font, %d: %s\n",
            res, dmErrorStr(res));
    }

exit:
    // Cleanup
#ifdef DM_GFX_TTF_TEXT
    if (initTTF)
        TTF_Quit();
#endif

    dmf_close(inFile);
    dmFreeBitmapFont(font);
    if (fontbmap != NULL)
        SDL_FreeSurface(fontbmap);

    return 0;
}