typedef struct sImage
{
unsigned w, h;
- char *data;
+ unsigned char *data;
} Image;
typedef struct sGlyph
Image image;
} Font;
+typedef struct sRange
+{
+ unsigned first;
+ unsigned last;
+} Range;
+
typedef int bool;
void usage(void);
int convert_numeric_option(char, int);
-void convert_code_point_range(char, unsigned *, unsigned *);
+void convert_code_point_range(char, Range *);
+unsigned str_to_code_point(const char *, char **);
void convert_size(char, unsigned *, unsigned *);
+void sort_and_compact_ranges(Range *, unsigned *);
+int range_cmp(const void *, const void *);
unsigned round_to_pot(unsigned);
void *alloc_image_data(size_t, size_t);
-int init_font(Font *, FT_Face, unsigned, unsigned, bool);
-int render_grid(Font *, unsigned, unsigned, unsigned, bool);
-int render_packed(Font *, unsigned, unsigned);
+int init_font(Font *, FT_Face, const Range *, unsigned, bool, unsigned);
+int init_glyphs(Font *, FT_Face, const Range *, bool, unsigned);
+int copy_bitmap(const FT_Bitmap *, Image *);
+unsigned sqrti(unsigned);
+unsigned find_distance_to_edge(const Image *, int, int, unsigned);
+int create_distance_field(const FT_Bitmap *, Image *, unsigned, unsigned);
+int render_grid(Font *, unsigned, unsigned, unsigned, bool, bool);
+int render_packed(Font *, unsigned, unsigned, bool);
int save_defs(const char *, const Font *);
int save_png(const char *, const Image *, char);
int main(int argc, char **argv)
{
char *fn;
- unsigned begin = 0;
- unsigned end = 255;
+ Range *ranges = NULL;
+ unsigned n_ranges = 0;
unsigned size = 10;
unsigned cpl = 0;
unsigned cellw = 0;
bool pack = 0;
unsigned margin = 0;
unsigned padding = 1;
+ bool npot = 0;
+ unsigned distfield = 0;
FT_Library freetype;
FT_Face face;
return 1;
}
- while((i = getopt(argc, argv, "r:s:l:c:o:atvh?ed:pim:n:")) != -1)
+ while((i = getopt(argc, argv, "r:s:l:c:o:atvh?ed:pim:n:gf:")) != -1)
{
switch(i)
{
case 'r':
- convert_code_point_range('r', &begin, &end);
+ ranges = (Range *)realloc(ranges, (++n_ranges)*sizeof(Range));
+ convert_code_point_range('r', &ranges[n_ranges-1]);
break;
case 's':
size = convert_numeric_option('s', 1);
case 'n':
padding = convert_numeric_option('n', 0);
break;
+ case 'g':
+ npot = 1;
+ break;
+ case 'f':
+ distfield = convert_numeric_option('f', 1);
+ break;
}
}
if(!strcmp(out_fn, "-"))
printf("Glyphs: %ld\n", face->num_glyphs);
}
- err = FT_Set_Pixel_Sizes(face, 0, size);
+ err = FT_Set_Pixel_Sizes(face, 0, (distfield ? size*distfield : size));
if(err)
{
fprintf(stderr, "Couldn't set size\n");
return 1;
}
+ if(!n_ranges)
+ {
+ ranges = malloc(sizeof(Range));
+ ranges[0].first = 0;
+ ranges[0].last = 255;
+ n_ranges = 1;
+ }
+ else
+ sort_and_compact_ranges(ranges, &n_ranges);
+
font.size = size;
- err = init_font(&font, face, begin, end, autohinter);
+ err = init_font(&font, face, ranges, n_ranges, autohinter, distfield);
if(err)
return 1;
+ if(!font.n_glyphs)
+ {
+ fprintf(stderr, "No glyphs found in the requested range\n");
+ return 1;
+ }
+
if(pack)
- err = render_packed(&font, margin, padding);
+ err = render_packed(&font, margin, padding, npot);
else
- err = render_grid(&font, cellw, cellh, cpl, seq);
+ err = render_grid(&font, cellw, cellh, cpl, seq, npot);
if(err)
return 1;
- if(invert)
+ if(invert || distfield)
{
for(i=0; (unsigned)i<font.image.w*font.image.h; ++i)
font.image.data[i] = 255-font.image.data[i];
}
- err = save_png(out_fn, &font.image, alpha);
+ err = save_png(out_fn, &font.image, (alpha && !distfield));
if(err)
return 1;
free(font.glyphs);
free(font.kerning);
free(font.image.data);
+ free(ranges);
FT_Done_Face(face);
FT_Done_FreeType(freetype);
" -p Pack the glyphs tightly instead of in a grid\n"
" -m Margin around image edges (packed mode only) [0]\n"
" -n Padding between glyphs (packed mode only) [1]\n"
+ " -g Allow non-power-of-two result\n"
+ " -f Create a distance field texture\n"
" -d File name for writing glyph definitions\n"
" -h Print this message\n");
}
return value;
}
-void convert_code_point_range(char opt, unsigned *begin, unsigned *end)
+void convert_code_point_range(char opt, Range *range)
{
int value;
char *ptr;
if(!strcmp(optarg, "all"))
{
- *begin = 0;
- *end = 0x10FFFF;
+ range->first = 0;
+ range->last = 0x10FFFF;
return;
}
- value = strtol(optarg, &ptr, 0);
+ value = str_to_code_point(optarg, &ptr);
if(value>0 && *ptr==',')
{
- *begin = value;
- value = strtol(ptr+1, &ptr, 0);
+ range->first = value;
+ value = str_to_code_point(ptr+1, &ptr);
if(value>0 && !*ptr)
{
- *end = value;
+ range->last = value;
return;
}
}
exit(1);
}
+unsigned str_to_code_point(const char *nptr, char **endptr)
+{
+ if(nptr[0]=='U' && nptr[1]=='+')
+ return strtoul(nptr+2, endptr, 16);
+ else if(nptr[0]&0x80)
+ {
+ unsigned bytes;
+ unsigned code;
+ unsigned i;
+
+ if(endptr)
+ *endptr = (char *)nptr;
+
+ for(bytes=1; (bytes<4 && (nptr[0]&(0x80>>bytes))); ++bytes)
+ if((nptr[bytes]&0xC0)!=0x80)
+ return 0;
+ if(bytes<2)
+ return 0;
+
+ code = nptr[0]&(0x3F>>bytes);
+ for(i=1; i<bytes; ++i)
+ code = (code<<6)|(nptr[i]&0x3F);
+
+ if(endptr)
+ *endptr = (char *)nptr+bytes;
+
+ return code;
+ }
+ else if(isdigit(nptr[0]))
+ return strtoul(nptr, endptr, 0);
+ else
+ {
+ if(endptr)
+ *endptr = (char *)nptr+1;
+ return *nptr;
+ }
+}
+
void convert_size(char opt, unsigned *width, unsigned *height)
{
int value;
exit(1);
}
+void sort_and_compact_ranges(Range *ranges, unsigned *n_ranges)
+{
+ unsigned i, j;
+
+ if(!*n_ranges)
+ return;
+
+ qsort(ranges, *n_ranges, sizeof(Range), &range_cmp);
+ for(i=0, j=1; j<*n_ranges; ++j)
+ {
+ if(ranges[i].last+1>=ranges[j].first)
+ {
+ if(ranges[j].last>ranges[i].last)
+ ranges[i].last = ranges[j].last;
+ }
+ else
+ {
+ ++i;
+ if(i!=j)
+ ranges[i] = ranges[j];
+ }
+ }
+
+ *n_ranges = i+1;
+}
+
+int range_cmp(const void *p1, const void *p2)
+{
+ const Range *r1 = (const Range *)p1;
+ const Range *r2 = (const Range *)p2;
+ if(r1->first!=r2->first)
+ return (r1->first<r2->first ? -1 : 1);
+ else if(r1->last!=r2->last)
+ return (r1->last<r2->last ? -1 : 1);
+ else
+ return 0;
+}
+
unsigned round_to_pot(unsigned n)
{
n -= 1;
return ptr;
}
-int init_font(Font *font, FT_Face face, unsigned first, unsigned last, bool autohinter)
+int init_font(Font *font, FT_Face face, const Range *ranges, unsigned n_ranges, bool autohinter, unsigned distfield)
{
unsigned i, j;
unsigned size = 0;
+ int scale = (distfield>0 ? distfield : 1);
- font->ascent = (face->size->metrics.ascender+63)>>6;
- font->descent = (face->size->metrics.descender+63)>>6;
+ font->ascent = (face->size->metrics.ascender/scale+63)/64;
+ font->descent = (face->size->metrics.descender/scale-63)/64;
if(verbose>=1)
{
font->n_glyphs = 0;
font->glyphs = NULL;
- for(i=first; i<=last; ++i)
+ for(i=0; i<n_ranges; ++i)
+ if(init_glyphs(font, face, &ranges[i], autohinter, distfield))
+ return -1;
+
+ if(verbose>=1)
+ printf("Loaded %u glyphs\n", font->n_glyphs);
+
+ font->n_kerning = 0;
+ font->kerning = NULL;
+ for(i=0; i<font->n_glyphs; ++i) for(j=0; j<font->n_glyphs; ++j)
+ if(j!=i)
+ {
+ FT_Vector kerning;
+ FT_Get_Kerning(face, font->glyphs[i].index, font->glyphs[j].index, FT_KERNING_DEFAULT, &kerning);
+
+ /* FreeType documentation says that vertical kerning is practically
+ never used, so we ignore it. */
+ if(kerning.x)
+ {
+ Kerning *kern;
+
+ if(font->n_kerning>=size)
+ {
+ size += 16;
+ font->kerning = (Kerning *)realloc(font->kerning, size*sizeof(Kerning));
+ }
+
+ kern = &font->kerning[font->n_kerning++];
+ kern->left_code = font->glyphs[i].code;
+ kern->right_code = font->glyphs[j].code;
+ kern->distance = (kerning.x/scale+32)/64;
+ }
+ }
+
+ if(verbose>=1)
+ printf("Loaded %d kerning pairs\n", font->n_kerning);
+
+ return 0;
+}
+
+int init_glyphs(Font *font, FT_Face face, const Range *range, bool autohinter, unsigned distfield)
+{
+ unsigned i, j;
+ unsigned size = font->n_glyphs;
+ int scale = (distfield>0 ? distfield : 1);
+
+ for(i=range->first; i<=range->last; ++i)
{
unsigned n;
FT_Bitmap *bmp = &face->glyph->bitmap;
- unsigned x, y;
int flags = 0;
Glyph *glyph;
if(autohinter)
flags |= FT_LOAD_FORCE_AUTOHINT;
FT_Load_Glyph(face, n, flags);
- FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
+ FT_Render_Glyph(face->glyph, (distfield ? FT_RENDER_MODE_MONO : FT_RENDER_MODE_NORMAL));
if(verbose>=2)
- printf(" Char %u: glyph %u, size %dx%d\n", i, n, bmp->width, bmp->rows);
+ {
+ printf(" Code point U+%04X", i);
+ if(i>=0x20 && i<0x7F)
+ printf(" (%c)", i);
+ else if(i>=0xA0 && i<=0x10FFFF)
+ {
+ char utf8[5];
+ unsigned bytes;
- if(bmp->pixel_mode!=FT_PIXEL_MODE_GRAY)
+ for(bytes=2; i>>(1+bytes*5); ++bytes) ;
+ for(j=0; j<bytes; ++j)
+ utf8[j] = 0x80 | ((i>>((bytes-j-1)*6))&0x3F);
+ utf8[0] |= 0xF0<<(4-bytes);
+ utf8[j] = 0;
+
+ printf(" (%s)", utf8);
+ }
+ printf(": glyph %u, size %dx%d\n", n, bmp->width/scale, bmp->rows/scale);
+ }
+
+ if(bmp->pixel_mode!=FT_PIXEL_MODE_GRAY && bmp->pixel_mode!=FT_PIXEL_MODE_MONO)
{
- fprintf(stderr, "Warning: Glyph %u skipped, not grayscale\n", n);
+ fprintf(stderr, "Warning: Glyph %u skipped, incompatible pixel mode\n", n);
continue;
}
glyph = &font->glyphs[font->n_glyphs++];
glyph->index = n;
glyph->code = i;
- glyph->image.w = bmp->width;
- glyph->image.h = bmp->rows;
- glyph->image.data = (char *)malloc(bmp->width*bmp->rows);
- if(!glyph->image.data)
- {
- fprintf(stderr, "Cannot allocate %d bytes of memory for glyph\n", bmp->width*bmp->rows);
- return -1;
- }
- glyph->offset_x = face->glyph->bitmap_left;
- glyph->offset_y = face->glyph->bitmap_top-bmp->rows;
- glyph->advance = (int)(face->glyph->advance.x+32)/64;
+ glyph->offset_x = (int)(face->glyph->bitmap_left+scale/2)/scale;
+ glyph->offset_y = (int)(face->glyph->bitmap_top-bmp->rows+scale/2)/scale;
+ glyph->advance = (int)(face->glyph->advance.x/scale+32)/64;
/* Copy the glyph image since FreeType uses a global buffer, which would
be overwritten by the next glyph. Negative pitch means the scanlines
start from the bottom. */
- if(bmp->pitch<0)
+ if(distfield)
{
- for(y=0; y<bmp->rows; ++y) for(x=0; x<bmp->width; ++x)
- glyph->image.data[x+(glyph->image.h-1-y)*glyph->image.w] = bmp->buffer[x-y*bmp->pitch];
+ unsigned margin = 3;
+
+ glyph->offset_x -= margin;
+ glyph->offset_y -= margin;
+ create_distance_field(bmp, &glyph->image, distfield, margin);
}
else
{
- for(y=0; y<bmp->rows; ++y) for(x=0; x<bmp->width; ++x)
- glyph->image.data[x+y*glyph->image.w] = bmp->buffer[x+y*bmp->pitch];
+ if(copy_bitmap(bmp, &glyph->image))
+ return -1;
}
}
- if(verbose>=1)
- printf("Loaded %u glyphs\n", font->n_glyphs);
+ return 0;
+}
- size = 0;
- font->n_kerning = 0;
- font->kerning = NULL;
- for(i=0; i<font->n_glyphs; ++i) for(j=0; j<font->n_glyphs; ++j)
- if(j!=i)
+int copy_bitmap(const FT_Bitmap *bmp, Image *image)
+{
+ unsigned x, y;
+ unsigned char *src;
+ unsigned char *dst;
+
+ image->w = bmp->width;
+ image->h = bmp->rows;
+ if(!image->w || !image->h)
+ {
+ image->data = NULL;
+ return 0;
+ }
+
+ image->data = (unsigned char *)malloc(image->w*image->h);
+ if(!image->data)
+ {
+ fprintf(stderr, "Cannot allocate %d bytes of memory for glyph\n", image->w*image->h);
+ return -1;
+ }
+
+ if(bmp->pitch<0)
+ src = bmp->buffer+(bmp->rows-1)*-bmp->pitch;
+ else
+ src = bmp->buffer;
+ dst = image->data;
+
+ for(y=0; y<bmp->rows; ++y)
+ {
+ if(bmp->pixel_mode==FT_PIXEL_MODE_MONO)
{
- FT_Vector kerning;
- FT_Get_Kerning(face, font->glyphs[i].index, font->glyphs[j].index, FT_KERNING_DEFAULT, &kerning);
+ for(x=0; x<bmp->width; ++x)
+ dst[x] = ((src[x/8]&(0x80>>(x%8))) ? 0xFF : 0x00);
+ }
+ else
+ {
+ for(x=0; x<bmp->width; ++x)
+ dst[x] = src[x];
+ }
- /* FreeType documentation says that vertical kerning is practically
- never used, so we ignore it. */
- if(kerning.x)
- {
- Kerning *kern;
+ src += bmp->pitch;
+ dst += image->w;
+ }
- if(font->n_kerning>=size)
- {
- size += 16;
- font->kerning = (Kerning *)realloc(font->kerning, size*sizeof(Kerning));
- }
+ return 0;
+}
- kern = &font->kerning[font->n_kerning++];
- kern->left_code = font->glyphs[i].code;
- kern->right_code = font->glyphs[j].code;
- kern->distance = kerning.x/64;
+unsigned sqrti(unsigned num)
+{
+ unsigned result = num;
+ while(result*result>num)
+ {
+ unsigned diff = result*result-num;
+ if(diff<result)
+ break;
+
+ result -= (diff+result)/(result*2);
+ }
+
+ return result;
+}
+
+unsigned find_distance_to_edge(const Image *image, int origin_x, int origin_y, unsigned range)
+{
+ unsigned i, j;
+ int x, y;
+ unsigned char origin_pixel = 0;
+ unsigned closest = range*range;
+
+ if(origin_x>=0 && (unsigned)origin_x<image->w && origin_y>=0 && (unsigned)origin_y<image->h)
+ origin_pixel = image->data[origin_x+origin_y*image->w];
+
+ x = origin_x-1;
+ y = origin_y-1;
+ for(i=1; (i<range && i*i<=closest); ++i, --x, --y) for(j=0; j<4; ++j)
+ {
+ unsigned k;
+ int dx = (j==0 ? 1 : j==2 ? -1 : 0);
+ int dy = (j==1 ? 1 : j==3 ? -1 : 0);
+
+ for(k=0; k<i*2; ++k, x+=dx, y+=dy)
+ {
+ unsigned char pixel = 0;
+ if(x>=0 && (unsigned)x<image->w && y>=0 && (unsigned)y<image->h)
+ pixel = image->data[x+y*image->w];
+
+ if((pixel^origin_pixel)&0x80)
+ {
+ unsigned d = 2*i*i + k*k - 2*k*i;
+ if(d<closest)
+ closest = d;
}
}
+ }
- if(verbose>=1)
- printf("Loaded %d kerning pairs\n", font->n_kerning);
+ return sqrti(closest)*0x7F/range;
+}
+
+int create_distance_field(const FT_Bitmap *bmp, Image *image, unsigned scale, unsigned margin)
+{
+ unsigned x, y;
+ Image base_image;
+
+ if(!bmp->width || !bmp->rows)
+ {
+ image->w = 0;
+ image->h = 0;
+ image->data = NULL;
+ return 0;
+ }
+
+ if(copy_bitmap(bmp, &base_image))
+ return -1;
+
+ image->w = (base_image.w-1)/scale+2*margin+1;
+ image->h = (base_image.h-1)/scale+2*margin+1;
+ image->data = (unsigned char *)malloc(image->w*image->h);
+ if(!image->data)
+ {
+ fprintf(stderr, "Cannot allocate %d bytes of memory for glyph\n", image->w*image->h);
+ free(base_image.data);
+ return -1;
+ }
+
+ for(y=0; y<image->h; ++y) for(x=0; x<image->w; ++x)
+ {
+ int bx = (x-margin)*scale+scale/2;
+ int by = (y-margin)*scale+scale/2;
+ unsigned char pixel = find_distance_to_edge(&base_image, bx, by, margin*scale);
+ if(bx>=0 && (unsigned)bx<base_image.w && by>=0 && (unsigned)by<base_image.h)
+ pixel |= base_image.data[bx+by*base_image.w]&0x80;
+ if(!(pixel&0x80))
+ pixel = 0x80-pixel;
+ image->data[x+y*image->w] = pixel;
+ }
+
+ free(base_image.data);
return 0;
}
-int render_grid(Font *font, unsigned cellw, unsigned cellh, unsigned cpl, bool seq)
+int render_grid(Font *font, unsigned cellw, unsigned cellh, unsigned cpl, bool seq, bool npot)
{
unsigned i;
int top = 0, bot = 0;
first -= first%cpl;
last = font->glyphs[font->n_glyphs-1].code;
- font->image.w = round_to_pot(cpl*cellw);
+ font->image.w = cpl*cellw;
+ if(!npot)
+ font->image.w = round_to_pot(font->image.w);
if(seq)
- font->image.h = round_to_pot((font->n_glyphs+cpl-1)/cpl*cellh);
+ font->image.h = (font->n_glyphs+cpl-1)/cpl*cellh;
else
- font->image.h = round_to_pot((last-first+cpl)/cpl*cellh);
+ font->image.h = (last-first+cpl)/cpl*cellh;
+ if(!npot)
+ font->image.h = round_to_pot(font->image.h);
- font->image.data = (char *)alloc_image_data(font->image.w, font->image.h);
+ font->image.data = (unsigned char *)alloc_image_data(font->image.w, font->image.h);
if(!font->image.data)
return -1;
memset(font->image.data, 255, font->image.w*font->image.h);
return 0;
}
-int render_packed(Font *font, unsigned margin, unsigned padding)
+int render_packed(Font *font, unsigned margin, unsigned padding, bool npot)
{
unsigned i;
size_t area = 0;
area = a;
}
- /* Find an image size that's no higher than wide, allowing for some
- imperfections in the packing. */
+ /* Find an image size that's approximately square. */
for(font->image.w=1;; font->image.w<<=1)
{
if(font->image.w<=margin*2)
continue;
- font->image.h = (area*5/4)/(font->image.w-margin*2)+margin*2;
+ font->image.h = area/(font->image.w-margin*2)+margin*2;
if(font->image.h<=font->image.w)
break;
}
- font->image.h = round_to_pot(font->image.h);
+
+ /* Add some extra space to accommodate packing imperfections. */
+ font->image.h = font->image.h*3/2;
/* Allocate arrays for storing the image and keeping track of used pixels and
glyphs. Since glyphs are rectangular and the image is filled starting from
the top, it's enough to track the number of used pixels at the top of each
column. */
- font->image.data = (char *)alloc_image_data(font->image.w, font->image.h);
+ font->image.data = (unsigned char *)alloc_image_data(font->image.w, font->image.h);
if(!font->image.data)
return -1;
memset(font->image.data, 255, font->image.w*font->image.h);
/* Trim the image to the actually used size, in case the original estimate
was too pessimistic. */
- font->image.h = round_to_pot(used_h);
+ font->image.h = used_h;
+ if(!npot)
+ font->image.h = round_to_pot(font->image.h);
free(used_glyphs);
free(used_pixels);