toaruos/lib/markup_text.c

312 lines
9.8 KiB
C

/**
* @brief Marked up text label renderer.
*
* @copyright
* This file is part of ToaruOS and is released under the terms
* of the NCSA / University of Illinois License - see LICENSE.md
* Copyright (C) 2021 K. Lange
*/
#include <math.h>
#include <toaru/markup.h>
#include <toaru/list.h>
#include <toaru/graphics.h>
#include <toaru/text.h>
#include <toaru/decodeutf8.h>
#include "toaru/markup_text.h"
static struct TT_Font * dejaVuSans = NULL;
static struct TT_Font * dejaVuSans_Bold = NULL;
static struct TT_Font * dejaVuSans_Oblique = NULL;
static struct TT_Font * dejaVuSans_BoldOblique = NULL;
static struct TT_Font * dejaVuSansMono = NULL;
static struct TT_Font * dejaVuSansMono_Bold = NULL;
static struct TT_Font * dejaVuSansMono_Oblique = NULL;
static struct TT_Font * dejaVuSansMono_BoldOblique = NULL;
struct MarkupState {
struct markup_state * parser;
list_t * state;
int current_state;
int cursor_x;
int cursor_y;
int initial_left;
uint32_t color;
gfx_context_t * ctx;
int max_cursor_x;
list_t * colors;
int sizes[3];
int dryrun;
};
static void push_state(struct MarkupState * state, int val) {
list_insert(state->state, (void*)(uintptr_t)state->current_state);
state->current_state |= val;
}
static void pop_state(struct MarkupState * state) {
node_t * nstate = list_pop(state->state);
state->current_state = (int)(uintptr_t)nstate->value;
free(nstate);
}
static uint32_t parseColor(const char * c) {
if (*c != '#' || strlen(c) != 7) return rgba(0,0,0,255);
char r[3] = {c[1],c[2],'\0'};
char g[3] = {c[3],c[4],'\0'};
char b[3] = {c[5],c[6],'\0'};
return rgba(strtoul(r,NULL,16),strtoul(g,NULL,16),strtoul(b,NULL,16),255);
}
static int parser_open(struct markup_state * self, void * user, struct markup_tag * tag) {
struct MarkupState * state = (struct MarkupState*)user;
if (!strcmp(tag->name, "b")) {
push_state(state, MARKUP_TEXT_STATE_BOLD);
} else if (!strcmp(tag->name, "i")) {
push_state(state, MARKUP_TEXT_STATE_OBLIQUE);
} else if (!strcmp(tag->name, "h1")) {
push_state(state, MARKUP_TEXT_STATE_HEADING);
} else if (!strcmp(tag->name, "small")) {
push_state(state, MARKUP_TEXT_STATE_SMALL);
} else if (!strcmp(tag->name, "mono")) {
push_state(state, MARKUP_TEXT_STATE_MONO);
} else if (!strcmp(tag->name, "br")) {
state->cursor_x = state->initial_left;
state->cursor_y += 20; /* state->line_height? */
} else if (!strcmp(tag->name, "color")) {
/* get options */
list_t * args = hashmap_keys(tag->options);
if (args->length == 1) {
list_insert(state->colors, (void*)(uintptr_t)state->color);
state->color = parseColor((char*)args->head->value);
}
free(args);
}
markup_free_tag(tag);
return 0;
}
static int parser_close(struct markup_state * self, void * user, char * tag_name) {
struct MarkupState * state = (struct MarkupState*)user;
if (!strcmp(tag_name, "b")) {
pop_state(state);
} else if (!strcmp(tag_name, "i")) {
pop_state(state);
} else if (!strcmp(tag_name, "h1")) {
pop_state(state);
} else if (!strcmp(tag_name, "small")) {
pop_state(state);
} else if (!strcmp(tag_name, "mono")) {
pop_state(state);
} else if (!strcmp(tag_name, "color")) {
node_t * ncolor = list_pop(state->colors);
state->color = (uint32_t)(uintptr_t)ncolor->value;
free(ncolor);
}
return 0;
}
static struct TT_Font * fontForState(struct MarkupState * state) {
int bold = !!(state->current_state & MARKUP_TEXT_STATE_BOLD);
int obli = !!(state->current_state & MARKUP_TEXT_STATE_OBLIQUE);
int mono = !!(state->current_state & MARKUP_TEXT_STATE_MONO);
if (mono) {
if (bold && obli) return dejaVuSansMono_BoldOblique;
if (bold) return dejaVuSansMono_Bold;
if (obli) return dejaVuSansMono_Oblique;
return dejaVuSansMono;
} else {
if (bold && obli) return dejaVuSans_BoldOblique;
if (bold) return dejaVuSans_Bold;
if (obli) return dejaVuSans_Oblique;
return dejaVuSans;
}
}
static int sizeForState(struct MarkupState * state) {
if (state->current_state & MARKUP_TEXT_STATE_HEADING) return state->sizes[2];
if (state->current_state & MARKUP_TEXT_STATE_SMALL) return state->sizes[1];
return state->sizes[0];
}
struct GlyphCacheEntry {
struct TT_Font * font;
sprite_t * sprites[3];
int xs[3];
uint32_t size;
uint32_t glyph;
uint32_t color;
int y;
};
static struct GlyphCacheEntry glyph_cache[1024];
static void draw_cached_glyph(gfx_context_t * ctx, struct TT_Font * _font, uint32_t size, int x, int y, uint32_t glyph, uint32_t fg, float xadj) {
unsigned int hash = (((uintptr_t)_font >> 8) ^ (glyph * size)) & 1023;
struct GlyphCacheEntry * entry = &glyph_cache[hash];
if (entry->font != _font || entry->size != size || entry->glyph != glyph) {
if (entry->sprites[0]) sprite_free(entry->sprites[0]);
if (entry->sprites[1]) sprite_free(entry->sprites[1]);
if (entry->sprites[2]) sprite_free(entry->sprites[2]);
tt_set_size(_font, size);
entry->font = _font;
entry->size = size;
entry->glyph = glyph;
entry->color = _ALP(fg) == 255 ? fg : rgb(0,0,0);
entry->sprites[0] = tt_bake_glyph(entry->font, entry->glyph, entry->color, &entry->xs[0], &entry->y, 0.0);
entry->sprites[1] = tt_bake_glyph(entry->font, entry->glyph, entry->color, &entry->xs[1], &entry->y, 0.333);
entry->sprites[2] = tt_bake_glyph(entry->font, entry->glyph, entry->color, &entry->xs[2], &entry->y, 0.666);
}
if (entry->sprites[0]) {
int sprite = xadj < 0.166 ? 0 : xadj < 0.5 ? 1 : 2;
if (entry->color != fg) {
draw_sprite_alpha_paint(ctx, entry->sprites[sprite], x + entry->xs[sprite], y + entry->y, 1.0, fg);
} else {
draw_sprite(ctx, entry->sprites[sprite], x + entry->xs[sprite], y + entry->y);
}
}
}
static int string_draw_internal(gfx_context_t * ctx, struct TT_Font * font, int font_size, int x, int y, char * data, uint32_t color) {
float x_offset = x;
uint32_t cp = 0;
uint32_t istate = 0;
for (const unsigned char * c = (const unsigned char*)data; *c; ++c) {
if (!decode(&istate, &cp, *c)) {
unsigned int glyph = tt_glyph_for_codepoint(font, cp);
draw_cached_glyph(ctx, font, font_size, (int)floor(x_offset), y, glyph, color, x_offset-floor(x_offset));
x_offset += tt_glyph_width(font, glyph);
}
}
return x_offset - x;
}
static int parser_data(struct markup_state * self, void * user, char * data) {
struct MarkupState * state = (struct MarkupState*)user;
struct TT_Font * font = fontForState(state);
int size = sizeForState(state);
tt_set_size(font, size);
state->cursor_x += string_draw_internal(state->ctx, font, size, state->cursor_x, state->cursor_y, data, state->color);
if (state->cursor_x > state->max_cursor_x) state->max_cursor_x = state->cursor_x;
return 0;
}
static int parser_dryrun(struct markup_state * self, void * user, char * data) {
struct MarkupState * state = (struct MarkupState*)user;
struct TT_Font * font = fontForState(state);
tt_set_size(font, sizeForState(state));
state->cursor_x += tt_string_width(font, data);
if (state->cursor_x > state->max_cursor_x) state->max_cursor_x = state->cursor_x;
return 0;
}
struct MarkupState * markup_setup_renderer(gfx_context_t * ctx, int x, int y, uint32_t color, int dryrun) {
struct MarkupState * state = malloc(sizeof(struct MarkupState));
state->parser = markup_init(state, parser_open, parser_close, dryrun ? parser_dryrun : parser_data);
state->state = list_create();
state->current_state = 0;
state->cursor_x = x;
state->cursor_y = y;
state->initial_left = x;
state->color = color;
state->ctx = ctx;
state->max_cursor_x = x;
state->colors = list_create();
state->sizes[0] = 13;
state->sizes[1] = 10;
state->sizes[2] = 18;
state->dryrun = dryrun;
return state;
}
void markup_set_base_font_size(struct MarkupState * state, int size) {
state->sizes[0] = size;
state->sizes[1] = 10 * size / 13;
state->sizes[2] = 18 * size / 13;
}
void markup_set_base_state(struct MarkupState * state, int mode) {
state->current_state = mode;
}
int markup_push_string(struct MarkupState * state, const char * str) {
while (*str) {
if (markup_parse(state->parser, *str++)) {
break;
}
}
return state->max_cursor_x - state->initial_left;
}
int markup_push_raw_string(struct MarkupState * state, const char * str) {
if (state->dryrun) {
return parser_dryrun(state->parser, state, (char*)str);
} else {
return parser_data(state->parser, state, (char*)str);
}
}
int markup_finish_renderer(struct MarkupState * state) {
markup_finish(state->parser);
list_free(state->state);
list_free(state->colors);
free(state->state);
free(state->colors);
int total = state->max_cursor_x - state->initial_left;
free(state);
return total;
}
int markup_string_width(const char * str) {
struct MarkupState * state = markup_setup_renderer(NULL,0,0,0,1);
while (*str) {
if (markup_parse(state->parser, *str++)) {
break;
}
}
return markup_finish_renderer(state);
}
int markup_string_height(const char * str) {
struct MarkupState * state = markup_setup_renderer(NULL,0,0,0,1);
while (*str) {
if (markup_parse(state->parser, *str++)) {
break;
}
}
int out = state->cursor_y;
markup_finish_renderer(state);
return out;
}
int markup_draw_string(gfx_context_t * ctx, int x, int y, const char * str, uint32_t color) {
struct MarkupState * state = markup_setup_renderer(ctx,x,y,color,0);
while (*str) {
if (markup_parse(state->parser, *str++)) {
break;
}
}
return markup_finish_renderer(state);
}
void markup_text_init(void) {
if (!dejaVuSans) {
dejaVuSans = tt_font_from_shm("sans-serif");
dejaVuSans_Bold = tt_font_from_shm("sans-serif.bold");
dejaVuSans_Oblique = tt_font_from_shm("sans-serif.italic");
dejaVuSans_BoldOblique = tt_font_from_shm("sans-serif.bolditalic");
dejaVuSansMono = tt_font_from_shm("monospace");
dejaVuSansMono_Bold = tt_font_from_shm("monospace.bold");
dejaVuSansMono_Oblique = tt_font_from_shm("monospace.italic");
dejaVuSansMono_BoldOblique = tt_font_from_shm("monospace.bolditalic");
}
}