libdrawtext
diff src/font.c @ 0:bfe431dd1d80
initial commit
author | John Tsiombikas <nuclear@mutantstargoat.com> |
---|---|
date | Thu, 15 Sep 2011 10:47:38 +0300 |
parents | |
children | fe0c54e574ae |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/font.c Thu Sep 15 10:47:38 2011 +0300 1.3 @@ -0,0 +1,493 @@ 1.4 +#ifndef NO_FREETYPE 1.5 +#define USE_FREETYPE 1.6 +#endif 1.7 + 1.8 +#include <stdio.h> 1.9 +#include <stdlib.h> 1.10 +#include <string.h> 1.11 +#include <limits.h> 1.12 +#include <ctype.h> 1.13 +#include <errno.h> 1.14 +#ifdef USE_FREETYPE 1.15 +#include <ft2build.h> 1.16 +#include FT_FREETYPE_H 1.17 +#endif 1.18 +#include "drawtext.h" 1.19 +#include "drawtext_impl.h" 1.20 + 1.21 +#define FTSZ_TO_PIXELS(x) ((x) / 64) 1.22 +#define MAX_IMG_WIDTH 4096 1.23 + 1.24 + 1.25 +#ifdef USE_FREETYPE 1.26 +static int init_freetype(void); 1.27 +static void cleanup(void); 1.28 + 1.29 +static void calc_best_size(int total_width, int max_glyph_height, int padding, int pow2, int *imgw, int *imgh); 1.30 +static int next_pow2(int x); 1.31 + 1.32 +static FT_Library ft; 1.33 + 1.34 + 1.35 +static int init_done; 1.36 + 1.37 +static int init_freetype(void) 1.38 +{ 1.39 + if(!init_done) { 1.40 + if(FT_Init_FreeType(&ft) != 0) { 1.41 + return -1; 1.42 + } 1.43 + atexit(cleanup); 1.44 + init_done = 1; 1.45 + } 1.46 + return 0; 1.47 +} 1.48 + 1.49 +static void cleanup(void) 1.50 +{ 1.51 + if(init_done) { 1.52 + FT_Done_FreeType(ft); 1.53 + } 1.54 +} 1.55 + 1.56 +struct dtx_font *dtx_open_font(const char *fname, int sz) 1.57 +{ 1.58 + struct dtx_font *fnt; 1.59 + 1.60 + init_freetype(); 1.61 + 1.62 + if(!(fnt = calloc(1, sizeof *fnt))) { 1.63 + fperror("failed to allocate font structure"); 1.64 + return 0; 1.65 + } 1.66 + 1.67 + if(FT_New_Face(ft, fname, 0, (FT_Face*)&fnt->face) != 0) { 1.68 + fprintf(stderr, "failed to open font file: %s\n", fname); 1.69 + return 0; 1.70 + } 1.71 + 1.72 + /* pre-create the extended ASCII range glyphmap */ 1.73 + if(sz) { 1.74 + dtx_prepare_range(fnt, sz, 0, 256); 1.75 + } 1.76 + 1.77 + return fnt; 1.78 +} 1.79 + 1.80 +void dtx_close_font(struct dtx_font *fnt) 1.81 +{ 1.82 + if(!fnt) return; 1.83 + 1.84 + FT_Done_Face(fnt->face); 1.85 + 1.86 + /* destroy the glyphmaps */ 1.87 + while(fnt->gmaps) { 1.88 + void *tmp = fnt->gmaps; 1.89 + fnt->gmaps = fnt->gmaps->next; 1.90 + dtx_free_glyphmap(tmp); 1.91 + } 1.92 + 1.93 + free(fnt); 1.94 +} 1.95 + 1.96 +void dtx_prepare(struct dtx_font *fnt, int sz) 1.97 +{ 1.98 + dtx_get_font_glyphmap_range(fnt, sz, 0, 256); 1.99 +} 1.100 + 1.101 +void dtx_prepare_range(struct dtx_font *fnt, int sz, int cstart, int cend) 1.102 +{ 1.103 + dtx_get_font_glyphmap_range(fnt, sz, cstart, cend); 1.104 +} 1.105 + 1.106 +struct dtx_glyphmap *dtx_get_font_glyphmap(struct dtx_font *fnt, int sz, int code) 1.107 +{ 1.108 + struct dtx_glyphmap *gm; 1.109 + 1.110 + /* check to see if the last we've given out fits the bill */ 1.111 + if(fnt->last_gmap && code >= fnt->last_gmap->cstart && code < fnt->last_gmap->cend && fnt->last_gmap->ptsize == sz) { 1.112 + return fnt->last_gmap; 1.113 + } 1.114 + 1.115 + /* otherwise search for the appropriate glyphmap */ 1.116 + gm = fnt->gmaps; 1.117 + while(gm) { 1.118 + if(code >= gm->cstart && code < gm->cend && sz == gm->ptsize) { 1.119 + fnt->last_gmap = gm; 1.120 + return gm; 1.121 + } 1.122 + gm = gm->next; 1.123 + } 1.124 + return 0; 1.125 +} 1.126 + 1.127 +struct dtx_glyphmap *dtx_get_font_glyphmap_range(struct dtx_font *fnt, int sz, int cstart, int cend) 1.128 +{ 1.129 + struct dtx_glyphmap *gm; 1.130 + 1.131 + /* search the available glyphmaps to see if we've got one that includes 1.132 + * the requested range 1.133 + */ 1.134 + gm = fnt->gmaps; 1.135 + while(gm) { 1.136 + if(gm->cstart <= cstart && gm->cend >= cend && gm->ptsize == sz) { 1.137 + return gm; 1.138 + } 1.139 + gm = gm->next; 1.140 + } 1.141 + 1.142 + /* not found, create one and add it to the list */ 1.143 + if(!(gm = dtx_create_glyphmap_range(fnt, sz, cstart, cend))) { 1.144 + return 0; 1.145 + } 1.146 + return gm; 1.147 +} 1.148 + 1.149 +struct dtx_glyphmap *dtx_create_glyphmap_range(struct dtx_font *fnt, int sz, int cstart, int cend) 1.150 +{ 1.151 + FT_Face face = fnt->face; 1.152 + struct dtx_glyphmap *gmap; 1.153 + int i, j; 1.154 + int gx, gy; 1.155 + int padding = 4; 1.156 + int total_width = padding; 1.157 + int max_height = 0; 1.158 + 1.159 + FT_Set_Char_Size(fnt->face, 0, sz * 64, 72, 72); 1.160 + 1.161 + if(!(gmap = malloc(sizeof *gmap))) { 1.162 + return 0; 1.163 + } 1.164 + 1.165 + gmap->ptsize = sz; 1.166 + gmap->cstart = cstart; 1.167 + gmap->cend = cend; 1.168 + gmap->crange = cend - cstart; 1.169 + gmap->line_advance = FTSZ_TO_PIXELS((float)face->size->metrics.height); 1.170 + 1.171 + if(!(gmap->glyphs = malloc(gmap->crange * sizeof *gmap->glyphs))) { 1.172 + free(gmap); 1.173 + return 0; 1.174 + } 1.175 + 1.176 + for(i=0; i<gmap->crange; i++) { 1.177 + int h; 1.178 + 1.179 + FT_Load_Char(face, i + cstart, 0); 1.180 + h = FTSZ_TO_PIXELS(face->glyph->metrics.height); 1.181 + 1.182 + if(h > max_height) { 1.183 + max_height = h; 1.184 + } 1.185 + total_width += FTSZ_TO_PIXELS(face->glyph->metrics.width) + padding; 1.186 + } 1.187 + 1.188 + calc_best_size(total_width, max_height, padding, 1, &gmap->xsz, &gmap->ysz); 1.189 + 1.190 + if(!(gmap->pixels = malloc(gmap->xsz * gmap->ysz))) { 1.191 + free(gmap->glyphs); 1.192 + free(gmap); 1.193 + return 0; 1.194 + } 1.195 + memset(gmap->pixels, 0, gmap->xsz * gmap->ysz); 1.196 + 1.197 + gx = padding; 1.198 + gy = padding; 1.199 + 1.200 + for(i=0; i<gmap->crange; i++) { 1.201 + float gwidth, gheight; 1.202 + unsigned char *src, *dst; 1.203 + FT_GlyphSlot glyph; 1.204 + 1.205 + FT_Load_Char(face, i + cstart, FT_LOAD_RENDER); 1.206 + glyph = face->glyph; 1.207 + gwidth = FTSZ_TO_PIXELS((float)glyph->metrics.width); 1.208 + gheight = FTSZ_TO_PIXELS((float)glyph->metrics.height); 1.209 + 1.210 + if(gx > gmap->xsz - gwidth - padding) { 1.211 + gx = padding; 1.212 + gy += max_height + padding; 1.213 + } 1.214 + 1.215 + src = glyph->bitmap.buffer; 1.216 + dst = gmap->pixels + gy * gmap->xsz + gx; 1.217 + 1.218 + for(j=0; j<glyph->bitmap.rows; j++) { 1.219 + memcpy(dst, src, glyph->bitmap.width); 1.220 + dst += gmap->xsz; 1.221 + src += glyph->bitmap.pitch; 1.222 + } 1.223 + 1.224 + gmap->glyphs[i].code = i; 1.225 + gmap->glyphs[i].x = gx - 1; 1.226 + gmap->glyphs[i].y = gy - 1; 1.227 + gmap->glyphs[i].width = gwidth + 2; 1.228 + gmap->glyphs[i].height = gheight + 2; 1.229 + gmap->glyphs[i].orig_x = -FTSZ_TO_PIXELS((float)glyph->metrics.horiBearingX) + 1; 1.230 + gmap->glyphs[i].orig_y = FTSZ_TO_PIXELS((float)glyph->metrics.height - glyph->metrics.horiBearingY) + 1; 1.231 + gmap->glyphs[i].advance = FTSZ_TO_PIXELS((float)glyph->metrics.horiAdvance); 1.232 + /* also precalc normalized */ 1.233 + gmap->glyphs[i].nx = (float)gmap->glyphs[i].x / (float)gmap->xsz; 1.234 + gmap->glyphs[i].ny = (float)gmap->glyphs[i].y / (float)gmap->ysz; 1.235 + gmap->glyphs[i].nwidth = (float)gmap->glyphs[i].width / (float)gmap->xsz; 1.236 + gmap->glyphs[i].nheight = (float)gmap->glyphs[i].height / (float)gmap->ysz; 1.237 + 1.238 + gx += gwidth + padding; 1.239 + } 1.240 + 1.241 + /* add it to the glyphmaps list of the font */ 1.242 + gmap->next = fnt->gmaps; 1.243 + fnt->gmaps = gmap; 1.244 + 1.245 + return gmap; 1.246 +} 1.247 +#endif /* USE_FREETYPE */ 1.248 + 1.249 +void dtx_free_glyphmap(struct dtx_glyphmap *gmap) 1.250 +{ 1.251 + if(gmap) { 1.252 + free(gmap->pixels); 1.253 + free(gmap->glyphs); 1.254 + free(gmap); 1.255 + } 1.256 +} 1.257 + 1.258 +unsigned char *dtx_get_glyphmap_pixels(struct dtx_glyphmap *gmap) 1.259 +{ 1.260 + return gmap->pixels; 1.261 +} 1.262 + 1.263 +int dtx_get_glyphmap_width(struct dtx_glyphmap *gmap) 1.264 +{ 1.265 + return gmap->xsz; 1.266 +} 1.267 + 1.268 +int dtx_get_glyphmap_height(struct dtx_glyphmap *gmap) 1.269 +{ 1.270 + return gmap->ysz; 1.271 +} 1.272 + 1.273 +struct dtx_glyphmap *dtx_load_glyphmap(const char *fname) 1.274 +{ 1.275 + FILE *fp; 1.276 + struct dtx_glyphmap *gmap; 1.277 + 1.278 + if(!(fp = fopen(fname, "r"))) { 1.279 + return 0; 1.280 + } 1.281 + gmap = dtx_load_glyphmap_stream(fp); 1.282 + fclose(fp); 1.283 + return gmap; 1.284 +} 1.285 + 1.286 +struct dtx_glyphmap *dtx_load_glyphmap_stream(FILE *fp) 1.287 +{ 1.288 + char buf[512]; 1.289 + int hdr_lines = 0; 1.290 + struct dtx_glyphmap *gmap; 1.291 + struct glyph *glyphs = 0; 1.292 + int min_code = INT_MAX; 1.293 + int max_code = INT_MIN; 1.294 + int i, max_pixval, num_pixels; 1.295 + 1.296 + if(!(gmap = calloc(1, sizeof *gmap))) { 1.297 + fperror("failed to allocate glyphmap"); 1.298 + return 0; 1.299 + } 1.300 + 1.301 + while(hdr_lines < 3) { 1.302 + char *line = buf; 1.303 + if(!fgets(buf, sizeof buf, fp)) { 1.304 + fperror("unexpected end of file"); 1.305 + goto err; 1.306 + } 1.307 + 1.308 + while(isspace(*line)) { 1.309 + line++; 1.310 + } 1.311 + 1.312 + if(line[0] == '#') { 1.313 + struct glyph *g; 1.314 + int c; 1.315 + float x, y, xsz, ysz, res; 1.316 + 1.317 + res = sscanf(line + 1, "%d: %fx%f+%f+%f\n", &c, &xsz, &ysz, &x, &y); 1.318 + if(res != 5) { 1.319 + fprintf(stderr, "%s: invalid glyph info line\n", __func__); 1.320 + goto err; 1.321 + } 1.322 + 1.323 + if(!(g = malloc(sizeof *g))) { 1.324 + fperror("failed to allocate glyph"); 1.325 + goto err; 1.326 + } 1.327 + g->code = c; 1.328 + g->x = x; 1.329 + g->y = y; 1.330 + g->width = xsz; 1.331 + g->height = ysz; 1.332 + g->next = glyphs; 1.333 + glyphs = g; 1.334 + 1.335 + if(c < min_code) { 1.336 + min_code = c; 1.337 + } 1.338 + if(c > max_code) { 1.339 + max_code = c; 1.340 + } 1.341 + } else { 1.342 + switch(hdr_lines) { 1.343 + case 0: 1.344 + if(0[line] != 'P' || 1[line] != '6') { 1.345 + fprintf(stderr, "%s: invalid file format (magic)\n", __func__); 1.346 + goto err; 1.347 + } 1.348 + break; 1.349 + 1.350 + case 1: 1.351 + if(sscanf(line, "%d %d", &gmap->xsz, &gmap->ysz) != 2) { 1.352 + fprintf(stderr, "%s: invalid file format (dim)\n", __func__); 1.353 + goto err; 1.354 + } 1.355 + break; 1.356 + 1.357 + case 2: 1.358 + { 1.359 + char *endp; 1.360 + max_pixval = strtol(line, &endp, 10); 1.361 + if(endp == line) { 1.362 + fprintf(stderr, "%s: invalid file format (maxval)\n", __func__); 1.363 + goto err; 1.364 + } 1.365 + } 1.366 + break; 1.367 + 1.368 + default: 1.369 + break; /* can't happen */ 1.370 + } 1.371 + hdr_lines++; 1.372 + } 1.373 + } 1.374 + 1.375 + num_pixels = gmap->xsz * gmap->ysz; 1.376 + if(!(gmap->pixels = malloc(num_pixels))) { 1.377 + fperror("failed to allocate pixels"); 1.378 + goto err; 1.379 + } 1.380 + 1.381 + for(i=0; i<num_pixels; i++) { 1.382 + long c = fgetc(fp); 1.383 + if(c == -1) { 1.384 + fprintf(stderr, "unexpected end of file while reading pixels\n"); 1.385 + goto err; 1.386 + } 1.387 + gmap->pixels[i] = 255 * c / max_pixval; 1.388 + fseek(fp, 2, SEEK_CUR); 1.389 + } 1.390 + 1.391 + gmap->cstart = min_code; 1.392 + gmap->cend = max_code + 1; 1.393 + gmap->crange = gmap->cend - gmap->cstart; 1.394 + 1.395 + if(!(gmap->glyphs = calloc(gmap->crange, sizeof *gmap->glyphs))) { 1.396 + fperror("failed to allocate glyph info"); 1.397 + goto err; 1.398 + } 1.399 + 1.400 + while(glyphs) { 1.401 + struct glyph *g = glyphs; 1.402 + glyphs = glyphs->next; 1.403 + 1.404 + gmap->glyphs[g->code - gmap->cstart] = *g; 1.405 + free(g); 1.406 + } 1.407 + return gmap; 1.408 + 1.409 +err: 1.410 + dtx_free_glyphmap(gmap); 1.411 + while(glyphs) { 1.412 + void *tmp = glyphs; 1.413 + glyphs = glyphs->next; 1.414 + free(tmp); 1.415 + } 1.416 + return 0; 1.417 +} 1.418 + 1.419 +int dtx_save_glyphmap(const char *fname, const struct dtx_glyphmap *gmap) 1.420 +{ 1.421 + FILE *fp; 1.422 + int res; 1.423 + 1.424 + if(!(fp = fopen(fname, "wb"))) { 1.425 + fprintf(stderr, "%s: failed to open file: %s: %s\n", __func__, fname, strerror(errno)); 1.426 + return -1; 1.427 + } 1.428 + res = dtx_save_glyphmap_stream(fp, gmap); 1.429 + fclose(fp); 1.430 + return res; 1.431 +} 1.432 + 1.433 +int dtx_save_glyphmap_stream(FILE *fp, const struct dtx_glyphmap *gmap) 1.434 +{ 1.435 + int i, num_pixels; 1.436 + struct glyph *g = gmap->glyphs; 1.437 + 1.438 + fprintf(fp, "P6\n%d %d\n", gmap->xsz, gmap->ysz); 1.439 + for(i=0; i<gmap->crange; i++) { 1.440 + fprintf(fp, "# %d: %fx%f+%f+%f\n", g->code, g->width, g->height, g->x, g->y); 1.441 + g++; 1.442 + } 1.443 + fprintf(fp, "255\n"); 1.444 + 1.445 + num_pixels = gmap->xsz * gmap->ysz; 1.446 + for(i=0; i<num_pixels; i++) { 1.447 + int c = gmap->pixels[i]; 1.448 + fputc(c, fp); 1.449 + fputc(c, fp); 1.450 + fputc(c, fp); 1.451 + } 1.452 + return 0; 1.453 +} 1.454 + 1.455 + 1.456 +static void calc_best_size(int total_width, int max_glyph_height, int padding, int pow2, int *imgw, int *imgh) 1.457 +{ 1.458 + int xsz, ysz, num_rows; 1.459 + float aspect; 1.460 + 1.461 + for(xsz=2; xsz<=MAX_IMG_WIDTH; xsz *= 2) { 1.462 + num_rows = total_width / xsz + 1; 1.463 + 1.464 + /* take into account the one extra padding for each row after the first */ 1.465 + num_rows = (total_width + padding * (num_rows - 1)) / xsz + 1; 1.466 + 1.467 + ysz = num_rows * (max_glyph_height + padding) + padding; 1.468 + if(pow2) { 1.469 + ysz = next_pow2(ysz); 1.470 + } 1.471 + aspect = (float)xsz / (float)ysz; 1.472 + 1.473 + if(aspect >= 1.0) { 1.474 + break; 1.475 + } 1.476 + } 1.477 + 1.478 + if(xsz > MAX_IMG_WIDTH) { 1.479 + xsz = MAX_IMG_WIDTH; 1.480 + } 1.481 + 1.482 + *imgw = xsz; 1.483 + *imgh = ysz; 1.484 +} 1.485 + 1.486 + 1.487 +static int next_pow2(int x) 1.488 +{ 1.489 + x--; 1.490 + x = (x >> 1) | x; 1.491 + x = (x >> 2) | x; 1.492 + x = (x >> 4) | x; 1.493 + x = (x >> 8) | x; 1.494 + x = (x >> 16) | x; 1.495 + return x + 1; 1.496 +}