view tools/fontconv.c @ 2208:90ec1ec89c56

Revamp the palette handling in lib64gfx somewhat, add helper functions to lib64util for handling external palette file options and add support for specifying one of the "internal" palettes or external (.act) palette file to gfxconv and 64vw.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 14 Jun 2019 05:01:12 +0300
parents e3f0eaf23f4f
children 8ca515ab9c84
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 out;
    }
    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));
        return 1;
    }


    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 out;
    }
#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 out;
        }

        // 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 out;
                }

                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 out;
        }

        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 out;
        }
    }

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

    // 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 out;
            }
        }
    }

    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 out;
    }

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

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

out:

#ifdef DM_GFX_TTF_TEXT
    if (initTTF)
        TTF_Quit();
#endif

    dmf_close(inFile);
    dmFreeBitmapFont(font);
    SDL_FreeSurface(fontbmap);

    return 0;
}