/* * mui_font.c * * Copyright (C) 2023 Michel Pollet * * SPDX-License-Identifier: MIT */ #include #include #include #define STB_TRUETYPE_IMPLEMENTATION #define STB_TTC_IMPLEMENTATION #include "stb_ttc.h" #define INCBIN_STYLE INCBIN_STYLE_SNAKE #define INCBIN_PREFIX mui_ #include "incbin.h" INCBIN(main_font, "fonts/Charcoal_mui.ttf"); INCBIN(icon_font, "fonts/typicon.ttf"); INCBIN(dingbat_font, "fonts/Dingbat.ttf"); INCBIN(geneva_font, "fonts/Geneva.ttf"); #include "mui.h" mui_font_t * mui_font_find( mui_t *ui, const char *name) { mui_font_t *f; TAILQ_FOREACH(f, &ui->fonts, self) { if (!strcmp(f->name, name)) return f; } return NULL; } static void _mui_font_pixman_prep( mui_font_t *f) { f->font.pix.bpp = 8; f->font.pix.size.x = f->ttc.p_stride; f->font.pix.size.y = f->ttc.p_height; f->font.pix.row_bytes = f->ttc.p_stride; f->font.pix.pixels = f->ttc.pixels; } mui_font_t * mui_font_from_mem( mui_t *ui, const char *name, unsigned int size, const void *font_data, unsigned int font_size ) { mui_font_t *f = calloc(1, sizeof(*f)); f->name = strdup(name); f->size = size; stb_ttc_LoadFont(&f->ttc, font_data, font_size); TAILQ_INSERT_TAIL(&ui->fonts, f, self); // printf("%s: Loaded font %s:%d\n", __func__, name, size); return f; } void mui_font_init( mui_t *ui) { // printf("%s: Loading fonts\n", __func__); mui_font_from_mem(ui, "main", 28, mui_main_font_data, mui_main_font_size); mui_font_from_mem(ui, "icon_large", 96, mui_icon_font_data, mui_icon_font_size); mui_font_from_mem(ui, "icon_small", 30, mui_icon_font_data, mui_icon_font_size); } void mui_font_dispose( mui_t *ui) { mui_font_t *f; while ((f = TAILQ_FIRST(&ui->fonts))) { TAILQ_REMOVE(&ui->fonts, f, self); stb_ttc_Free(&f->ttc); free(f->name); free(f); } } int mui_font_text_measure( mui_font_t *font, const char *text, stb_ttc_measure *m ) { struct stb_ttc_info * ttc = &font->ttc; float scale = stbtt_ScaleForPixelHeight(&ttc->font, font->size); int w = stb_ttc_MeasureText(ttc, scale, text, m); return w; } void mui_font_text_draw( mui_font_t *font, mui_drawable_t *dr, c2_pt_t where, const char *text, unsigned int text_len, mui_color_t color) { struct stb_ttc_info * ttc = &font->ttc; unsigned int state = 0; float scale = stbtt_ScaleForPixelHeight(&ttc->font, font->size); double xpos = 0; unsigned int last = 0; unsigned int cp = 0; if (!text_len) text_len = strlen(text); mui_drawable_t * src = &font->font; mui_drawable_t * dst = dr; pixman_color_t pc = PIXMAN_COLOR(color); pixman_image_t * fill = pixman_image_create_solid_fill(&pc); where.y += font->ttc.ascent * scale; for (unsigned int ch = 0; text[ch] && ch < text_len; ch++) { if (stb_ttc__UTF8_Decode(&state, &cp, text[ch]) != UTF8_ACCEPT) continue; if (last) { int kern = scale * stb_ttc__CodepointsGetKerning(ttc, last, cp); xpos += kern; } last = cp; int gl = stb_ttc__CodepointGetGlyph(ttc, cp); if (gl == -1) continue; stb_ttc_g *gc = stb_ttc__ScaledGlyphGetCache(ttc, gl, scale); if (!gc) continue; if (gc->p_y == (unsigned short) -1) stb_ttc__ScaledGlyphRenderToCache(ttc, gc); // int pxpos = gc->x0 + ((xpos + gc->lsb) * scale); int pxpos = where.x + gc->x0 + ((xpos + 0) * scale); // if (gc->lsb) // printf("glyph %3d : %04x:%c lsb %d\n", ch, cp, cp < 32 ? '.' : cp, gc->lsb); int ph = gc->y1 - gc->y0; int pw = gc->x1 - gc->x0; _mui_font_pixman_prep(font); pixman_image_composite32( PIXMAN_OP_OVER, fill, mui_drawable_get_pixman(src), mui_drawable_get_pixman(dst), 0, 0, gc->p_x, gc->p_y, pxpos, where.y + gc->y0, pw, ph); xpos += gc->advance; } pixman_image_unref(fill); } IMPLEMENT_C_ARRAY(mui_glyph_array); IMPLEMENT_C_ARRAY(mui_glyph_line_array); void mui_font_measure( mui_font_t *font, c2_rect_t bbox, const char *text, unsigned int text_len, mui_glyph_line_array_t *lines, uint16_t flags) { struct stb_ttc_info * ttc = &font->ttc; unsigned int state = 0; float scale = stbtt_ScaleForPixelHeight(&ttc->font, font->size); unsigned int last = 0; unsigned int cp = 0; if (!text_len) text_len = strlen(text); c2_pt_t where = {}; unsigned int ch = 0; int wrap_chi = 0; int wrap_w = 0; int wrap_count = 0; do { where.y += font->ttc.ascent * scale; const mui_glyph_array_t zero = {}; mui_glyph_line_array_push(lines, zero); mui_glyph_array_t * line = &lines->e[lines->count - 1]; line->x = 0; line->y = where.y; line->w = 0; wrap_chi = ch; wrap_w = 0; wrap_count = 0; for (;text[ch]; ch++) { if (stb_ttc__UTF8_Decode(&state, &cp, text[ch]) != UTF8_ACCEPT) continue; if (last) { int kern = scale * stb_ttc__CodepointsGetKerning(ttc, last, cp); line->w += kern; } last = cp; // printf("glyph %3d : %04x:%c\n", ch, cp, cp < 32 ? '.' : cp); if (cp == '\n') { ch++; break; } if (isspace(cp) || ispunct(cp)) { wrap_chi = ch; wrap_w = line->w; wrap_count = line->count; } int gl = stb_ttc__CodepointGetGlyph(ttc, cp); if (gl == -1) continue; stb_ttc_g *gc = stb_ttc__ScaledGlyphGetCache(ttc, gl, scale); if (!gc) continue; if (gc->p_y == (unsigned short) -1) stb_ttc__ScaledGlyphRenderToCache(ttc, gc); if (((line->w + gc->advance) * scale) > c2_rect_width(&bbox)) { if (wrap_count) { ch = wrap_chi + 1; line->count = wrap_count; line->w = wrap_w; } break; } line->w += gc->advance; mui_glyph_array_push(line, gc->index); }; } while (text[ch] && ch < text_len); int bh = 0; for (int i = 0; i < (int)lines->count; i++) { mui_glyph_array_t * line = &lines->e[i]; bh = line->y - (font->ttc.descent * scale); line->w *= scale; // printf(" line %d y %3d size %d width %d\n", i, // line->y, line->count, line->w); } // printf("box height is %d/%d\n", bh, c2_rect_height(&bbox)); int ydiff = 0; if (flags & MUI_TEXT_ALIGN_MIDDLE) { ydiff = (c2_rect_height(&bbox) - bh) / 2; } else if (flags & MUI_TEXT_ALIGN_BOTTOM) { ydiff = c2_rect_height(&bbox) - bh; } for (int i = 0; i < (int)lines->count; i++) { mui_glyph_array_t * line = &lines->e[i]; line->y += ydiff; if (flags & MUI_TEXT_ALIGN_RIGHT) { line->x = c2_rect_width(&bbox) - line->w; } else if (flags & MUI_TEXT_ALIGN_CENTER) { line->x = (c2_rect_width(&bbox) - line->w) / 2; } } } void mui_font_measure_clear( mui_glyph_line_array_t *lines) { if (!lines) return; for (int i = 0; i < (int)lines->count; i++) { mui_glyph_array_t * line = &lines->e[i]; mui_glyph_array_free(line); } mui_glyph_line_array_free(lines); } void mui_font_measure_draw( mui_font_t *font, mui_drawable_t *dr, c2_rect_t bbox, mui_glyph_line_array_t *lines, mui_color_t color, uint16_t flags) { pixman_color_t pc = PIXMAN_COLOR(color); pixman_image_t * fill = pixman_image_create_solid_fill(&pc); struct stb_ttc_info * ttc = &font->ttc; float scale = stbtt_ScaleForPixelHeight(&ttc->font, font->size); mui_drawable_t * src = &font->font; mui_drawable_t * dst = dr; // all glyphs we need were loaded, update the pixman texture _mui_font_pixman_prep(font); for (int li = 0; li < (int)lines->count; li++) { mui_glyph_array_t * line = &lines->e[li]; int xpos = 0;//where.x / scale; for (int ci = 0; ci < (int)line->count; ci++) { unsigned int cache_index = line->e[ci]; stb_ttc_g *gc = &ttc->glyph[cache_index]; // int pxpos = gc->x0 + ((xpos + gc->lsb) * scale); int pxpos = gc->x0 + ((xpos + 0) * scale); int ph = gc->y1 - gc->y0; int pw = gc->x1 - gc->x0; pixman_image_composite32( PIXMAN_OP_OVER, fill, mui_drawable_get_pixman(src), mui_drawable_get_pixman(dst), 0, 0, gc->p_x, gc->p_y, bbox.l + line->x + pxpos, bbox.t + line->y + gc->y0, pw, ph); xpos += gc->advance; } } pixman_image_unref(fill); } void mui_font_textbox( mui_font_t *font, mui_drawable_t *dr, c2_rect_t bbox, const char *text, unsigned int text_len, mui_color_t color, uint16_t flags) { mui_glyph_line_array_t lines = {}; if (!text_len) text_len = strlen(text); mui_font_measure(font, bbox, text, text_len, &lines, flags); mui_font_measure_draw(font, dr, bbox, &lines, color, flags); mui_font_measure_clear(&lines); }