Include mkbmfont tool.
This commit is contained in:
parent
416505075a
commit
0115015373
1 changed files with 802 additions and 0 deletions
802
mkbmfont.c
Normal file
802
mkbmfont.c
Normal 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue