802 lines
21 KiB
C
802 lines
21 KiB
C
/*
|
|
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;
|
|
}
|