nuclear@10: /* nuclear@10: libdrawtext - a simple library for fast text rendering in OpenGL nuclear@10: Copyright (C) 2011-2014 John Tsiombikas nuclear@10: nuclear@10: This program is free software: you can redistribute it and/or modify nuclear@10: it under the terms of the GNU Lesser General Public License as published by nuclear@10: the Free Software Foundation, either version 3 of the License, or nuclear@10: (at your option) any later version. nuclear@10: nuclear@10: This program is distributed in the hope that it will be useful, nuclear@10: but WITHOUT ANY WARRANTY; without even the implied warranty of nuclear@10: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nuclear@10: GNU Lesser General Public License for more details. nuclear@10: nuclear@10: You should have received a copy of the GNU Lesser General Public License nuclear@10: along with this program. If not, see . nuclear@10: */ nuclear@10: #ifndef NO_FREETYPE nuclear@10: #define USE_FREETYPE nuclear@10: #endif nuclear@10: nuclear@10: #include nuclear@10: #include nuclear@10: #include nuclear@10: #include nuclear@10: #include nuclear@10: #include nuclear@10: #include nuclear@10: #include nuclear@10: #ifdef USE_FREETYPE nuclear@10: #include nuclear@10: #include FT_FREETYPE_H nuclear@10: #endif nuclear@22: #include "assman.h" nuclear@10: #include "drawtext.h" nuclear@10: #include "drawtext_impl.h" nuclear@10: nuclear@10: #define FTSZ_TO_PIXELS(x) ((x) / 64) nuclear@10: #define MAX_IMG_WIDTH 4096 nuclear@10: nuclear@10: nuclear@10: #ifdef USE_FREETYPE nuclear@10: static int init_freetype(void); nuclear@10: static void cleanup(void); nuclear@10: nuclear@10: static void calc_best_size(int total_width, int max_gwidth, int max_gheight, nuclear@10: int padding, int pow2, int *imgw, int *imgh); nuclear@10: static int next_pow2(int x); nuclear@10: nuclear@10: static FT_Library ft; nuclear@10: nuclear@10: nuclear@10: static int init_done; nuclear@10: nuclear@10: static int init_freetype(void) nuclear@10: { nuclear@10: if(!init_done) { nuclear@10: if(FT_Init_FreeType(&ft) != 0) { nuclear@10: return -1; nuclear@10: } nuclear@10: atexit(cleanup); nuclear@10: init_done = 1; nuclear@10: } nuclear@10: return 0; nuclear@10: } nuclear@10: nuclear@10: static void cleanup(void) nuclear@10: { nuclear@10: if(init_done) { nuclear@10: FT_Done_FreeType(ft); nuclear@10: } nuclear@10: } nuclear@10: #endif /* USE_FREETYPE */ nuclear@10: nuclear@10: struct dtx_font *dtx_open_font(const char *fname, int sz) nuclear@10: { nuclear@10: struct dtx_font *fnt = 0; nuclear@10: nuclear@10: #ifdef USE_FREETYPE nuclear@10: init_freetype(); nuclear@10: nuclear@10: if(!(fnt = calloc(1, sizeof *fnt))) { nuclear@10: fperror("failed to allocate font structure"); nuclear@10: return 0; nuclear@10: } nuclear@10: nuclear@10: if(FT_New_Face(ft, fname, 0, (FT_Face*)&fnt->face) != 0) { nuclear@10: fprintf(stderr, "failed to open font file: %s\n", fname); nuclear@10: return 0; nuclear@10: } nuclear@10: nuclear@10: /* pre-create the extended ASCII range glyphmap */ nuclear@10: if(sz) { nuclear@10: dtx_prepare_range(fnt, sz, 0, 256); nuclear@10: nuclear@10: if(!dtx_font) { nuclear@10: dtx_use_font(fnt, sz); nuclear@10: } nuclear@10: } nuclear@10: #else nuclear@10: fprintf(stderr, "ignoring call to dtx_open_font: not compiled with freetype support!\n"); nuclear@10: #endif nuclear@10: nuclear@10: return fnt; nuclear@10: } nuclear@10: nuclear@10: struct dtx_font *dtx_open_font_glyphmap(const char *fname) nuclear@10: { nuclear@10: struct dtx_font *fnt; nuclear@10: struct dtx_glyphmap *gmap; nuclear@10: nuclear@10: if(!(fnt = calloc(1, sizeof *fnt))) { nuclear@10: fperror("failed to allocate font structure"); nuclear@10: return 0; nuclear@10: } nuclear@10: nuclear@10: if(fname) { nuclear@10: if(!(gmap = dtx_load_glyphmap(fname))) { nuclear@10: free(fnt); nuclear@10: return 0; nuclear@10: } nuclear@10: nuclear@10: dtx_add_glyphmap(fnt, gmap); nuclear@10: nuclear@10: if(!dtx_font) { nuclear@10: dtx_use_font(fnt, gmap->ptsize); nuclear@10: } nuclear@10: } nuclear@10: return fnt; nuclear@10: } nuclear@10: nuclear@10: void dtx_close_font(struct dtx_font *fnt) nuclear@10: { nuclear@10: if(!fnt) return; nuclear@10: nuclear@10: #ifdef USE_FREETYPE nuclear@10: FT_Done_Face(fnt->face); nuclear@10: #endif nuclear@10: nuclear@10: /* destroy the glyphmaps */ nuclear@10: while(fnt->gmaps) { nuclear@10: void *tmp = fnt->gmaps; nuclear@10: fnt->gmaps = fnt->gmaps->next; nuclear@10: dtx_free_glyphmap(tmp); nuclear@10: } nuclear@10: nuclear@10: free(fnt); nuclear@10: } nuclear@10: nuclear@10: void dtx_prepare(struct dtx_font *fnt, int sz) nuclear@10: { nuclear@10: if(!dtx_get_font_glyphmap_range(fnt, sz, 0, 256)) { nuclear@10: fprintf(stderr, "%s: failed (sz: %d, range: 0-255 [ascii])\n", __FUNCTION__, sz); nuclear@10: } nuclear@10: } nuclear@10: nuclear@10: void dtx_prepare_range(struct dtx_font *fnt, int sz, int cstart, int cend) nuclear@10: { nuclear@10: if(!dtx_get_font_glyphmap_range(fnt, sz, cstart, cend)) { nuclear@10: fprintf(stderr, "%s: failed (sz: %d, range: %d-%d)\n", __FUNCTION__, sz, cstart, cend); nuclear@10: } nuclear@10: } nuclear@10: nuclear@10: struct dtx_glyphmap *dtx_get_font_glyphmap(struct dtx_font *fnt, int sz, int code) nuclear@10: { nuclear@10: struct dtx_glyphmap *gm; nuclear@10: nuclear@10: /* check to see if the last we've given out fits the bill */ nuclear@10: if(fnt->last_gmap && code >= fnt->last_gmap->cstart && code < fnt->last_gmap->cend && fnt->last_gmap->ptsize == sz) { nuclear@10: return fnt->last_gmap; nuclear@10: } nuclear@10: nuclear@10: /* otherwise search for the appropriate glyphmap */ nuclear@10: gm = fnt->gmaps; nuclear@10: while(gm) { nuclear@10: if(code >= gm->cstart && code < gm->cend && sz == gm->ptsize) { nuclear@10: fnt->last_gmap = gm; nuclear@10: return gm; nuclear@10: } nuclear@10: gm = gm->next; nuclear@10: } nuclear@10: return 0; nuclear@10: } nuclear@10: nuclear@10: struct dtx_glyphmap *dtx_get_font_glyphmap_range(struct dtx_font *fnt, int sz, int cstart, int cend) nuclear@10: { nuclear@10: struct dtx_glyphmap *gm; nuclear@10: nuclear@10: /* search the available glyphmaps to see if we've got one that includes nuclear@10: * the requested range nuclear@10: */ nuclear@10: gm = fnt->gmaps; nuclear@10: while(gm) { nuclear@10: if(gm->cstart <= cstart && gm->cend >= cend && gm->ptsize == sz) { nuclear@10: return gm; nuclear@10: } nuclear@10: gm = gm->next; nuclear@10: } nuclear@10: nuclear@10: /* not found, create one and add it to the list */ nuclear@10: if(!(gm = dtx_create_glyphmap_range(fnt, sz, cstart, cend))) { nuclear@10: return 0; nuclear@10: } nuclear@10: return gm; nuclear@10: } nuclear@10: nuclear@10: struct dtx_glyphmap *dtx_create_glyphmap_range(struct dtx_font *fnt, int sz, int cstart, int cend) nuclear@10: { nuclear@10: struct dtx_glyphmap *gmap = 0; nuclear@10: nuclear@10: #ifdef USE_FREETYPE nuclear@10: FT_Face face = fnt->face; nuclear@10: int i, j; nuclear@10: int gx, gy; nuclear@10: int padding = 4; nuclear@10: int total_width, max_width, max_height; nuclear@10: nuclear@10: FT_Set_Char_Size(fnt->face, 0, sz * 64, 72, 72); nuclear@10: nuclear@10: if(!(gmap = calloc(1, sizeof *gmap))) { nuclear@10: return 0; nuclear@10: } nuclear@10: nuclear@10: gmap->ptsize = sz; nuclear@10: gmap->cstart = cstart; nuclear@10: gmap->cend = cend; nuclear@10: gmap->crange = cend - cstart; nuclear@10: gmap->line_advance = FTSZ_TO_PIXELS((float)face->size->metrics.height); nuclear@10: nuclear@10: if(!(gmap->glyphs = malloc(gmap->crange * sizeof *gmap->glyphs))) { nuclear@10: free(gmap); nuclear@10: return 0; nuclear@10: } nuclear@10: nuclear@10: total_width = padding; nuclear@10: max_width = max_height = 0; nuclear@10: nuclear@10: for(i=0; icrange; i++) { nuclear@10: int w, h; nuclear@10: nuclear@10: FT_Load_Char(face, i + cstart, 0); nuclear@10: w = FTSZ_TO_PIXELS(face->glyph->metrics.width); nuclear@10: h = FTSZ_TO_PIXELS(face->glyph->metrics.height); nuclear@10: nuclear@10: if(w > max_width) max_width = w; nuclear@10: if(h > max_height) max_height = h; nuclear@10: nuclear@10: total_width += w + padding; nuclear@10: } nuclear@10: nuclear@10: calc_best_size(total_width, max_width, max_height, padding, 1, &gmap->xsz, &gmap->ysz); nuclear@10: nuclear@10: if(!(gmap->pixels = malloc(gmap->xsz * gmap->ysz))) { nuclear@10: free(gmap->glyphs); nuclear@10: free(gmap); nuclear@10: return 0; nuclear@10: } nuclear@10: memset(gmap->pixels, 0, gmap->xsz * gmap->ysz); nuclear@10: nuclear@10: gx = padding; nuclear@10: gy = padding; nuclear@10: nuclear@10: for(i=0; icrange; i++) { nuclear@10: float gwidth, gheight; nuclear@10: unsigned char *src, *dst; nuclear@10: FT_GlyphSlot glyph; nuclear@10: nuclear@10: FT_Load_Char(face, i + cstart, FT_LOAD_RENDER); nuclear@10: glyph = face->glyph; nuclear@10: gwidth = FTSZ_TO_PIXELS((float)glyph->metrics.width); nuclear@10: gheight = FTSZ_TO_PIXELS((float)glyph->metrics.height); nuclear@10: nuclear@10: if(gx > gmap->xsz - gwidth - padding) { nuclear@10: gx = padding; nuclear@10: gy += max_height + padding; nuclear@10: } nuclear@10: nuclear@10: src = glyph->bitmap.buffer; nuclear@10: dst = gmap->pixels + gy * gmap->xsz + gx; nuclear@10: nuclear@10: for(j=0; jbitmap.rows; j++) { nuclear@10: memcpy(dst, src, glyph->bitmap.width); nuclear@10: dst += gmap->xsz; nuclear@10: src += glyph->bitmap.pitch; nuclear@10: } nuclear@10: nuclear@10: gmap->glyphs[i].code = i; nuclear@10: gmap->glyphs[i].x = gx - 1; nuclear@10: gmap->glyphs[i].y = gy - 1; nuclear@10: gmap->glyphs[i].width = gwidth + 2; nuclear@10: gmap->glyphs[i].height = gheight + 2; nuclear@10: gmap->glyphs[i].orig_x = -FTSZ_TO_PIXELS((float)glyph->metrics.horiBearingX) + 1; nuclear@10: gmap->glyphs[i].orig_y = FTSZ_TO_PIXELS((float)glyph->metrics.height - glyph->metrics.horiBearingY) + 1; nuclear@10: gmap->glyphs[i].advance = FTSZ_TO_PIXELS((float)glyph->metrics.horiAdvance); nuclear@10: /* also precalc normalized */ nuclear@10: gmap->glyphs[i].nx = (float)gmap->glyphs[i].x / (float)gmap->xsz; nuclear@10: gmap->glyphs[i].ny = (float)gmap->glyphs[i].y / (float)gmap->ysz; nuclear@10: gmap->glyphs[i].nwidth = (float)gmap->glyphs[i].width / (float)gmap->xsz; nuclear@10: gmap->glyphs[i].nheight = (float)gmap->glyphs[i].height / (float)gmap->ysz; nuclear@10: nuclear@10: gx += gwidth + padding; nuclear@10: } nuclear@10: nuclear@10: /* add it to the glyphmaps list of the font */ nuclear@10: dtx_add_glyphmap(fnt, gmap); nuclear@10: #endif /* USE_FREETYPE */ nuclear@10: nuclear@10: return gmap; nuclear@10: } nuclear@10: nuclear@10: void dtx_free_glyphmap(struct dtx_glyphmap *gmap) nuclear@10: { nuclear@10: if(gmap) { nuclear@10: free(gmap->pixels); nuclear@10: free(gmap->glyphs); nuclear@10: free(gmap); nuclear@10: } nuclear@10: } nuclear@10: nuclear@10: unsigned char *dtx_get_glyphmap_pixels(struct dtx_glyphmap *gmap) nuclear@10: { nuclear@10: return gmap->pixels; nuclear@10: } nuclear@10: nuclear@10: int dtx_get_glyphmap_width(struct dtx_glyphmap *gmap) nuclear@10: { nuclear@10: return gmap->xsz; nuclear@10: } nuclear@10: nuclear@10: int dtx_get_glyphmap_height(struct dtx_glyphmap *gmap) nuclear@10: { nuclear@10: return gmap->ysz; nuclear@10: } nuclear@10: nuclear@10: struct dtx_glyphmap *dtx_load_glyphmap(const char *fname) nuclear@10: { nuclear@22: ass_file *fp; nuclear@10: struct dtx_glyphmap *gmap; nuclear@10: nuclear@22: if(!(fp = ass_fopen(fname, "rb"))) { nuclear@10: return 0; nuclear@10: } nuclear@10: gmap = dtx_load_glyphmap_stream(fp); nuclear@22: ass_fclose(fp); nuclear@10: return gmap; nuclear@10: } nuclear@10: nuclear@22: struct dtx_glyphmap *dtx_load_glyphmap_stream(ass_file *fp) nuclear@10: { nuclear@10: char buf[512]; nuclear@10: int hdr_lines = 0; nuclear@10: struct dtx_glyphmap *gmap; nuclear@10: struct glyph *glyphs = 0; nuclear@10: struct glyph *g; nuclear@10: int min_code = INT_MAX; nuclear@10: int max_code = INT_MIN; nuclear@10: int i, max_pixval, num_pixels; nuclear@10: nuclear@10: if(!(gmap = calloc(1, sizeof *gmap))) { nuclear@10: fperror("failed to allocate glyphmap"); nuclear@10: return 0; nuclear@10: } nuclear@10: gmap->ptsize = -1; nuclear@10: gmap->line_advance = FLT_MIN; nuclear@10: nuclear@10: while(hdr_lines < 3) { nuclear@10: char *line = buf; nuclear@22: if(!ass_fgets(buf, sizeof buf, fp)) { nuclear@10: fperror("unexpected end of file"); nuclear@10: goto err; nuclear@10: } nuclear@10: nuclear@10: while(isspace(*line)) { nuclear@10: line++; nuclear@10: } nuclear@10: nuclear@10: if(line[0] == '#') { nuclear@10: int c, res; nuclear@10: float x, y, xsz, ysz, orig_x, orig_y, adv, line_adv; nuclear@10: int ptsize; nuclear@10: nuclear@10: if((res = sscanf(line + 1, " size: %d\n", &ptsize)) == 1) { nuclear@10: gmap->ptsize = ptsize; nuclear@10: nuclear@10: } else if((res = sscanf(line + 1, " advance: %f\n", &line_adv)) == 1) { nuclear@10: gmap->line_advance = line_adv; nuclear@10: nuclear@10: } else if((res = sscanf(line + 1, " %d: %fx%f+%f+%f o:%f,%f adv:%f\n", nuclear@10: &c, &xsz, &ysz, &x, &y, &orig_x, &orig_y, &adv)) == 8) { nuclear@10: if(!(g = malloc(sizeof *g))) { nuclear@10: fperror("failed to allocate glyph"); nuclear@10: goto err; nuclear@10: } nuclear@10: g->code = c; nuclear@10: g->x = x; nuclear@10: g->y = y; nuclear@10: g->width = xsz; nuclear@10: g->height = ysz; nuclear@10: g->orig_x = orig_x; nuclear@10: g->orig_y = orig_y; nuclear@10: g->advance = adv; nuclear@10: /* normalized coordinates will be precalculated after everything is loaded */ nuclear@10: nuclear@10: g->next = glyphs; nuclear@10: glyphs = g; nuclear@10: nuclear@10: if(c < min_code) { nuclear@10: min_code = c; nuclear@10: } nuclear@10: if(c > max_code) { nuclear@10: max_code = c; nuclear@10: } nuclear@10: nuclear@10: } else { nuclear@10: fprintf(stderr, "%s: invalid glyph info line\n", __FUNCTION__); nuclear@10: goto err; nuclear@10: } nuclear@10: nuclear@10: } else { nuclear@10: switch(hdr_lines) { nuclear@10: case 0: nuclear@10: if(0[line] != 'P' || 1[line] != '6') { nuclear@10: fprintf(stderr, "%s: invalid file format (magic)\n", __FUNCTION__); nuclear@10: goto err; nuclear@10: } nuclear@10: break; nuclear@10: nuclear@10: case 1: nuclear@10: if(sscanf(line, "%d %d", &gmap->xsz, &gmap->ysz) != 2) { nuclear@10: fprintf(stderr, "%s: invalid file format (dim)\n", __FUNCTION__); nuclear@10: goto err; nuclear@10: } nuclear@10: break; nuclear@10: nuclear@10: case 2: nuclear@10: { nuclear@10: char *endp; nuclear@10: max_pixval = strtol(line, &endp, 10); nuclear@10: if(endp == line) { nuclear@10: fprintf(stderr, "%s: invalid file format (maxval)\n", __FUNCTION__); nuclear@10: goto err; nuclear@10: } nuclear@10: } nuclear@10: break; nuclear@10: nuclear@10: default: nuclear@10: break; /* can't happen */ nuclear@10: } nuclear@10: hdr_lines++; nuclear@10: } nuclear@10: } nuclear@10: nuclear@10: if(gmap->ptsize == -1 || gmap->line_advance == FLT_MIN) { nuclear@10: fprintf(stderr, "%s: invalid glyphmap, insufficient information in ppm comments\n", __FUNCTION__); nuclear@10: goto err; nuclear@10: } nuclear@10: nuclear@10: /* precalculate normalized glyph coordinates */ nuclear@10: g = glyphs; nuclear@10: while(g) { nuclear@10: g->nx = g->x / gmap->xsz; nuclear@10: g->ny = g->y / gmap->ysz; nuclear@10: g->nwidth = g->width / gmap->xsz; nuclear@10: g->nheight = g->height / gmap->ysz; nuclear@10: g = g->next; nuclear@10: } nuclear@10: nuclear@10: num_pixels = gmap->xsz * gmap->ysz; nuclear@10: if(!(gmap->pixels = malloc(num_pixels))) { nuclear@10: fperror("failed to allocate pixels"); nuclear@10: goto err; nuclear@10: } nuclear@10: nuclear@10: for(i=0; ipixels[i] = 255 * c / max_pixval; nuclear@22: ass_fseek(fp, 2, SEEK_CUR); nuclear@10: } nuclear@10: nuclear@10: gmap->cstart = min_code; nuclear@10: gmap->cend = max_code + 1; nuclear@10: gmap->crange = gmap->cend - gmap->cstart; nuclear@10: nuclear@10: if(!(gmap->glyphs = calloc(gmap->crange, sizeof *gmap->glyphs))) { nuclear@10: fperror("failed to allocate glyph info"); nuclear@10: goto err; nuclear@10: } nuclear@10: nuclear@10: while(glyphs) { nuclear@10: struct glyph *g = glyphs; nuclear@10: glyphs = glyphs->next; nuclear@10: nuclear@10: gmap->glyphs[g->code - gmap->cstart] = *g; nuclear@10: free(g); nuclear@10: } nuclear@10: return gmap; nuclear@10: nuclear@10: err: nuclear@10: dtx_free_glyphmap(gmap); nuclear@10: while(glyphs) { nuclear@10: void *tmp = glyphs; nuclear@10: glyphs = glyphs->next; nuclear@10: free(tmp); nuclear@10: } nuclear@10: return 0; nuclear@10: } nuclear@10: nuclear@10: int dtx_save_glyphmap(const char *fname, const struct dtx_glyphmap *gmap) nuclear@10: { nuclear@10: FILE *fp; nuclear@10: int res; nuclear@10: nuclear@10: if(!(fp = fopen(fname, "wb"))) { nuclear@10: fprintf(stderr, "%s: failed to open file: %s: %s\n", __FUNCTION__, fname, strerror(errno)); nuclear@10: return -1; nuclear@10: } nuclear@10: res = dtx_save_glyphmap_stream(fp, gmap); nuclear@10: fclose(fp); nuclear@10: return res; nuclear@10: } nuclear@10: nuclear@10: int dtx_save_glyphmap_stream(FILE *fp, const struct dtx_glyphmap *gmap) nuclear@10: { nuclear@10: int i, num_pixels; nuclear@10: struct glyph *g = gmap->glyphs; nuclear@10: nuclear@10: fprintf(fp, "P6\n%d %d\n", gmap->xsz, gmap->ysz); nuclear@10: fprintf(fp, "# size: %d\n", gmap->ptsize); nuclear@10: fprintf(fp, "# advance: %g\n", gmap->line_advance); nuclear@10: for(i=0; icrange; i++) { nuclear@10: fprintf(fp, "# %d: %gx%g+%g+%g o:%g,%g adv:%g\n", g->code, g->width, g->height, g->x, g->y, nuclear@10: g->orig_x, g->orig_y, g->advance); nuclear@10: g++; nuclear@10: } nuclear@10: fprintf(fp, "255\n"); nuclear@10: nuclear@10: num_pixels = gmap->xsz * gmap->ysz; nuclear@10: for(i=0; ipixels[i]; nuclear@10: fputc(c, fp); nuclear@10: fputc(c, fp); nuclear@10: fputc(c, fp); nuclear@10: } nuclear@10: return 0; nuclear@10: } nuclear@10: nuclear@10: void dtx_add_glyphmap(struct dtx_font *fnt, struct dtx_glyphmap *gmap) nuclear@10: { nuclear@10: gmap->next = fnt->gmaps; nuclear@10: fnt->gmaps = gmap; nuclear@10: } nuclear@10: nuclear@10: nuclear@10: void dtx_use_font(struct dtx_font *fnt, int sz) nuclear@10: { nuclear@10: dtx_gl_init(); nuclear@10: nuclear@10: dtx_font = fnt; nuclear@10: dtx_font_sz = sz; nuclear@10: } nuclear@10: nuclear@10: float dtx_line_height(void) nuclear@10: { nuclear@10: struct dtx_glyphmap *gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, '\n'); nuclear@10: nuclear@10: return gmap->line_advance; nuclear@10: } nuclear@10: nuclear@10: void dtx_glyph_box(int code, struct dtx_box *box) nuclear@10: { nuclear@10: int cidx; nuclear@10: struct dtx_glyphmap *gmap; nuclear@10: gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, code); nuclear@10: nuclear@10: cidx = code - gmap->cstart; nuclear@10: nuclear@10: box->x = gmap->glyphs[cidx].orig_x; nuclear@10: box->y = gmap->glyphs[cidx].orig_y; nuclear@10: box->width = gmap->glyphs[cidx].width; nuclear@10: box->height = gmap->glyphs[cidx].height; nuclear@10: } nuclear@10: nuclear@10: float dtx_glyph_width(int code) nuclear@10: { nuclear@10: struct dtx_box box; nuclear@10: dtx_glyph_box(code, &box); nuclear@10: return box.width; nuclear@10: } nuclear@10: nuclear@10: float dtx_glyph_height(int code) nuclear@10: { nuclear@10: struct dtx_box box; nuclear@10: dtx_glyph_box(code, &box); nuclear@10: return box.height; nuclear@10: } nuclear@10: nuclear@10: void dtx_string_box(const char *str, struct dtx_box *box) nuclear@10: { nuclear@10: int code; nuclear@10: float pos_x = 0.0f, pos_y = 0.0f; nuclear@10: struct glyph *g = 0; nuclear@10: float x0, y0, x1, y1; nuclear@10: nuclear@10: x0 = y0 = FLT_MAX; nuclear@10: x1 = y1 = -FLT_MAX; nuclear@10: nuclear@10: while(*str) { nuclear@10: float px, py; nuclear@10: struct dtx_glyphmap *gmap; nuclear@10: nuclear@10: code = dtx_utf8_char_code(str); nuclear@10: str = dtx_utf8_next_char((char*)str); nuclear@10: nuclear@10: px = pos_x; nuclear@10: py = pos_y; nuclear@10: nuclear@10: if((gmap = dtx_proc_char(code, &pos_x, &pos_y))) { nuclear@10: g = gmap->glyphs + code - gmap->cstart; nuclear@10: nuclear@10: if(px + g->orig_x < x0) { nuclear@10: x0 = px + g->orig_x; nuclear@10: } nuclear@10: if(py - g->orig_y < y0) { nuclear@10: y0 = py - g->orig_y; nuclear@10: } nuclear@10: if(px + g->orig_x + g->width > x1) { nuclear@10: x1 = px + g->orig_x + g->width; nuclear@10: } nuclear@10: if(py - g->orig_y + g->height > y1) { nuclear@10: y1 = py - g->orig_y + g->height; nuclear@10: } nuclear@10: } nuclear@10: } nuclear@10: nuclear@10: box->x = x0; nuclear@10: box->y = y0; nuclear@10: box->width = x1 - x0; nuclear@10: box->height = y1 - y0; nuclear@10: } nuclear@10: nuclear@10: float dtx_string_width(const char *str) nuclear@10: { nuclear@10: struct dtx_box box; nuclear@10: nuclear@10: dtx_string_box(str, &box); nuclear@10: return box.width; nuclear@10: } nuclear@10: nuclear@10: float dtx_string_height(const char *str) nuclear@10: { nuclear@10: struct dtx_box box; nuclear@10: nuclear@10: dtx_string_box(str, &box); nuclear@10: return box.height; nuclear@10: } nuclear@10: nuclear@10: float dtx_char_pos(const char *str, int n) nuclear@10: { nuclear@10: int i; nuclear@10: float pos = 0.0; nuclear@10: struct dtx_glyphmap *gmap; nuclear@10: nuclear@10: for(i=0; iglyphs[i].advance; nuclear@10: } nuclear@10: return pos; nuclear@10: } nuclear@10: nuclear@10: int dtx_char_at_pt(const char *str, float pt) nuclear@10: { nuclear@10: int i; nuclear@10: float prev_pos = 0.0f, pos = 0.0f; nuclear@10: struct dtx_glyphmap *gmap; nuclear@10: nuclear@10: for(i=0; *str; i++) { nuclear@10: int code = dtx_utf8_char_code(str); nuclear@10: str = dtx_utf8_next_char((char*)str); nuclear@10: nuclear@10: gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, code); nuclear@10: pos += gmap->glyphs[i].advance; nuclear@10: nuclear@10: if(fabs(pt - prev_pos) < fabs(pt - pos)) { nuclear@10: break; nuclear@10: } nuclear@10: prev_pos = pos; nuclear@10: } nuclear@10: return i; nuclear@10: } nuclear@10: nuclear@10: struct dtx_glyphmap *dtx_proc_char(int code, float *xpos, float *ypos) nuclear@10: { nuclear@10: struct dtx_glyphmap *gmap; nuclear@10: gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, code); nuclear@10: nuclear@10: switch(code) { nuclear@10: case '\n': nuclear@10: *xpos = 0.0; nuclear@10: if(gmap) { nuclear@10: *ypos -= gmap->line_advance; nuclear@10: } nuclear@10: return 0; nuclear@10: nuclear@10: case '\t': nuclear@10: if(gmap) { nuclear@10: *xpos = (fmod(*xpos, 4.0) + 4.0) * gmap->glyphs[0].advance; nuclear@10: } nuclear@10: return 0; nuclear@10: nuclear@10: case '\r': nuclear@10: *xpos = 0.0; nuclear@10: return 0; nuclear@10: nuclear@10: default: nuclear@10: break; nuclear@10: } nuclear@10: nuclear@10: if(gmap) { nuclear@10: *xpos += gmap->glyphs[code - gmap->cstart].advance; nuclear@10: } nuclear@10: return gmap; nuclear@10: } nuclear@10: nuclear@10: #ifdef USE_FREETYPE nuclear@10: static void calc_best_size(int total_width, int max_gwidth, int max_gheight, int padding, int pow2, int *imgw, int *imgh) nuclear@10: { nuclear@10: int xsz, ysz, num_rows; nuclear@10: float aspect; nuclear@10: nuclear@10: for(xsz=2; xsz<=MAX_IMG_WIDTH; xsz *= 2) { nuclear@10: num_rows = total_width / xsz + 1; nuclear@10: nuclear@10: /* assume worst case, all last glyphs will float to the next line nuclear@10: * so let's add extra rows for that. */ nuclear@10: num_rows += (padding + (max_gwidth + padding) * num_rows + xsz - 1) / xsz; nuclear@10: nuclear@10: ysz = num_rows * (max_gheight + padding) + padding; nuclear@10: if(pow2) { nuclear@10: ysz = next_pow2(ysz); nuclear@10: } nuclear@10: aspect = (float)xsz / (float)ysz; nuclear@10: nuclear@10: if(aspect >= 1.0) { nuclear@10: break; nuclear@10: } nuclear@10: } nuclear@10: nuclear@10: if(xsz > MAX_IMG_WIDTH) { nuclear@10: xsz = MAX_IMG_WIDTH; nuclear@10: } nuclear@10: nuclear@10: *imgw = xsz; nuclear@10: *imgh = ysz; nuclear@10: } nuclear@10: nuclear@10: nuclear@10: static int next_pow2(int x) nuclear@10: { nuclear@10: x--; nuclear@10: x = (x >> 1) | x; nuclear@10: x = (x >> 2) | x; nuclear@10: x = (x >> 4) | x; nuclear@10: x = (x >> 8) | x; nuclear@10: x = (x >> 16) | x; nuclear@10: return x + 1; nuclear@10: } nuclear@10: #endif