#include #include #include #include #include #include #include #include "parg.h" #include "common.h" #if !SDL_TTF_VERSION_ATLEAST(2,0,18) #error SDL2_ttf of version >= 2.0.18 required #else #ifndef MAX #define MAX(a,b) ((a)>(b)?(a):(b)) #endif enum { Substitute = 0xfffd, Maxsubfwidth = 3000, /* rough */ }; static uint8_t pjw[] = { #include "pjw.h" }; static char *opts = "d:hPs:x:H:S:"; static SDL_Color white = {255, 255, 255, 255}; static SDL_Color black = {0, 0, 0, 255}; static char *opath, *prefix; static int ptsz, hint, style; static uint8_t * surf2k8(SDL_Surface *s) { uint8_t *b, *p; int sz, x, y; sz = s->w*s->h; b = p = malloc(sz); for(y = 0; y < s->h; y++){ for(x = 0; x < s->w; x++) *p++ = ((uint8_t*)s->pixels + y*s->pitch)[x*4]; } return b; } static int pjwit(int f, char *s, int ssz, int scale) { SDL_Surface *g, *line; int dummy, advance, r; SDL_RWops *ops; TTF_Font *pf; Fontchar fc; uint8_t *b; r = 0; g = NULL; b = NULL; line = NULL; dprintf(f, "0x%X\t0x%X\t%s.%X-%X\n", 0, 0, prefix, 0, 0); dprintf(f, "0x%X\t0x%X\t%s.%X-%X\n", Substitute, Substitute, prefix, Substitute, Substitute); if((ops = SDL_RWFromConstMem(pjw, sizeof(pjw))) == NULL){ fprintf(stderr, "pjw: %s\n", SDL_GetError()); return -1; } if((pf = TTF_OpenFontRW(ops, 1, ptsz*scale)) == NULL || TTF_SetFontSize(pf, ptsz*scale) != 0 || TTF_GlyphMetrics32(pf, Substitute, &dummy, &dummy, &dummy, &dummy, &advance) != 0 || (g = TTF_RenderGlyph32_Shaded(pf, Substitute, white, black)) == NULL || (line = SDL_CreateRGBSurfaceWithFormat(0, g->w, g->h, 32, SDL_PIXELFORMAT_XRGB8888)) == NULL) goto err; SDL_FillRect(line, NULL, SDL_MapRGBA(line->format, 0, 0, 0, 0)); SDL_BlitSurface(g, NULL, line, NULL); b = surf2k8(line); if(b == NULL) goto err; snprintf(s, ssz, "%s.%X-%X", opath, 0, 0); memset(&fc, 0, sizeof(fc)); fc.width = advance; fc.bottom = line->h; if(savesubfont(b, line->w, line->h, TTF_FontAscent(pf), 1, &fc, 1, s) != 0) goto err2; snprintf(s, ssz, "%s.%X-%X", opath, Substitute, Substitute); if(savesubfont(b, line->w, line->h, TTF_FontAscent(pf), 1, &fc, 1, s) != 0) goto err2; if(0){ err: fprintf(stderr, "pjw: %s\n", TTF_GetError()); err2: r = -1; } if(g != NULL) SDL_FreeSurface(g); if(line != NULL) SDL_FreeSurface(line); if(pf != NULL) TTF_CloseFont(pf); else SDL_RWclose(ops); free(b); return r; } int main(int argc, char **argv) { int c, optend, nr, i, fdfont, ssz, res, dpi; int minx, maxx, advance, dummy, w, h, maxh, pjwface, hdr, scale; struct parg_state ps; Range *ranges, *rn; char *ofile, *s; TTF_Font *f; uint8_t *b; Uint32 r; if(TTF_Init() != 0){ fprintf(stderr, "ttf init failed: %s\n", TTF_GetError()); return 1; } dpi = -1; ptsz = 16; hint = TTF_HINTING_NORMAL; style = TTF_STYLE_NORMAL; pjwface = 1; scale = 1; parg_init(&ps); optend = parg_reorder(argc, argv, opts, NULL); while((c = parg_getopt(&ps, argc, argv, opts)) >= 0){ switch(c){ case 1: break; case 'd': dpi = atoi(ps.optarg); break; case 's': if((ptsz = atoi(ps.optarg)) < 2){ fprintf(stderr, "invalid point size: %s\n", ps.optarg); return 1; } break; case 'x': if((scale = atoi(ps.optarg)) < 1){ fprintf(stderr, "invalid scaling: %s\n", ps.optarg); return 1; } break; case 'H': if(strcmp(ps.optarg, "normal") == 0) hint = TTF_HINTING_NORMAL; else if(strcmp(ps.optarg, "light") == 0) hint = TTF_HINTING_LIGHT; else if(strcmp(ps.optarg, "mono") == 0) hint = TTF_HINTING_MONO; else if(strcmp(ps.optarg, "none") == 0) hint = TTF_HINTING_NONE; else if(strcmp(ps.optarg, "light_subpixel") == 0) hint = TTF_HINTING_LIGHT_SUBPIXEL; else{ fprintf(stderr, "invalid hinting: %s\n", ps.optarg); return 1; } break; case 'P': pjwface = 0; fprintf(stderr, "shame on you!\n"); break; case 'S': if(strcmp(ps.optarg, "normal") == 0) style = TTF_STYLE_NORMAL; else if(strcmp(ps.optarg, "bold") == 0) style |= TTF_STYLE_BOLD; else if(strcmp(ps.optarg, "italic") == 0) style |= TTF_STYLE_ITALIC; else if(strcmp(ps.optarg, "underline") == 0) style |= TTF_STYLE_UNDERLINE; else if(strcmp(ps.optarg, "strikethrough") == 0) style |= TTF_STYLE_STRIKETHROUGH; else{ fprintf(stderr, "invalid style: %s\n", ps.optarg); return 1; } break; case 'h': usage: fprintf(stderr, "usage: %s [-d DPI] [-s point_size] [-x scale] [-H hinting] [-S style [-S ...]] [-P] FILE [FILE ...] PATH/PREFIX\n", argv[0]); fprintf(stderr, " -H: normal (default), none, light, mono, light_subpixel\n"); fprintf(stderr, " -S: normal (default), bold, italic, underline, strikethrough\n"); fprintf(stderr, " -P: I don't want PJW face in my font (shame on you)\n"); return 1; case '?': fprintf(stderr, "unknown option -%c\n", ps.optopt); return 1; default: fprintf(stderr, "unhandled option -%c\n", c); return 1; } } if(argc-optend < 2) goto usage; opath = argv[argc-1]; i = strlen(opath); s = malloc(ssz = i+64); snprintf(s, ssz, "%s.font", opath); if((prefix = strrchr(s, '/')) == NULL) prefix = s; else{ *prefix++ = 0; mkdir(s, 0755); prefix[-1] = '/'; } res = 0; if((fdfont = open(s, O_CREAT|O_TRUNC|O_WRONLY|O_CLOEXEC, 0644)) < 0){ prefix = NULL; perror(s); goto err; } s[i] = 0; prefix = strdup(prefix); maxh = 0; for(hdr = 0; optend < argc-1; optend++){ ofile = argv[optend]; if((f = TTF_OpenFont(ofile, ptsz)) == NULL){ fprintf(stderr, "%s: %s\n", argv[0], TTF_GetError()); goto err; } if(dpi > 0) TTF_SetFontSizeDPI(f, ptsz, dpi, dpi); TTF_SetFontStyle(f, style); TTF_SetFontKerning(f, 0); TTF_SetFontHinting(f, hint); if(!hdr){ h = TTF_FontLineSkip(f); dprintf(fdfont, "%-4d %d\n", h*scale, TTF_FontAscent(f)*scale); hdr = 1; } if((ranges = getcharset(ofile, &nr, pjwface)) == NULL) goto err; for(i = 0; i < nr; i++){ rn = ranges+i; w = 0; for(r = rn->min; r <= rn->max; r++){ if(TTF_GlyphMetrics32(f, r, &minx, &maxx, &dummy, &dummy, &advance) == 0){ advance = MAX(maxx-minx, advance); if(w+advance > Maxsubfwidth){ /* subfont too wide - split the range */ ranges = realloc(ranges, (nr+1)*sizeof(Range)); rn = ranges+i; memmove(rn+1, rn, (nr-i)*sizeof(Range)); rn[0].max = r-1; rn[1].min = r; nr++; break; } w += advance; } } if(w == 0){ /* skip zero-width subfonts altogether */ memmove(rn, rn+1, (nr-i-1)*sizeof(Range)); nr--; i--; } } for(i = 0; i < nr; i++){ int ncs, x, y; SDL_Surface *g, *line; Fontchar *cs; SDL_Rect dst; rn = ranges+i; dprintf(fdfont, "0x%X\t0x%X\t%s.%X-%X\n", rn->min, rn->max, prefix, rn->min, rn->max); w = 0; for(r = rn->min; r <= rn->max; r++){ if(TTF_GlyphMetrics32(f, r, &minx, &maxx, &dummy, &dummy, &advance) == 0) w += MAX(maxx-minx, advance); } h = TTF_FontLineSkip(f); line = SDL_CreateRGBSurfaceWithFormat(0, w, h, 32, SDL_PIXELFORMAT_XRGB8888); SDL_FillRect(line, NULL, SDL_MapRGBA(line->format, 0, 0, 0, 0)); memset(&dst, 0, sizeof(dst)); cs = calloc(rn->max - rn->min + 1, sizeof(*cs)); for(r = rn->min, ncs = 0; r <= rn->max; r++, ncs++){ cs[ncs].x = dst.x; if(TTF_GlyphMetrics32(f, r, &minx, &maxx, &dummy, &dummy, &advance) != 0){ fprintf(stderr, "%s: rune 0x%x: %s\n", ofile, r, TTF_GetError()); free(cs); goto err; } if((g = TTF_RenderGlyph32_Shaded(f, r, white, black)) == NULL){ /* zero-width chars end up here */ continue; } // this would make sure glyphs always fit, but it also // makes some monospace fonts not actually have a fixed width //advance = MAX(g->w, MAX(maxx-minx, advance)); if(w < dst.x+advance || h < g->h){ SDL_Surface *bigger; if(w < dst.x+advance) w = dst.x+advance; if(h < g->h) h = g->h; bigger = SDL_CreateRGBSurfaceWithFormat(0, w, h, 32, SDL_PIXELFORMAT_XRGB8888); SDL_FillRect(bigger, NULL, SDL_MapRGBA(bigger->format, 0, 0, 0, 0)); SDL_BlitSurface(line, NULL, bigger, NULL); SDL_FreeSurface(line); line = bigger; } for(y = 0; y < g->h; y++){ for(x = 0; x < g->w; x++){ if(((uint8_t*)g->pixels+y*g->pitch)[x*g->format->BytesPerPixel] != 0){ cs[ncs].top = y; for(y = g->h-1; y >= cs[ncs].top; y--){ for(x = g->w-1; x >= 0; x--){ if(((uint8_t*)g->pixels+y*g->pitch)[x*g->format->BytesPerPixel] != 0){ cs[ncs].bottom = y+1; goto done; } } } } } } done: // if(r == RUNE) // fprintf(stderr, "0x%x is %dx%d, top=%d bottom=%d\n", r, g->w, g->h, cs[ncs].top, cs[ncs].bottom); if(maxh < cs[ncs].bottom) maxh = cs[ncs].bottom; SDL_BlitSurface(g, NULL, line, &dst); cs[ncs].width = advance; dst.x += advance; SDL_FreeSurface(g); } snprintf(s, ssz, "%s.%X-%X", opath, rn->min, rn->max); b = surf2k8(line); if(savesubfont(b, line->w, line->h, TTF_FontAscent(f), scale, cs, ncs, s) != 0) goto err; free(b); free(cs); SDL_FreeSurface(line); } TTF_CloseFont(f); free(ranges); } if(pjwface) pjwit(fdfont, s, ssz, scale); /* actual height of the font might be slightly different, so rewrite it */ lseek(fdfont, 0, 0); dprintf(fdfont, "%-4d", maxh*scale); close(fdfont); if(0){ err: res = 1; } free(s); free(prefix); return res; } #endif