view fontconv.c @ 510:43ea59887c69

Start work on making C64 formats encoding possible by changing DMDecodeOps to DMEncDecOps and adding fields and op enums for custom encode functions, renaming, etc. Split generic op sanity checking into a separate function in preparation for its use in generic encoding function.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 19 Nov 2012 15:06:01 +0200
parents d6c184800384
children f87446a81887
line wrap: on
line source

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

enum
{
    OFMT_DMFONT,
    OFMT_C
};

char    *optInFilename = NULL, *optOutFilename = NULL,
        *optOutName = NULL;

int     optOutFormat = OFMT_DMFONT,
        optSplitWidth = 8,
        optSplitHeight = 8;


DMOptArg optList[] =
{
    {  0, '?', "help",     "Show this help", OPT_NONE },
    {  1, 'v', "verbose",  "Be more verbose", OPT_NONE },
    {  2, 'o', "output",   "Output file (default stdout)", OPT_ARGREQ },
    {  3, 's', "size",     "Set glyph dimensions (-s WxH) for image->font conversion", OPT_ARGREQ },
    {  4, 'C', "csource",  "DMFONT as C source", OPT_NONE },
    {  5, 'n', "name",     "Variable name prefix for C output", OPT_ARGREQ },
};

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]");
                
            dmArgsPrintHelp(stdout, optList, optListN);
            exit(0);
            break;

        case 1:
            dmVerbosity++;
            break;

        case 2:
            optOutFilename = optArg;
            break;

        case 3:
            {
                int w, h;
                if (sscanf(optArg, "%dx%d", &w, &h) != 2)
                {
                    dmError("Invalid argument for -s option, '%s'.\n",
                        optArg);
                    return FALSE;
                }
                if (w < DMFONT_MIN_WIDTH  || w > DMFONT_MAX_WIDTH ||
                    h < DMFONT_MIN_HEIGHT || h > DMFONT_MAX_HEIGHT)
                {
                    dmError("Invalid 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 = w;
                optSplitHeight = h;
            }
            break;

        case 4:
            optOutFormat = OFMT_C;
            break;

        case 5:
            optOutName = optArg;
            break;

        default:
            dmError("Unknown argument '%s'.\n", currArg);
            return FALSE;
    }
    
    return TRUE;
}


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


static int dm_csrc_ferror(DMResource * f)
{
    return f->error;
}


static int dm_csrc_fputc(int v, DMResource * f)
{
    if (f->dataSize++ >= 16)
    {
        fprintf(f->fh, "\n");
        f->dataSize = 0;
    }
    fprintf(f->fh, "%3d,", v);
    f->error = dmGetErrno();
    return 1;
}


static size_t dm_csrc_fwrite(void *ptr, size_t size, size_t nmemb, DMResource * f)
{
    size_t n;
    Uint8 *p = (Uint8 *) ptr;
    for (n = 0; n < size * nmemb; n++, p++)
    {
        if (f->dataSize++ >= 16)
        {
            fprintf(f->fh, "\n");
            f->dataSize = 0;
        }
        fprintf(f->fh, "%3d,", *p);
    }
    f->error = dmGetErrno();
    return nmemb;
}


static int dm_csrc_fopen(DMResource * f)
{
    fprintf(f->fh,
    "const Uint8 %s[] = {\n", (char *) f->data
    );
    return DMERR_OK;
}


static void dm_csrc_fclose(DMResource * f)
{
    if (f->fh != NULL)
    {
        fprintf(f->fh,
        "\n};\n"
        "const unsigned int %s_size = sizeof(%s) / sizeof(%s[0]);\n",
        (char *)f->data, (char *)f->data, (char *)f->data
        );
        fclose(f->fh);
        f->fh = NULL;
    }
}


DMResourceOps dfCSourceFileOps =
{
    dm_csrc_ferror,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    dm_csrc_fputc,
    NULL,
    dm_csrc_fwrite,

    dm_csrc_fopen,
    dm_csrc_fclose,
    NULL
};


