font-preview: tool for viewing TrueType fonts

This commit is contained in:
K. Lange 2021-07-06 19:43:09 +09:00
parent 2b08195002
commit 7b902c26fe
3 changed files with 306 additions and 39 deletions

View File

@ -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");

188
apps/font-preview.c Normal file
View File

@ -0,0 +1,188 @@
/**
* @brief TrueType font previewer
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <toaru/yutani.h>
#include <toaru/graphics.h>
#include <toaru/decorations.h>
#include <toaru/menu.h>
#include <toaru/text.h>
/* 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;
}

View File

@ -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;
}