Include mkbmfont tool.

This commit is contained in:
Marisa the Magician 2026-03-06 14:23:08 +01:00
commit 0115015373

802
mkbmfont.c Normal file
View file

@ -0,0 +1,802 @@
/*
mkbmfont.c : Like mkfont but the output is in BMFont format.
The code is rather messy, since it's a tool meant for personal use,
apologies in advance.
Copyright (c) 2026 Marisa the Magician, UnSX Team
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 THE AUTHORS OR COPYRIGHT HOLDERS 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.
*/
/*
Known bugs/limitations/quirks:
- Unlike my old mkfont tool, proportional fonts are handled just fine,
and non-bitmap font handling "just works". Unicode blocks beyond the
BMP are also covered too, funny enough.
- Packing algorithm for the texture atlas is very naive and may not
offer the finest density, but at least it's fast and dead simple to
implement in plain C.
- Does not recognize visually identical glyphs that share the same code
point, so there will be potential duplicates in the atlas.
- Compiling with -DASCII_ONLY is required if you want to use this with
Godot, as it doesn't support unicode bitmap fonts for some reason.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <png.h>
#include <ft2build.h>
#include FT_FREETYPE_H
typedef struct
{
int16_t fontSize;
uint8_t bitField;
uint8_t charSet;
uint16_t stretchH;
uint8_t aa, paddingUp, paddingRight, paddingDown, paddingLeft, spacingHoriz, spacingVert, outline;
} __attribute__((packed)) bmfinfo_t;
typedef struct
{
uint16_t lineHeight, base, scaleW, scaleH, pages;
uint8_t bitField, alphaChnl, redChnl, greenChnl, blueChnl;
} __attribute__((packed)) bmfcommon_t;
typedef struct
{
uint32_t id;
uint16_t x, y, width, height;
int16_t xoffset, yoffset, xadvance;
uint8_t page, chnl;
} __attribute__((packed)) bmfchar_t;
typedef struct
{
uint32_t unicode, glyph;
uint16_t x, y, w, h;
int16_t xofs, yofs, xadv;
uint8_t page, packed;
// channels not needed (always defaults to 15)
} packglyph_t;
// BEGIN naive packing algo
static int cmpglyph( const void *p1, const void *p2 )
{
// cast for convenience
packglyph_t *a = (packglyph_t*)p1,
*b = (packglyph_t*)p2;
// check which is taller first
int hdiff = b->h-a->h;
if ( hdiff ) return hdiff;
// if equal height, check which is wider
int wdiff = b->w-a->w;
if ( wdiff ) return wdiff;
// equal height and width, just sort by unicode value
return a->unicode-b->unicode;
}
static void nextbox( int* boxw, int* boxh )
{
// increase width before height
if ( *boxw > *boxh ) *boxh <<= 1;
else *boxw <<= 1;
}
// some sane defaults here
#ifndef PACK_TRIES
#define PACK_TRIES 100
#endif
#ifndef MAX_PACK_SIDE
#define MAX_PACK_SIDE 1024
#endif
#ifndef MAX_PAGES
#define MAX_PAGES 36
#endif
int packglyphs( packglyph_t *glyphs, uint32_t nglyphs, int* boxw, int* boxh )
{
// sort the glyphs first
qsort(glyphs,nglyphs,sizeof(packglyph_t),cmpglyph);
int pageno = 0;
size_t packed = 0;
for ( int i=0; i<PACK_TRIES; i++ )
{
int x = 0;
int y = 0;
int lh = 0;
for ( uint32_t j=0; j<nglyphs; j++ )
{
if ( glyphs[j].packed ) continue;
// past box width, next row
if ( (x+glyphs[j].w+1) > *boxw )
{
y += lh;
x = 0;
lh = 0;
}
// past box height, bail out
if ( y+glyphs[j].h+1 > *boxh ) break;
// otherwise pack it and advance
glyphs[j].x = x;
glyphs[j].y = y;
x += glyphs[j].w+1;
// save tallest in row
if ( (glyphs[j].h+1) > lh ) lh = glyphs[j].h+1;
glyphs[j].page = pageno;
glyphs[j].packed = 1;
packed++;
}
if ( packed == nglyphs ) return 0;
// box is already at size limit, make a new page
if ( (*boxw >= MAX_PACK_SIDE) && (*boxh >= MAX_PACK_SIDE) )
{
pageno++;
// if we're past the page limit, fail early
if ( pageno >= MAX_PAGES ) return 2;
continue;
}
// try again from scratch with a larger box
packed = 0;
for ( uint32_t j=0; j<nglyphs; j++ )
glyphs[j].packed = 0;
nextbox(boxw,boxh);
}
// failed to pack
return 1;
}
// what could be improved: for optimal column coverage, we could keep looping
// until we find a glyph whose width fits the space before moving on to the
// next row. though this would require storing a bit more data inside the
// struct itself, and would increase the complexity of the inner loop
// (potentially taking even longer to execute, especially if multiple retries
// are needed until we find the optimal box size)
// END naive packing algo
uint8_t pal[768];
int palsize = 0;
int aw = 64, ah = 64; // generous default?
uint8_t *adata = 0;
// dead simple png writing, with raw data, width, height and row pitch
int writepng( const char *filename, uint8_t *fdata, int w, int h, int p )
{
if ( !filename ) return 0;
png_structp pngp;
png_infop infp;
FILE *pf;
if ( !(pf = fopen(filename,"wb")) ) return 0;
pngp = png_create_write_struct(PNG_LIBPNG_VER_STRING,0,0,0);
if ( !pngp )
{
fclose(pf);
return 0;
}
infp = png_create_info_struct(pngp);
if ( !infp )
{
fclose(pf);
png_destroy_write_struct(&pngp,0);
return 0;
}
if ( setjmp(png_jmpbuf(pngp)) )
{
png_destroy_write_struct(&pngp,&infp);
fclose(pf);
return 0;
}
png_init_io(pngp,pf);
png_set_IHDR(pngp,infp,w,h,8,PNG_COLOR_TYPE_RGBA,
PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);
png_write_info(pngp,infp);
for ( int i=0; i<h; i++ ) png_write_row(pngp,fdata+(p*i));
png_write_end(pngp,infp);
png_destroy_write_struct(&pngp,&infp);
fclose(pf);
return 1;
}
FT_Library ftlib;
FT_Face fnt;
int lh, bl;
int w = 0, h = 0;
uint8_t *idata = 0;
int calcsize = 1;
int gradient = 0;
int gw = 0, gh = 0, gx = 0, gy = 0;
// dead simple, we don't need to check if we draw out of bounds or anything
// as those are guaranteed to never be the case once glyphs are successfully
// packed into the atlas texture
int oob = 0;
void blitglyph( packglyph_t *g )
{
if ( (g->x < 0) || (g->x >= (aw-g->w)) || (g->y < 0) || (g->y >= (ah-g->h)) )
{
oob = 1;
return;
}
uint8_t *gpos = idata;
uint8_t *apos = adata+(g->x+g->y*aw)*4;
for ( int i=0; i<(g->h); i++ )
{
memcpy(apos,gpos,(g->w)*4);
gpos += w*4;
apos += aw*4;
}
}
void putpixel_grayscale( uint8_t v, uint8_t a, int x, int y )
{
uint32_t tpos = (x+y*w)*4;
// add alpha
int alph = idata[tpos+3];
alph += a;
if ( alph > 255 ) alph = 255;
idata[tpos+3] = alph;
// blend color
int col = idata[tpos]*(a-255);
col += v*a;
col /= 255;
idata[tpos] = col;
idata[tpos+1] = col;
idata[tpos+2] = col;
}
void putpixel_color( uint8_t v, uint8_t a, int x, int y )
{
uint32_t tpos = (x+y*w)*4;
// add alpha
int alph = idata[tpos+3];
alph += a;
if ( alph > 255 ) alph = 255;
idata[tpos+3] = alph;
// blend color (RGB)
for ( int i=0; i<3; i++ )
{
int col = idata[tpos+i]*(a-255);
int palent = (v*palsize)/256;
col += pal[palent*3+i]*a;
col /= 255;
idata[tpos+i] = col;
}
}
void putpixel( uint8_t v, uint8_t a, int x, int y )
{
if ( (x < 0) || (x >= w) || (y < 0) || (y >= h) )
{
oob = 1;
return;
}
if ( palsize == 0 ) putpixel_grayscale(v,a,x,y);
else putpixel_color(v,a,x,y);
}
uint8_t lerpg( float a )
{
if ( a >= 1. ) return 255;
if ( a <= 0. ) return 64;
return (uint8_t)(a*191+64);
}
int draw_glyph( FT_Bitmap *bmp, uint8_t v, int px, int py, int oy )
{
if ( !bmp->buffer ) return 0;
int drawn = 0;
unsigned i, j;
for ( j=0; j<bmp->rows; j++ )
{
uint8_t rv = v;
// apply gradient, if any
if ( v == 255 )
{
float a;
int ofs = j+oy;
if ( ofs < 0 ) a = 0.;
else a = ofs/(float)(h-gh);
if ( (gradient&3) == 1 ) rv = lerpg(1.-a);
else if ( (gradient&3) == 2 ) rv = lerpg(a);
else if ( (gradient&3) == 3 ) rv = lerpg((a>.5)?((1.-a)*2.):(a*2.));
}
for ( i=0; i<bmp->width; i++ )
{
if ( bmp->pixel_mode == FT_PIXEL_MODE_GRAY )
{
uint8_t a = bmp->buffer[i+j*bmp->pitch];
drawn |= (a > 0);
putpixel(rv,a,i+px,j+py);
}
else if ( bmp->pixel_mode == FT_PIXEL_MODE_MONO )
{
// thanks to https://stackoverflow.com/a/14905971
unsigned p = bmp->pitch;
uint8_t *row = &bmp->buffer[p*j];
uint8_t a = ((row[i>>3])&(128>>(i&7)))?255:0;
drawn |= (a > 0);
putpixel(rv,a,i+px,j+py);
}
}
}
return drawn;
}
int palinv = 0;
void loadpalette( const char *path )
{
FILE *f = fopen(path,"rb");
if ( !f )
{
fprintf(stderr,"warning: could not open palette file '%s', falling back to grayscale\n",path);
return;
}
fseek(f,0,SEEK_END);
long sz = ftell(f);
fseek(f,0,SEEK_SET);
if ( sz <= 0 )
{
fprintf(stderr,"warning: palette is empty, falling back to grayscale\n");
goto palend;
}
if ( !(sz%3) )
{
// RGB8 palette
if ( sz > 768 )
{
fprintf(stderr,"warning: palette has more than 256 entries, extra colors will be ignored\n");
palsize = 256;
}
else palsize = sz/3;
for ( int i=0; i<palsize; i++ )
{
int j = palinv?(palsize-(i+1)):i;
pal[j*3] = fgetc(f);
pal[j*3+1] = fgetc(f);
pal[j*3+2] = fgetc(f);
}
fprintf(stderr,"info: RGB8 palette loaded with %d colors.\n",palsize);
}
else if ( !(sz%4) )
{
// RGBA8 palette
if ( sz > 1024 )
{
fprintf(stderr,"warning: palette has more than 256 entries, extra colors will be ignored\n");
palsize = 256;
}
else palsize = sz/4;
for ( int i=0; i<palsize; i++ )
{
int j = palinv?(palsize-(i+1)):i;
pal[j*3] = fgetc(f);
pal[j*3+1] = fgetc(f);
pal[j*3+2] = fgetc(f);
fgetc(f); // skip alpha
}
fprintf(stderr,"info: RGBA8 palette loaded with %d colors (alpha will be ignored).\n",palsize);
}
else fprintf(stderr,"warning: palette is in an unsupported format, falling back to grayscale\n");
palend:
fclose(f);
}
const char grads[12][20] =
{
"Flat (Shadow)",
"Top-Bottom (Shadow)",
"Bottom-Top (Shadow)",
"Centered (Shadow)",
"Flat (Border)",
"Top-Bottom (Border)",
"Bottom-Top (Border)",
"Centered (Border)",
"Flat",
"Top-Bottom",
"Bottom-Top",
"Centered"
};
// grad stuff: shadow adds 1 to glyph width and height
// border adds 2 to glyph width and height
// and subtracts 1 from glyph x/y offsets
// ez stuff...
#define LOADFLAGS FT_LOAD_PEDANTIC
#define RENDERMODE FT_RENDER_MODE_NORMAL
#ifdef ASCII_ONLY
// only 00-FF, which Godot should expect for bitmap fonts
#define MAX_GLYPHS 256
#define MAX_UNICODE 0xFF
#else
// values as of Unicode 17.0
#define MAX_GLYPHS 303808
#define MAX_UNICODE 0x10FFFF
#endif
static int cmpglyph_final( const void *p1, const void *p2 )
{
// cast for convenience
packglyph_t *a = (packglyph_t*)p1,
*b = (packglyph_t*)p2;
// sort from lowest to highest page number first
if ( a->page != b->page )
return a->page-b->page;
// sort from lowest to highest unicode value second
return a->unicode-b->unicode;
}
// used for naming a grand total of 36 potential pages
char base36( uint8_t val )
{
if ( val < 10 ) return '0'+val;
else return 'A'+(val-10);
}
int main( int argc, char **argv )
{
if ( argc < 4 )
{
fprintf(stderr,"usage: mkbmfont <font name>[:face index] <pxsize> <bmfont name>"
" [gradient type (0,11)] [color palette] [-palinv]\n");
return 1;
}
if ( FT_Init_FreeType(&ftlib) )
{
fprintf(stderr,"error: failed to init freetype library\n");
return 2;
}
int pxsiz;
sscanf(argv[2],"%d",&pxsiz);
char bmname[256], pname[256];
snprintf(bmname,256,"%s.fnt",argv[3]);
if ( argc > 4 )
{
sscanf(argv[4],"%d",&gradient);
if ( (gradient < 0) || (gradient >= 12) )
{
fprintf(stderr,"warning: gradient type out of range, falling back to type 0.\n");
gradient = 0;
}
}
if ( !(gradient&8) )
{
gw++;
gh++;
if ( gradient&4 )
{
gx--;
gy--;
gw++;
gh++;
}
}
fprintf(stderr,"info: gradient selected is '%s'.\n",grads[gradient]);
if ( argc > 6 ) palinv = !strcmp(argv[6],"-palinv");
if ( argc > 5 ) loadpalette(argv[5]);
char *fidx = strrchr(argv[1],':');
int fnum = 0;
if ( fidx )
{
*fidx = 0;
sscanf(fidx+1,"%d",&fnum);
}
if ( FT_New_Face(ftlib,argv[1],fnum,&fnt) )
{
if ( fidx ) fprintf(stderr,"error: failed to open font '%s' face %d.\n",argv[1],fnum);
else fprintf(stderr,"error: failed to open font '%s'.\n",argv[1]);
return 4;
}
fprintf(stderr,"info: loaded font \'%s %s\'.\n",fnt->family_name,fnt->style_name);
int nfnt = fnt->num_faces;
if ( !fidx && (nfnt > 1) )
{
fprintf(stderr,"info: loaded font has %d faces, you may want to load a specific one (see below).\n",nfnt);
FT_Done_Face(fnt);
for ( int i=0; i<nfnt; i++ )
{
if ( FT_New_Face(ftlib,argv[1],i,&fnt) )
{
fprintf(stderr,"error: failed to open font '%s' face %d. Skipping.\n",argv[1],i);
continue;
}
fprintf(stderr,"info: face %d: \'%s %s\'.\n",i,fnt->family_name,fnt->style_name);
FT_Done_Face(fnt);
}
FT_Done_FreeType(ftlib);
return 0;
}
if ( FT_Set_Pixel_Sizes(fnt,0,pxsiz) )
{
if ( fnt->num_fixed_sizes <= 0 )
{
fprintf(stderr,"error: font pixel size of '%d' not available.\n",pxsiz);
return 8;
}
fprintf(stderr,"warning: failed to set pixel size '%d', trying fixed sizes.\n",pxsiz);
int match = -1;
for ( int i=0; i<fnt->num_fixed_sizes; i++ )
{
if ( fnt->available_sizes[i].height == pxsiz ) continue;
match = i;
break;
}
if ( (match == -1) || FT_Select_Size(fnt,match) )
{
fprintf(stderr,"error: font fixed size of '%d' not available\n",pxsiz);
fprintf(stderr,"available size(s): ");
for ( int i=0; i<fnt->num_fixed_sizes; i++ )
fprintf(stderr,"%u%s",fnt->available_sizes[i].height,(i<(fnt->num_fixed_sizes-1))?", ":".\n");
FT_Done_Face(fnt);
FT_Done_FreeType(ftlib);
return 8;
}
}
lh = (fnt->size->metrics.height>>6);
FT_Select_Charmap(fnt,FT_ENCODING_UNICODE);
// pre-allocate
uint32_t nglyphs = MAX_GLYPHS;
packglyph_t* glyphs = calloc(nglyphs,sizeof(packglyph_t));
uint32_t cur = 0;
// count up and populate valid glyphs
for ( uint32_t i=0; i<=MAX_UNICODE; i++ )
{
FT_UInt glyph = FT_Get_Char_Index(fnt,i);
if ( !glyph || FT_Load_Glyph(fnt,glyph,LOADFLAGS) ) continue;
FT_Render_Glyph(fnt->glyph,RENDERMODE);
glyphs[cur].unicode = i;
glyphs[cur].glyph = glyph;
glyphs[cur].x = 0;
glyphs[cur].y = 0;
if ( !fnt->glyph->bitmap.buffer )
{
glyphs[cur].w = 0;
glyphs[cur].h = 0;
glyphs[cur].xofs = 0;
glyphs[cur].yofs = 0;
}
else
{
glyphs[cur].w = fnt->glyph->bitmap.width+gw;
glyphs[cur].h = fnt->glyph->bitmap.rows+gh;
glyphs[cur].xofs = fnt->glyph->bitmap_left+gx;
glyphs[cur].yofs = (-fnt->glyph->bitmap_top)+gy;
}
glyphs[cur].xadv = fnt->glyph->advance.x>>6;
// calculate baseline using 'n' as reference
if ( i == 'n' ) bl = pxsiz-(-fnt->glyph->bitmap_top+fnt->glyph->bitmap.rows);
cur++;
if ( cur > nglyphs )
{
fprintf(stderr,"warning: font somehow covers more code points than expected\n");
break;
}
}
if ( cur <= 0 )
{
fprintf(stderr,"error: no valid glyphs in font.\n");
FT_Done_Face(fnt);
FT_Done_FreeType(ftlib);
return 16;
}
// reduce array size if smaller, for convenience
if ( nglyphs > cur )
{
nglyphs = cur;
glyphs = realloc(glyphs,nglyphs*sizeof(packglyph_t));
}
// update offsets to account for baseline
for ( uint32_t i=0; i<nglyphs; i++ )
{
// skip glyphs with no visual
if ( !(glyphs[i].w|glyphs[i].h) ) continue;
glyphs[i].yofs += bl;
}
fprintf(stderr,"info: total glyphs is %u.\n",nglyphs);
// second pass, compute maximum glyph bitmap size, then allocate the drawing data
for ( uint32_t i=0; i<nglyphs; i++ )
{
if ( glyphs[i].w > w ) w = glyphs[i].w;
if ( glyphs[i].h > h ) h = glyphs[i].h;
}
fprintf(stderr,"info: max cell size is %dx%d.\n",w,h);
idata = calloc(4,w*h);
// third pass, pack, allocate atlas, then draw
int retval = packglyphs(glyphs,nglyphs,&aw,&ah);
if ( retval )
{
char errormsg[][32] = {"max tries hit","max pages hit"};
fprintf(stderr,"error: failed to pack glyphs into an adequate atlas: %s.\n",errormsg[retval-1]);
free(idata);
FT_Done_Face(fnt);
FT_Done_FreeType(ftlib);
return 32;
}
// re-sort
qsort(glyphs,nglyphs,sizeof(packglyph_t),cmpglyph_final);
adata = calloc(4,aw*ah);
int npages = 1;
for ( uint32_t i=0; i<nglyphs; i++ )
{
if ( !glyphs[i].glyph || FT_Load_Glyph(fnt,glyphs[i].glyph,LOADFLAGS) ) continue;
FT_Render_Glyph(fnt->glyph,RENDERMODE);
if ( !fnt->glyph->bitmap.buffer ) continue;
int oy = (h-gh) - (2+fnt->glyph->bitmap_top);
if ( gradient&8 )
draw_glyph(&fnt->glyph->bitmap,255,0,0,oy);
else if ( gradient&4 )
{
// outline first
for ( int x=-1; x<=1; x++ )
for ( int y=-1; y<=1; y++ )
{
// ignore center
if ( !(x|y) ) continue;
draw_glyph(&fnt->glyph->bitmap,0,1+x,1+y,oy);
}
draw_glyph(&fnt->glyph->bitmap,255,1,1,oy);
}
else
{
// drop shadow first
draw_glyph(&fnt->glyph->bitmap,0,1,1,oy);
draw_glyph(&fnt->glyph->bitmap,255,0,0,oy);
}
// are we switching pages?
if ( glyphs[i].page >= npages )
{
snprintf(pname,256,"%s%c.png",argv[3],base36(npages-1));
writepng(pname,adata,aw,ah,aw*4);
memset(adata,0,aw*ah*4);
npages++;
}
// blit to canvas
blitglyph(glyphs+i);
// clear for next draw
memset(idata,0,w*h*4);
}
fprintf(stderr,"info: texture atlas size is %ux%u, with %d page(s).\n",aw,ah,npages);
free(idata);
if ( npages == 1 ) snprintf(pname,256,"%s.png",argv[3]);
else snprintf(pname,256,"%s%c.png",argv[3],base36(npages-1));
writepng(pname,adata,aw,ah,aw*4);
// don't forget to write the bmfont file
#ifdef USE_BINARY_BMFONT
FILE *bmf = fopen(bmname,"wb");
if ( !bmf )
{
fprintf(stderr,"error: failed to open '%s' for writing: %s\n",bmname,strerror(errno));
free(adata);
FT_Done_Face(fnt);
FT_Done_FreeType(ftlib);
return 64;
}
uint32_t bsiz = 0;
// header
fputc(66,bmf);
fputc(77,bmf);
fputc(70,bmf);
fputc(3,bmf);
// info
char fontName[256];
snprintf(fontName,256,"%s %s",fnt->family_name,fnt->style_name);
bmfinfo_t info = { pxsiz, 2, 0, 100, 1, 0, 0, 0, 0, 1, 1, 0 };
bsiz = sizeof(bmfinfo_t)+strlen(fontName)+1;
fputc(1,bmf);
fwrite(&bsiz,4,1,bmf);
fwrite(&info,sizeof(bmfinfo_t),1,bmf);
fwrite(fontName,strlen(fontName),1,bmf);
fputc(0,bmf);
// common
bmfcommon_t cmn = { lh, bl, aw, ah, npages, 0, 0, 4, 4, 4 };
bsiz = sizeof(bmfcommon_t);
fputc(2,bmf);
fwrite(&bsiz,4,1,bmf);
fwrite(&cmn,sizeof(bmfcommon_t),1,bmf);
// pages
fputc(3,bmf);
if ( npages == 1 )
{
snprintf(pname,256,"%s.png",argv[3]);
bsiz = strlen(pname)+1;
fwrite(&bsiz,4,1,bmf);
fwrite(pname,strlen(pname),1,bmf);
fputc(0,bmf);
}
else
{
snprintf(pname,256,"%s0.png",argv[3]);
bsiz = (strlen(pname)+1)*npages;
fwrite(&bsiz,4,1,bmf);
for ( int i=0; i<npages; i++ )
{
fwrite(pname,strlen(pname),1,bmf);
fputc(0,bmf);
snprintf(pname,256,"%s%c.png",argv[3],base36(i+1));
}
}
// chars
bmfchar_t *chars = calloc(nglyphs,sizeof(bmfchar_t));
for ( uint32_t i=0; i<nglyphs; i++ )
{
chars[i].id = glyphs[i].unicode;
chars[i].x = glyphs[i].x;
chars[i].y = glyphs[i].y;
chars[i].width = glyphs[i].w;
chars[i].height = glyphs[i].h;
chars[i].xoffset = glyphs[i].xofs;
chars[i].yoffset = glyphs[i].yofs;
chars[i].xadvance = glyphs[i].xadv;
chars[i].page = glyphs[i].page;
chars[i].chnl = 15;
}
fputc(4,bmf);
bsiz = nglyphs*sizeof(bmfchar_t);
fwrite(&bsiz,4,1,bmf);
fwrite(chars,sizeof(bmfchar_t),nglyphs,bmf);
free(chars);
#else
FILE *bmf = fopen(bmname,"w");
if ( !bmf )
{
fprintf(stderr,"error: failed to open '%s' for writing: %s\n",bmname,strerror(errno));
free(adata);
FT_Done_Face(fnt);
FT_Done_FreeType(ftlib);
return 64;
}
// info
fprintf(bmf,"info face=\"%s %s\" size=%u bold=0 italic=0 charset=\"\" unicode=1 stretchH=100 smooth=0 aa=1 padding=0,0,0,0 spacing=1,1 outline=0\n",fnt->family_name,fnt->style_name,pxsiz);
// common
fprintf(bmf,"common lineHeight=%u base=%u scaleW=%u scaleH=%u pages=%d packed=0 alphaChnl=0 redChnl=4 greenChnl=4 blueChnl=4\n",lh,bl,aw,ah,npages);
// pages
if ( npages == 1 )
{
snprintf(pname,256,"%s.png",argv[3]);
fprintf(bmf,"page id=0 file=\"%s\"\n",pname);
}
else for ( int i=0; i<npages; i++ )
{
snprintf(pname,256,"%s%c.png",argv[3],base36(i));
fprintf(bmf,"page id=%d file=\"%s\"\n",i,pname);
}
// chars
fprintf(bmf,"chars count=%u\n",nglyphs);
for ( uint32_t i=0; i<nglyphs; i++ )
fprintf(bmf,"char id=%u x=%u y=%u width=%u height=%u xoffset=%d yoffset=%d xadvance=%d page=%d chnl=15\n",glyphs[i].unicode,glyphs[i].x,glyphs[i].y,glyphs[i].w,glyphs[i].h,glyphs[i].xofs,glyphs[i].yofs,glyphs[i].xadv,glyphs[i].page);
#endif
fclose(bmf);
free(adata);
FT_Done_Face(fnt);
FT_Done_FreeType(ftlib);
return 0;
}