From 7b902c26fe9b42cc0a51d11b7f10a84c0c27ee0a Mon Sep 17 00:00:00 2001 From: "K. Lange" Date: Tue, 6 Jul 2021 19:43:09 +0900 Subject: [PATCH] font-preview: tool for viewing TrueType fonts --- apps/file-browser.c | 1 + apps/font-preview.c | 188 ++++++++++++++++++++++++++++++++++++++++++++ lib/text.c | 156 +++++++++++++++++++++++++++--------- 3 files changed, 306 insertions(+), 39 deletions(-) create mode 100644 apps/font-preview.c diff --git a/apps/file-browser.c b/apps/file-browser.c index 0daafe46..df4a6b61 100644 --- a/apps/file-browser.c +++ b/apps/file-browser.c @@ -650,6 +650,7 @@ static void load_directory(const char * path, int modifies_history) { /* TODO: Font viewer for SDF and TrueType */ } else if (has_extension(f, ".ttf")) { sprintf(f->icon, "font"); + sprintf(f->launcher,"exec font-preview"); sprintf(f->filetype, "TrueType Font"); } else if (has_extension(f, ".tgz") || has_extension(f, ".tar.gz")) { sprintf(f->icon, "package_targz"); diff --git a/apps/font-preview.c b/apps/font-preview.c new file mode 100644 index 00000000..d598a5db --- /dev/null +++ b/apps/font-preview.c @@ -0,0 +1,188 @@ +/** + * @brief TrueType font previewer + */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* Pointer to graphics memory */ +static yutani_t * yctx; +static yutani_window_t * window = NULL; +static gfx_context_t * ctx = NULL; +static struct TT_Font * tt_font = NULL; + +static int decor_left_width = 0; +static int decor_top_height = 0; +static int decor_right_width = 0; +static int decor_bottom_height = 0; +static int decor_width = 0; +static int decor_height = 0; + +static int width = 640; +static int height = 480; + +char * tt_get_name_string(struct TT_Font * font, int identifier); + +void redraw(void) { + draw_fill(ctx, rgb(255,255,255)); + + int y = 10; + + char * fontName = tt_get_name_string(tt_font, 4); + + if (fontName) { + tt_set_size(tt_font, 48); + y += 48; + tt_draw_string(ctx, tt_font, decor_left_width + 10, decor_top_height + y, fontName, rgb(0,0,0)); + y += 10; + free(fontName); + } + + tt_set_size(tt_font, 22); + y += 26; + tt_draw_string(ctx, tt_font, decor_left_width + 10, decor_top_height + y, "abcdefghijklmnopqrstuvwxyz", rgb(0,0,0)); + y += 26; + tt_draw_string(ctx, tt_font, decor_left_width + 10, decor_top_height + y, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", rgb(0,0,0)); + y += 26; + tt_draw_string(ctx, tt_font, decor_left_width + 10, decor_top_height + y, "0123456789.:,;(*!?')", rgb(0,0,0)); + y += 10; + + int sizes[] = {7,10,13,16,19,22,25,48,64,92,0}; + for (int * s = sizes; *s; ++s) { + tt_set_size(tt_font, *s); + y += *s + 4; + tt_draw_string(ctx, tt_font, decor_left_width + 10, decor_top_height + y, "The quick brown fox jumps over the lazy dog.", rgb(0,0,0)); + } + + render_decorations(window, ctx, "Font Preview"); + + flip(ctx); +} + +void resize_finish(int w, int h) { + yutani_window_resize_accept(yctx, window, w, h); + reinit_graphics_yutani(ctx, window); + + struct decor_bounds bounds; + decor_get_bounds(window, &bounds); + + decor_left_width = bounds.left_width; + decor_top_height = bounds.top_height; + decor_right_width = bounds.right_width; + decor_bottom_height = bounds.bottom_height; + decor_width = bounds.width; + decor_height = bounds.height; + + width = w - decor_left_width - decor_right_width; + height = h - decor_top_height - decor_bottom_height; + + redraw(); + + yutani_window_resize_done(yctx, window); + yutani_flip(yctx, window); +} + + +int main(int argc, char * argv[]) { + + if (argc < 2) return 1; + + tt_font = tt_font_from_file(argv[1]); + + if (!tt_font) return 1; + + yctx = yutani_init(); + if (!yctx) { + fprintf(stderr, "%s: failed to connect to compositor\n", argv[0]); + return 1; + } + init_decorations(); + + struct decor_bounds bounds; + decor_get_bounds(NULL, &bounds); + + decor_left_width = bounds.left_width; + decor_top_height = bounds.top_height; + decor_right_width = bounds.right_width; + decor_bottom_height = bounds.bottom_height; + decor_width = bounds.width; + decor_height = bounds.height; + + window = yutani_window_create(yctx, width + decor_width, height + decor_height); + yutani_window_move(yctx, window, 100, 100); + + yutani_window_advertise_icon(yctx, window, "Font Preview", "font"); + + ctx = init_graphics_yutani_double_buffer(window); + + redraw(); + yutani_flip(yctx, window); + + int playing = 1; + while (playing) { + yutani_msg_t * m = yutani_poll(yctx); + while (m) { + if (menu_process_event(yctx, m)) { + redraw(); + yutani_flip(yctx, window); + } + switch (m->type) { + case YUTANI_MSG_WINDOW_FOCUS_CHANGE: + { + struct yutani_msg_window_focus_change * wf = (void*)m->data; + yutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)wf->wid); + if (win && win == window) { + win->focused = wf->focused; + redraw(); + yutani_flip(yctx, window); + } + } + break; + case YUTANI_MSG_RESIZE_OFFER: + { + struct yutani_msg_window_resize * wr = (void*)m->data; + resize_finish(wr->width, wr->height); + } + break; + case YUTANI_MSG_WINDOW_MOUSE_EVENT: + { + struct yutani_msg_window_mouse_event * me = (void*)m->data; + int result = decor_handle_event(yctx, m); + switch (result) { + case DECOR_CLOSE: + playing = 0; + break; + case DECOR_RIGHT: + /* right click in decoration, show appropriate menu */ + decor_show_default_menu(window, window->x + me->new_x, window->y + me->new_y); + break; + default: + /* Other actions */ + break; + } + } + break; + case YUTANI_MSG_WINDOW_CLOSE: + case YUTANI_MSG_SESSION_END: + playing = 0; + break; + default: + break; + } + free(m); + m = yutani_poll_async(yctx); + } + } + + yutani_close(yctx, window); + + return 0; +} + diff --git a/lib/text.c b/lib/text.c index 363c51c2..d9ac18dd 100644 --- a/lib/text.c +++ b/lib/text.c @@ -54,6 +54,30 @@ struct TT_Vertex { int y; }; +struct TT_Font { + int privFlags; + FILE * filePtr; + uint8_t * buffer; + uint8_t * memPtr; + + struct TT_Table head_ptr; + struct TT_Table cmap_ptr; + struct TT_Table loca_ptr; + struct TT_Table glyf_ptr; + struct TT_Table hhea_ptr; + struct TT_Table hmtx_ptr; + struct TT_Table name_ptr; + + off_t cmap_start; + + size_t cmap_maxInd; + + float scale; +}; + + +/* Currently, the edge sorter is disabled. It doesn't really help much, + * and it's very slow with our horrible qsort implementation. */ #if 0 static int edge_sorter_high_scanline(const void * a, const void * b) { const struct TT_Edge * left = a; @@ -97,25 +121,22 @@ static float edge_at(float y, struct TT_Edge * edge) { return edge->start.x + u * (edge->end.x - edge->start.x); } -/** - * FIXME: This spans the whole context; it should use the path's bounding box... - */ void tt_path_paint(gfx_context_t * ctx, struct TT_Shape * shape, uint32_t color) { size_t size = shape->edgeCount; struct TT_Edge * intersects = malloc(sizeof(struct TT_Edge) * size); struct TT_Intersection * crosses = malloc(sizeof(struct TT_Intersection) * size); - float * subsamples = malloc(sizeof(float) * ctx->width); - size_t subsample_width = shape->lastX - shape->startX + 1; + size_t subsample_width = shape->lastX - shape->startX; + float * subsamples = malloc(sizeof(float) * subsample_width); memset(subsamples, 0, sizeof(float) * subsample_width); - /* We have sorted by the scanline at which the line becomes active, so we should be able to do this... */ + int startY = shape->startY < 0 ? 0 : shape->startY; + int endY = shape->lastY <= ctx->height ? shape->lastY : ctx->height; + int yres = 4; - for (int y = shape->startY; y < shape->lastY; ++y) { + for (int y = startY; y < endY; ++y) { /* Figure out which ones fit here */ float _y = y; - int start_x = ctx->width; - int max_x = 0; for (int l = 0; l < yres; ++l) { size_t cnt = prune_edges(size, _y, shape->edges, intersects); if (cnt) { @@ -128,12 +149,9 @@ void tt_path_paint(gfx_context_t * ctx, struct TT_Shape * shape, uint32_t color) /* Now sort the intersections */ sort_intersections(cnt, crosses); - if (crosses[0].x < start_x) start_x = crosses[0].x; - if (crosses[cnt-1].x+1 > max_x) max_x = crosses[cnt-1].x+1; - int wind = 0; size_t j = 0; - for (int x = 0; x < ctx->width && j < cnt; ++x) { + for (int x = shape->startX; x < shape->lastX && j < cnt; ++x) { while (j < cnt && x > crosses[j].x) { wind += crosses[j].affect; j++; @@ -154,9 +172,8 @@ void tt_path_paint(gfx_context_t * ctx, struct TT_Shape * shape, uint32_t color) } _y += 1.0/(float)yres; } - if (start_x < 0) start_x = 0; - for (int x = start_x; x < max_x && x < ctx->width; ++x) { - if (x < 0 || y < 0 || x >= ctx->width || y >= ctx->height) return; + for (int x = shape->startX; x < shape->lastX && x < ctx->width; ++x) { + if (x < 0 || y < 0 || x >= ctx->width || y >= ctx->height) continue; #ifdef __toaru__ unsigned int c = subsamples[x - shape->startX] / (float)yres * (float)_ALP(color); uint32_t nc = premultiply((color & 0xFFFFFF) | ((c & 0xFF) << 24)); @@ -267,29 +284,6 @@ struct TT_Shape * tt_contour_finish(struct TT_Contour * in) { return tmp; } -/** - * Opaque data struct. - */ -struct TT_Font { - int privFlags; - FILE * filePtr; - uint8_t * buffer; - uint8_t * memPtr; - - struct TT_Table head_ptr; - struct TT_Table cmap_ptr; - struct TT_Table loca_ptr; - struct TT_Table glyf_ptr; - struct TT_Table hhea_ptr; - struct TT_Table hmtx_ptr; - - off_t cmap_start; - - size_t cmap_maxInd; - - float scale; -}; - static inline int tt_seek(struct TT_Font * font, off_t offset) { if (font->privFlags & 1) { return fseek(font->filePtr, offset, SEEK_SET); @@ -666,6 +660,10 @@ static int tt_font_load(struct TT_Font * font) { font->hmtx_ptr.offset = offset; font->hmtx_ptr.length = length; break; + case 0x6e616d65: /* name */ + font->name_ptr.offset = offset; + font->name_ptr.length = length; + break; } } @@ -777,3 +775,83 @@ void tt_draw_string_shadow(gfx_context_t * ctx, struct TT_Font * font, char * st sprite_free(_tmp_s); tt_draw_string(ctx, font, left, top + font_size, string, text_color); } + +static int to_eight(uint32_t codepoint, char * out) { + memset(out, 0x00, 7); + + if (codepoint < 0x0080) { + out[0] = (char)codepoint; + } else if (codepoint < 0x0800) { + out[0] = 0xC0 | (codepoint >> 6); + out[1] = 0x80 | (codepoint & 0x3F); + } else if (codepoint < 0x10000) { + out[0] = 0xE0 | (codepoint >> 12); + out[1] = 0x80 | ((codepoint >> 6) & 0x3F); + out[2] = 0x80 | (codepoint & 0x3F); + } else if (codepoint < 0x200000) { + out[0] = 0xF0 | (codepoint >> 18); + out[1] = 0x80 | ((codepoint >> 12) & 0x3F); + out[2] = 0x80 | ((codepoint >> 6) & 0x3F); + out[3] = 0x80 | ((codepoint) & 0x3F); + } else if (codepoint < 0x4000000) { + out[0] = 0xF8 | (codepoint >> 24); + out[1] = 0x80 | (codepoint >> 18); + out[2] = 0x80 | ((codepoint >> 12) & 0x3F); + out[3] = 0x80 | ((codepoint >> 6) & 0x3F); + out[4] = 0x80 | ((codepoint) & 0x3F); + } else { + out[0] = 0xF8 | (codepoint >> 30); + out[1] = 0x80 | ((codepoint >> 24) & 0x3F); + out[2] = 0x80 | ((codepoint >> 18) & 0x3F); + out[3] = 0x80 | ((codepoint >> 12) & 0x3F); + out[4] = 0x80 | ((codepoint >> 6) & 0x3F); + out[5] = 0x80 | ((codepoint) & 0x3F); + } + + return strlen(out); +} + + +char * tt_get_name_string(struct TT_Font * font, int identifier) { + if (!font->name_ptr.offset) return NULL; + + tt_seek(font, font->name_ptr.offset); + uint16_t nameFormat = tt_read_16(font); + uint16_t count = tt_read_16(font); + uint16_t stringOffset = tt_read_16(font); + + if (nameFormat != 0) return NULL; /* Unsupported table format */ + + /* Read records until we find one that matches what we asked for, in a suitable format */ + for (unsigned int i = 0; i < count; ++i) { + uint16_t platformId = tt_read_16(font); + uint16_t platformSpecificId = tt_read_16(font); + /* uint16_t languageId = */ tt_read_16(font); + uint16_t nameId = tt_read_16(font); + uint16_t length = tt_read_16(font); + uint16_t offset = tt_read_16(font); + + if (nameId != identifier) continue; + if (!(platformId == 3 && platformSpecificId == 1)) continue; + + char * tmp = calloc(length * 3 + 1,1); /* Should be enough ? */ + char * c = tmp; + + tt_seek(font, stringOffset + offset + font->name_ptr.offset); + + for (unsigned int j = 0; j < length; j += 2) { + uint32_t cp = tt_read_16(font); + if (cp > 0xD7FF && cp < 0xE000) { + uint32_t highBits = cp - 0xD800; + uint32_t lowBits = tt_read_16(font) - 0xDC00; + cp = 0x10000 + (highBits << 10) + lowBits; + j += 2; + } + c += to_eight(cp, c); + } + + return tmp; + } + + return NULL; +}