font-preview: tool for viewing TrueType fonts
This commit is contained in:
parent
2b08195002
commit
7b902c26fe
@ -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
188
apps/font-preview.c
Normal 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;
|
||||
}
|
||||
|
156
lib/text.c
156
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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user