2 ttf2png - True Type Font to PNG converter
3 Copyright (c) 2004-2008 Mikko Rasa
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 #include FT_FREETYPE_H
45 typedef struct sKerning
67 int convert_numeric_option(char, int);
68 void convert_code_point_range(char, unsigned *, unsigned *);
69 void convert_size(char, unsigned *, unsigned *);
70 unsigned round_to_pot(unsigned);
71 void *alloc_image_data(size_t, size_t);
72 int init_font(Font *, FT_Face, unsigned, unsigned, bool);
73 int render_grid(Font *, unsigned, unsigned, unsigned, bool);
74 int render_packed(Font *, unsigned, unsigned);
75 int save_defs(const char *, const Font *);
76 int save_png(const char *, const Image *, char);
80 int main(int argc, char **argv)
103 char *out_fn = "font.png";
114 while((i = getopt(argc, argv, "r:s:l:c:o:atvh?ed:pim:n:")) != -1)
119 convert_code_point_range('r', &begin, &end);
122 size = convert_numeric_option('s', 1);
125 cpl = convert_numeric_option('l', 1);
128 convert_size('c', &cellw, &cellh);
159 margin = convert_numeric_option('m', 0);
162 padding = convert_numeric_option('n', 0);
166 if(!strcmp(out_fn, "-"))
177 err = FT_Init_FreeType(&freetype);
180 fprintf(stderr, "Couldn't initialize FreeType library\n");
184 err = FT_New_Face(freetype, fn, 0, &face);
187 fprintf(stderr, "Couldn't load font file\n");
188 if(err==FT_Err_Unknown_File_Format)
189 fprintf(stderr, "Unknown file format\n");
195 const char *name = FT_Get_Postscript_Name(face);
196 printf("Font name: %s\n", name);
197 printf("Glyphs: %ld\n", face->num_glyphs);
200 err = FT_Set_Pixel_Sizes(face, 0, size);
203 fprintf(stderr, "Couldn't set size\n");
208 err = init_font(&font, face, begin, end, autohinter);
213 err = render_packed(&font, margin, padding);
215 err = render_grid(&font, cellw, cellh, cpl, seq);
221 for(i=0; (unsigned)i<font.image.w*font.image.h; ++i)
222 font.image.data[i] = 255-font.image.data[i];
224 err = save_png(out_fn, &font.image, alpha);
229 save_defs(def_fn, &font);
231 for(i=0; (unsigned)i<font.n_glyphs; ++i)
232 free(font.glyphs[i].image.data);
235 free(font.image.data);
238 FT_Done_FreeType(freetype);
245 printf("ttf2png 1.1 - True Type Font to PNG converter\n"
246 "Copyright (c) 2004-2018 Mikko Rasa, Mikkosoft Productions\n"
247 "Distributed under the GNU General Public License\n\n");
249 printf("Usage: ttf2png [options] <TTF file>\n\n");
251 printf("Accepted options (default values in [brackets])\n"
252 " -r Range of code points to convert [0,255]\n"
253 " -s Font size to use, in pixels [10]\n"
254 " -l Number of glyphs to put in one line [auto]\n"
255 " -c Glyph cell size, in pixels (grid mode only) [auto]\n"
256 " -o Output file name (or - for stdout) [font.png]\n");
257 printf(" -a Force autohinter\n"
258 " -t Render glyphs to alpha channel\n"
259 " -i Invert colors of the glyphs\n"
260 " -v Increase the level of verbosity\n"
261 " -e Use cells in sequence, without gaps (grid mode only)\n"
262 " -p Pack the glyphs tightly instead of in a grid\n"
263 " -m Margin around image edges (packed mode only) [0]\n"
264 " -n Padding between glyphs (packed mode only) [1]\n"
265 " -d File name for writing glyph definitions\n"
266 " -h Print this message\n");
269 int convert_numeric_option(char opt, int min_value)
274 value = strtol(optarg, &ptr, 0);
275 if(value<min_value || *ptr)
277 printf("Invalid option argument in -%c %s\n", opt, optarg);
284 void convert_code_point_range(char opt, unsigned *begin, unsigned *end)
289 if(!strcmp(optarg, "all"))
296 value = strtol(optarg, &ptr, 0);
297 if(value>0 && *ptr==',')
300 value = strtol(ptr+1, &ptr, 0);
308 printf("Invalid option argument in -%c %s\n", opt, optarg);
312 void convert_size(char opt, unsigned *width, unsigned *height)
317 if(!strcmp(optarg, "auto"))
323 else if(!strcmp(optarg, "autorect"))
330 value = strtol(optarg, &ptr, 0);
336 value = strtol(ptr+1, &ptr, 0);
350 printf("Invalid option argument in -%c %s\n", opt, optarg);
354 unsigned round_to_pot(unsigned n)
366 void *alloc_image_data(size_t a, size_t b)
370 /* Carry out the multiplication manually so we can check for overflow. */
379 fprintf(stderr, "Cannot allocate %lu kbytes of memory for image\n", (unsigned long)(c/1024*b));
386 fprintf(stderr, "Cannot allocate %lu kbytes of memory for image\n", (unsigned long)(a/1024*b));
390 int init_font(Font *font, FT_Face face, unsigned first, unsigned last, bool autohinter)
395 font->ascent = (face->size->metrics.ascender+63)>>6;
396 font->descent = (face->size->metrics.descender+63)>>6;
400 printf("Ascent: %d\n", font->ascent);
401 printf("Descent: %d\n", font->descent);
406 for(i=first; i<=last; ++i)
409 FT_Bitmap *bmp = &face->glyph->bitmap;
414 n = FT_Get_Char_Index(face, i);
419 flags |= FT_LOAD_FORCE_AUTOHINT;
420 FT_Load_Glyph(face, n, flags);
421 FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
424 printf(" Char %u: glyph %u, size %dx%d\n", i, n, bmp->width, bmp->rows);
426 if(bmp->pixel_mode!=FT_PIXEL_MODE_GRAY)
428 fprintf(stderr, "Warning: Glyph %u skipped, not grayscale\n", n);
432 if(font->n_glyphs>=size)
435 font->glyphs = (Glyph *)realloc(font->glyphs, size*sizeof(Glyph));
438 glyph = &font->glyphs[font->n_glyphs++];
441 glyph->image.w = bmp->width;
442 glyph->image.h = bmp->rows;
443 glyph->image.data = (char *)malloc(bmp->width*bmp->rows);
444 if(!glyph->image.data)
446 fprintf(stderr, "Cannot allocate %d bytes of memory for glyph\n", bmp->width*bmp->rows);
449 glyph->offset_x = face->glyph->bitmap_left;
450 glyph->offset_y = face->glyph->bitmap_top-bmp->rows;
451 glyph->advance = (int)(face->glyph->advance.x+32)/64;
453 /* Copy the glyph image since FreeType uses a global buffer, which would
454 be overwritten by the next glyph. Negative pitch means the scanlines
455 start from the bottom. */
458 for(y=0; y<bmp->rows; ++y) for(x=0; x<bmp->width; ++x)
459 glyph->image.data[x+(glyph->image.h-1-y)*glyph->image.w] = bmp->buffer[x-y*bmp->pitch];
463 for(y=0; y<bmp->rows; ++y) for(x=0; x<bmp->width; ++x)
464 glyph->image.data[x+y*glyph->image.w] = bmp->buffer[x+y*bmp->pitch];
469 printf("Loaded %u glyphs\n", font->n_glyphs);
473 font->kerning = NULL;
474 for(i=0; i<font->n_glyphs; ++i) for(j=0; j<font->n_glyphs; ++j)
478 FT_Get_Kerning(face, font->glyphs[i].index, font->glyphs[j].index, FT_KERNING_DEFAULT, &kerning);
480 /* FreeType documentation says that vertical kerning is practically
481 never used, so we ignore it. */
486 if(font->n_kerning>=size)
489 font->kerning = (Kerning *)realloc(font->kerning, size*sizeof(Kerning));
492 kern = &font->kerning[font->n_kerning++];
493 kern->left_code = font->glyphs[i].code;
494 kern->right_code = font->glyphs[j].code;
495 kern->distance = kerning.x/64;
500 printf("Loaded %d kerning pairs\n", font->n_kerning);
505 int render_grid(Font *font, unsigned cellw, unsigned cellh, unsigned cpl, bool seq)
508 int top = 0, bot = 0;
509 unsigned first, last;
510 unsigned maxw = 0, maxh = 0;
512 /* Find extremes of the glyph images. */
513 for(i=0; i<font->n_glyphs; ++i)
517 y = font->glyphs[i].offset_y+font->glyphs[i].image.h;
520 if(font->glyphs[i].offset_y<bot)
521 bot = font->glyphs[i].offset_y;
522 if(font->glyphs[i].image.w>maxw)
523 maxw = font->glyphs[i].image.w;
524 if(font->glyphs[i].image.h>maxh)
525 maxh = font->glyphs[i].image.h;
530 /* Establish a large enough cell to hold all glyphs in the range. */
531 int square = (cellh==cellw);
545 printf("Max size: %u x %u\n", maxw, maxh);
546 printf("Y range: [%d %d]\n", bot, top);
547 printf("Cell size: %u x %u\n", cellw, cellh);
548 if(maxw>cellw || (unsigned)(top-bot)>cellh)
549 fprintf(stderr, "Warning: character size exceeds cell size\n");
554 /* Determine number of characters per line, trying to fit all the glyphs
555 in a square image. */
559 if(cpl>0 && font->n_glyphs/cpl*cellh<=cpl*cellw)
564 first = font->glyphs[0].code;
567 last = font->glyphs[font->n_glyphs-1].code;
569 font->image.w = round_to_pot(cpl*cellw);
571 font->image.h = round_to_pot((font->n_glyphs+cpl-1)/cpl*cellh);
573 font->image.h = round_to_pot((last-first+cpl)/cpl*cellh);
575 font->image.data = (char *)alloc_image_data(font->image.w, font->image.h);
576 if(!font->image.data)
578 memset(font->image.data, 255, font->image.w*font->image.h);
580 for(i=0; i<font->n_glyphs; ++i)
586 glyph = &font->glyphs[i];
591 ci = glyph->code-first;
596 if(cellw>glyph->image.w)
597 cx += (cellw-glyph->image.w)/2;
598 cy += top-glyph->offset_y-glyph->image.h;
603 for(y=0; y<glyph->image.h; ++y) for(x=0; x<glyph->image.w; ++x)
605 if(cx+x>=font->image.w || cy+y>=font->image.h)
607 font->image.data[cx+x+(cy+y)*font->image.w] = 255-glyph->image.data[x+y*glyph->image.w];
614 int render_packed(Font *font, unsigned margin, unsigned padding)
619 unsigned *used_pixels;
620 unsigned cx = margin, cy;
623 /* Compute the total area occupied by glyphs and padding. */
624 for(i=0; i<font->n_glyphs; ++i)
626 size_t a = area+(font->glyphs[i].image.w+padding)*(font->glyphs[i].image.h+padding);
629 fprintf(stderr, "Overflow in counting total glyph area\n");
635 /* Find an image size that's no higher than wide, allowing for some
636 imperfections in the packing. */
637 for(font->image.w=1;; font->image.w<<=1)
639 if(font->image.w<=margin*2)
641 font->image.h = (area*5/4)/(font->image.w-margin*2)+margin*2;
642 if(font->image.h<=font->image.w)
645 font->image.h = round_to_pot(font->image.h);
647 /* Allocate arrays for storing the image and keeping track of used pixels and
648 glyphs. Since glyphs are rectangular and the image is filled starting from
649 the top, it's enough to track the number of used pixels at the top of each
651 font->image.data = (char *)alloc_image_data(font->image.w, font->image.h);
652 if(!font->image.data)
654 memset(font->image.data, 255, font->image.w*font->image.h);
655 used_pixels = (unsigned *)malloc(font->image.w*sizeof(unsigned));
656 memset(used_pixels, 0, font->image.w*sizeof(unsigned));
657 used_glyphs = (char *)malloc(font->n_glyphs);
658 memset(used_glyphs, 0, font->n_glyphs);
660 for(cy=margin; cy+margin<font->image.h;)
665 unsigned best_score = 0;
666 unsigned target_h = 0;
668 /* Find the leftmost free pixel on this row. Also record the lowest
669 extent of glyphs to the left of the free position. */
670 for(; (cx+margin<font->image.w && used_pixels[cx]>cy); ++cx)
671 if(used_pixels[cx]-cy-padding>target_h)
672 target_h = used_pixels[cx]-cy-padding;
674 if(cx+margin>=font->image.w)
681 /* Count the free pixel at this position. */
682 for(w=0; (cx+w+margin<font->image.w && used_pixels[cx+w]<=cy); ++w) ;
684 /* Find a suitable glyph to put here. */
685 for(i=0; i<font->n_glyphs; ++i)
689 g = &font->glyphs[i];
690 if(!used_glyphs[i] && g->image.w<=w)
694 /* Prefer glyphs that would reach exactly as low as the ones left
695 of here. This aims to create a straight edge at the bottom for
696 lining up further glyphs. */
697 score = g->image.h+padding;
698 if(g->image.h==target_h)
717 used_glyphs[glyph-font->glyphs] = 1;
721 for(y=0; y<glyph->image.h; ++y) for(x=0; x<glyph->image.w; ++x)
723 if(cx+x>=font->image.w || cy+y>=font->image.h)
725 font->image.data[cx+x+(cy+y)*font->image.w] = 255-glyph->image.data[x+y*glyph->image.w];
727 for(x=0; x<glyph->image.w+2*padding; ++x)
729 if(cx+x<padding || cx+x>=font->image.w+padding)
731 if(used_pixels[cx+x-padding]<cy+glyph->image.h+padding)
732 used_pixels[cx+x-padding] = cy+glyph->image.h+padding;
735 if(cy+glyph->image.h+margin>used_h)
736 used_h = cy+glyph->image.h+margin;
739 /* Trim the image to the actually used size, in case the original estimate
740 was too pessimistic. */
741 font->image.h = round_to_pot(used_h);
749 int save_defs(const char *fn, const Font *font)
754 out = fopen(fn, "w");
757 fprintf(stderr, "Couldn't open %s\n",fn);
761 fprintf(out, "# Image/font info:\n");
762 fprintf(out, "# width height size ascent descent\n");
763 fprintf(out, "font %d %d %d %d %d\n", font->image.w, font->image.h, font->size, font->ascent, font->descent);
764 fprintf(out, "\n# Glyph info:\n");
765 fprintf(out, "# code x y width height offset_x offset_y advance\n");
766 for(i=0; i<font->n_glyphs; ++i)
768 const Glyph *g = &font->glyphs[i];
769 fprintf(out, "glyph %u %u %u %u %u %d %d %d\n", g->code, g->x, g->y, g->image.w, g->image.h, g->offset_x, g->offset_y, g->advance);
771 fprintf(out, "\n# Kerning info:\n");
772 fprintf(out, "# left right distance\n");
773 for(i=0; i<font->n_kerning; ++i)
775 const Kerning *k = &font->kerning[i];
776 fprintf(out, "kern %u %u %d\n", k->left_code, k->right_code, k->distance);
784 int save_png(const char *fn, const Image *image, char alpha)
798 out = fopen(fn, "wb");
801 fprintf(stderr, "Couldn't open %s\n",fn);
806 pngs = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
809 fprintf(stderr, "Error writing PNG file\n");
812 pngi = png_create_info_struct(pngs);
815 png_destroy_write_struct(&pngs, NULL);
816 fprintf(stderr, "Error writing PNG file\n");
820 png_init_io(pngs, out);
821 rows = (png_byte **)malloc(image->h*sizeof(png_byte *));
824 data2 = (png_byte *)alloc_image_data(image->w*2, image->h);
827 for(i=0; i<image->w*image->h; ++i)
830 data2[i*2+1] = 255-image->data[i];
832 for(i=0; i<image->h; ++i)
833 rows[i] = (png_byte *)(data2+i*image->w*2);
834 color = PNG_COLOR_TYPE_GRAY_ALPHA;
838 for(i=0; i<image->h; ++i)
839 rows[i] = (png_byte *)(image->data+i*image->w);
840 color = PNG_COLOR_TYPE_GRAY;
842 png_set_IHDR(pngs, pngi, image->w, image->h, 8, color, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
843 png_set_rows(pngs, pngi, rows);
844 png_write_png(pngs, pngi, PNG_TRANSFORM_IDENTITY, NULL);
845 png_destroy_write_struct(&pngs, &pngi);
851 printf("Saved %dx%d PNG image to %s\n", image->w, image->h, fn);