DMResource * dmf_create_csrc(const char *filename, const char *name)
{
    DMResource *handle = dmres_new(NULL, filename, 0, 0);
    if (handle == NULL)
        return NULL;

    handle->fops = &dfCSourceFileOps;

    handle->fh = fopen(filename, "w");
    handle->error = dmGetErrno();
    handle->data = (Uint8 *) dm_strdup(name);
    
    if (handle->fh != NULL)
    {
        handle->fops->fopen(handle);
        dmres_ref(handle);
        return handle;
    }
    else
    {
        dmres_free(handle);
        return NULL;
    }
}


DMResource * dmf_create_csrc_stream(FILE *fh, const char *name)
{
    DMResource *handle = dmres_new(NULL, NULL, 0, 0);
    if (handle == NULL)
        return NULL;

    handle->fops = &dfCSourceFileOps;

    handle->fh = fh;
    handle->error = dmGetErrno();
    handle->data = (Uint8 *) dm_strdup(name);
    handle->fops->fopen(handle);
    dmres_ref(handle);
    return handle;
}


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

    if (image->w < width || width < 4 || image->h < height || height < 4)
        return DMERR_INVALID_ARGS;
    
    xglyphs = image->w / width;
    yglyphs = image->h / height;
    
    if ((font = dmNewBitmapFont(xglyphs * yglyphs, width, height)) == NULL)
        return DMERR_MALLOC;

    dmMsg(1, "%d x %d split as %d x %d blocks => %d x %d = %d glyphs.\n",
        image->w, image->h,
        width, height,
        xglyphs, yglyphs, xglyphs * yglyphs);
    
    nglyph = 0;
    for (yc = 0; yc < yglyphs; yc++)
    for (xc = 0; xc < xglyphs; xc++)
    {
        SDL_Surface *glyph = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height,
            image->format->BitsPerPixel,
            image->format->Rmask,
            image->format->Gmask,
            image->format->Bmask,
            image->format->Amask);
        
        if (glyph == NULL)
        {
            dmFreeBitmapFont(font);
            return DMERR_MALLOC;
        }

        SDL_Rect r;
        r.x = xc * width;
        r.y = yc * height;
        r.w = width;
        r.h = height;
        
        SDL_BlitSurface(image, &r, glyph, NULL);
        
        font->glyphs[nglyph++] = glyph;
    }
    
    *pfont = font;
    return DMERR_OK;
}


