view tools/fontconv.c @ 2576:812b16ee49db

I had been living under apparent false impression that "realfft.c" on which the FFT implementation in DMLIB was basically copied from was released in public domain at some point, but it could very well be that it never was. Correct license is (or seems to be) GNU GPL. Thus I removing the code from DMLIB, and profusely apologize to the author, Philip Van Baren. It was never my intention to distribute code based on his original work under a more liberal license than originally intended.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 11 Mar 2022 16:32:50 +0200
parents 92b93a12c014
children 9807ae37ad69
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,   0, "license"         , "Print out this program's license agreement", OPT_NONE },
    {  2, 'v', "verbose"         , "Be more verbose", OPT_NONE },

    { 10, 's', "size"            , "Set glyph dimensions (-s W:H or -s N) for image->font conversion", OPT_ARGREQ },
#ifdef DM_GFX_TTF_TEXT
    { 12, 'c', "color"           , "TTF font rendering color (def: 0xFFFFFF)", OPT_ARGREQ },
    { 14, 'b', "bpp"             , "Render font in 8 or 32 bits per pixel (default 32)", OPT_ARGREQ },
#endif
};

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


void argShowHelp()
{
    dmPrintBanner(stdout, dmProgName,
        "[options] <sourcefile.(ttf|fnt|dmf|png)> <outputfile.dmf>");

    dmArgsPrintHelp(stdout, optList, optListN, 0, 80 - 2);
    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");
}


BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
{
    switch (optN)
    {
        case 0:
            argShowHelp();
            exit(0);
            break;

        case 1:
            dmPrintLicense(stdout);
            exit(0);
            break;

        case 2:
            dmVerbosity++;
            break;

        case 10:
            {
                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 12:
            {
                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 14:
            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 = (Uint8 *) 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 = DMERR_OK;
#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))
        goto out;

    // Check arguments
    if (optInFilename == NULL || optOutFilename == NULL)
    {
        argShowHelp();
        res = dmError(DMERR_INVALID_ARGS,
            "No input or output file specified!\n");
        goto out;
    }

#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': %s\n",
            optInFilename, dmErrorStr(res));
        goto out;
    }


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

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

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

    return res;
}