int dmSaveBitmapFont(DMResource *res, DMBitmapFont *font)
{
    int maxglyph, nglyphs, n;
    if (font == NULL)
        return DMERR_NULLPTR;

    if (font->nglyphs > 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;

    // Count number of actually existing glyphs
    for (maxglyph = nglyphs = n = 0; n < font->nglyphs; n++)
    {
        SDL_Surface *glyph = font->glyphs[n];
        if (glyph != NULL)
        {
            maxglyph = n;
            if (glyph->w < DMFONT_MIN_WIDTH ||
                glyph->h < DMFONT_MIN_HEIGHT ||
                glyph->w > DMFONT_MAX_WIDTH ||
                glyph->h > DMFONT_MAX_HEIGHT)
                continue;
            nglyphs++;
        }
    }
    
    // Write the DMFONT header
    if (!dmf_write_str(res, (Uint8 *) DMFONT_MAGIC, 6))
        return DMERR_FWRITE;

    dmf_write_le16(res, DMFONT_VERSION);
    dmf_write_le16(res, nglyphs);
    dmf_write_le16(res, maxglyph + 1);
    dmfputc(font->width, res);
    dmfputc(font->height, res);
    
    if (nglyphs > 0)
    {
        int i;
        SDL_Surface *glyph = font->glyphs[maxglyph];

        // If there are actual glyphs stored, save this
        dmfputc(glyph->format->BitsPerPixel, res);
        dmf_write_le32(res, glyph->format->Rmask);
        dmf_write_le32(res, glyph->format->Gmask);
        dmf_write_le32(res, glyph->format->Bmask);
        dmf_write_le32(res, glyph->format->Amask);

        for (i = 0; i < font->nglyphs; i++)
        {
            glyph = font->glyphs[i];
            if (glyph != NULL)
            {
                int y;
                Uint8 *pixels = glyph->pixels;
                
                if (glyph->w < DMFONT_MIN_WIDTH ||
                    glyph->h < DMFONT_MIN_HEIGHT ||
                    glyph->w > DMFONT_MAX_WIDTH ||
                    glyph->h > DMFONT_MAX_HEIGHT)
                    continue;

                // Each glyph has its table index and w/h stored
                dmf_write_le16(res, i);
                dmfputc(glyph->w, res);
                dmfputc(glyph->h, res);

                // Write the pixel data
                for (y = 0; y < glyph->h; y++)
                {
                    if (dmfwrite(pixels, glyph->format->BytesPerPixel, glyph->w, res) != (size_t) glyph->w)
                        return DMERR_FWRITE;
                    pixels += glyph->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.2", NULL, NULL);
    dmVerbosity = 1;

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

    // Check arguments
    if (!optInFilename)
    {
        dmError("Input or output file not specified!\n");
        return 1;
    }

#ifdef DM_GFX_TTF_TEXT
    if (TTF_Init() < 0)
    {
        dmError("Could not initialize FreeType/TTF: %s\n", SDL_GetError());
        goto error_exit;
    }
    initTTF = TRUE;
#endif
    
    // Open the source file
    if ((inFile = dmf_create_stdio(optInFilename, "rb")) == NULL)
    {
        dmError("Error opening input file '%s', %d: %s\n",
            optInFilename, errno, strerror(errno));
        return 1;
    }


    if ((res = dmLoadBitmapFont(inFile, &font)) == DMERR_OK)
    {
        dmMsg(1, "Input is a TSFONT/DMFONT font file.\n");
    }
#ifdef DM_GFX_TTF_TEXT
    else
    if ((ttf = TTF_OpenFont(optInFilename, optSplitWidth)) != NULL)
    {
        int i;
        SDL_Color col = { 255, 255, 255, 100 };
        dmMsg(1, "Input is a TTF TrueType font, rendering at %d x %d.\n",
            optSplitWidth, optSplitHeight);

        TTF_SetFontStyle(ttf, TTF_STYLE_NORMAL);
        
        if ((font = dmNewBitmapFont(256, optSplitWidth - 1, optSplitHeight+4)) == NULL)
        {
            goto error_exit;
        }

        for (i = 0; i < 255; i++)
        {
            char str[2];
            str[0] = i;
            str[1] = 0;
            font->glyphs[i] = TTF_RenderText_Blended(ttf, str, col);
        }
    }
#endif
    else
    {
        dmfseek(inFile, 0L, SEEK_SET);

        if ((fontbmap = dmLoadImage(inFile)) == NULL)
        {
            dmError("Could not load image file '%s'.\n", optInFilename);
            goto error_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)
        {
            dmError("Could not create a font from image, %d: %s\n",
                res, dmErrorStr(res));
            goto error_exit;
        }
    }

    if (font == NULL)
    {
        dmError("No font loaded.\n");
        goto error_exit;
    }
    
    if (optOutFormat == OFMT_DMFONT)
    {
        dmMsg(1, "Outputting a DMFONT format bitmap font.\n");
        if (optOutFilename == NULL)
            outFile = dmf_create_stdio_stream(stdout);
        else
            outFile = dmf_create_stdio(optOutFilename, "wb");
    }
    else
    if (optOutFormat == OFMT_C)
    {
        if (optOutName == NULL)
        {
            dmError("C source output selected, but variable name not specified.\n");
            goto error_exit;
        }

        dmMsg(1, "Outputting a DMFONT format bitmap font as C source file.\n");

        if (optOutFilename == NULL)
            outFile = dmf_create_csrc_stream(stdout, optOutName);
        else
            outFile = dmf_create_csrc(optOutFilename, optOutName);
    }

    if (outFile == NULL)
    {
        dmError("Error creating file '%s', %d: %s\n",
            optInFilename, errno, strerror(errno));
        goto error_exit;
    }

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

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

error_exit:

#ifdef DM_GFX_TTF_TEXT
    if (initTTF)
        TTF_Quit();
#endif
    
    dmf_close(inFile);
    dmFreeBitmapFont(font);
    SDL_FreeSurface(fontbmap);
    
    return 0;
}