From 0db858e814fe0dab7326ec577b883cc9f7481ab9 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sat, 14 Dec 2024 01:06:58 +0100 Subject: [PATCH] use libgrapheme to interpret utf8 encoded strings --- lib/libgrapheme.c3l/libgrapheme.c3i | 2 +- project.json | 7 ++-- src/main.c3 | 4 +- src/ugui_cmd.c3 | 57 ++++++++++++++++++++++++++-- src/ugui_data.c3 | 1 - src/ugui_text.c3 | 58 +++++++++++++---------------- 6 files changed, 84 insertions(+), 45 deletions(-) diff --git a/lib/libgrapheme.c3l/libgrapheme.c3i b/lib/libgrapheme.c3l/libgrapheme.c3i index 076fb4b..6b1c7fc 100644 --- a/lib/libgrapheme.c3l/libgrapheme.c3i +++ b/lib/libgrapheme.c3l/libgrapheme.c3i @@ -1,6 +1,6 @@ module grapheme; -const uint GRAPHEME_INVALID_CODEPOINT UINT32_C = 0xFFFD; +const uint GRAPHEME_INVALID_CODEPOINT = 0xFFFD; enum BidirectionalDirection { GRAPHEME_BIDIRECTIONAL_DIRECTION_NEUTRAL, diff --git a/project.json b/project.json index 0e48c97..6c11f55 100644 --- a/project.json +++ b/project.json @@ -6,7 +6,7 @@ // Directories where C3 library files may be found. "dependency-search-paths": [ "lib" ], // Libraries to use for all targets. - "dependencies": [ "raylib", "schrift" ], + "dependencies": [ "raylib", "schrift", "grapheme" ], "features": [ // See rcore.c3 //"SUPPORT_INTERNAL_MEMORY_MANAGEMENT", @@ -17,13 +17,12 @@ //"SUPPORT_TEXT_CODEPOINTS_MANAGEMENT", //"SUPPORT_TEXT_C_STRING_MANAGEMENT", //"SUPPORT_RANDOM_GENERATION", - - "SUPPORT_RAYGUI", + //"SUPPORT_RAYGUI", //"RAYGUI_NO_ICONS", //"RAYGUI_CUSTOM_ICONS", ], // Authors, optionally with email. - "authors": [ "John Doe " ], + "authors": [ "John Doe " ], // Version using semantic versioning. "version": "0.1.0", // Sources compiled for all targets. diff --git a/src/main.c3 b/src/main.c3 index 49876a3..cb37cbf 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -27,7 +27,7 @@ fn int main(String[] args) { ugui::Ctx ui; ui.init()!!; - ui.font.load("/usr/share/fonts/TTF/Hack-Regular.ttf", 16, scale: 1.5)!!; + ui.font.load("/usr/share/fonts/NerdFonts/ttf/HackNerdFont-Regular.ttf", 16, scale: 1.5)!!; short width = 800; short height = 450; @@ -94,7 +94,7 @@ fn int main(String[] args) io::printfn("slider: %f", e.slider.value); } - ui.text_unbounded("text1", "Ciao Mamma\nSono a Casa")!!; + ui.text_unbounded("text1", "Ciao Mamma\nAbilità ⚡")!!; |}; ui.div_end()!!; diff --git a/src/ugui_cmd.c3 b/src/ugui_cmd.c3 index 8857c5a..d882121 100644 --- a/src/ugui_cmd.c3 +++ b/src/ugui_cmd.c3 @@ -1,13 +1,15 @@ module ugui; +import std::ascii; + // FIXME: is this really the best solution? // "rect" is the bounding box of the element, which includes the border and the padding (so not just the content) fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, bool do_padding = false, bool do_radius = false) { - // FIXME: this should be culled higher up, maybe - if (rect.w <= 0 || rect.h <= 0) { - return; - } + // FIXME: this should be culled higher up, maybe + if (rect.w <= 0 || rect.h <= 0) { + return; + } Rect border = ctx.style.border; Rect padding = ctx.style.padding; @@ -48,3 +50,50 @@ fn void! Ctx.push_sprite(&ctx, Rect bounds, Rect texture) }; ctx.cmd_queue.enqueue(&cmd)!; } + +fn void! Ctx.push_string(&ctx, Rect bounds, String text) +{ + if (text.len == 0) { + return; + } + + short baseline = (short)ctx.font.ascender; + short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; + short line_gap = (short)ctx.font.linegap; + Point orig = { + .x = bounds.x, + .y = bounds.y, + }; + + short line_len; + Codepoint cp; + usz off, x; + while ((cp = str_to_codepoint(text[off..], &x)) != 0) { + off += x; + Glyph* gp; + if (!ascii::is_cntrl((char)cp)) { + gp = ctx.font.get_glyph(cp)!; + Rect gb = { + .x = orig.x + line_len + gp.ox, + .y = orig.y + gp.oy + baseline, + .w = gp.w, + .h = gp.h, + }; + Rect gt = { + .x = gp.u, + .y = gp.v, + .w = gp.w, + .h = gp.h, + }; + if (rect_collision(gb, bounds)) { + ctx.push_sprite(gb, gt)!; + } + line_len += gp.adv; + } else if (cp == '\n'){ + orig.y += line_height + line_gap; + line_len = 0; + } else { + continue; + } + } +} diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 index e61b16f..bcfad9c 100644 --- a/src/ugui_data.c3 +++ b/src/ugui_data.c3 @@ -1,6 +1,5 @@ module ugui; -import std::io; import std::core::string; import vtree; diff --git a/src/ugui_text.c3 b/src/ugui_text.c3 index 97efbae..cd5ba97 100644 --- a/src/ugui_text.c3 +++ b/src/ugui_text.c3 @@ -1,6 +1,22 @@ module ugui; import std::io; +import std::ascii; +import grapheme; + +<* +@require off != null +*> +fn Codepoint str_to_codepoint(char[] str, usz* off) +{ + Codepoint cp; + isz b = grapheme::decode_utf8(str, str.len, &cp); + if (b == 0 || b > str.len) { + return 0; + } + *off = b; + return cp; +} fn Rect! Ctx.get_text_bounds(&ctx, String text, bool* update_atlas) { @@ -12,16 +28,20 @@ fn Rect! Ctx.get_text_bounds(&ctx, String text, bool* update_atlas) // TODO: account for unicode codepoints short line_len; - foreach (c: text) { - Codepoint cp = (Codepoint)c; + Codepoint cp; + usz off, x; + while ((cp = str_to_codepoint(text[off..], &x)) != 0) { + off += x; bool n; - if (cp != '\n') { + if (!ascii::is_cntrl((char)cp)) { gp = ctx.font.get_glyph(cp, &n)!; line_len += gp.adv; if (n) { *update_atlas = true; } - } else { + } else if (cp == '\n'){ text_bounds.h += line_height + line_gap; line_len = 0; + } else { + continue; } if (line_len > text_bounds.w) { text_bounds.w = line_len; @@ -79,33 +99,5 @@ fn void! Ctx.text_unbounded(&ctx, String label, String text) }; ctx.cmd_queue.enqueue(&bounds)!; - Point orig = { - .x = c_elem.bounds.x, - .y = c_elem.bounds.y, - }; - short line_len; - foreach (c: text) { - Glyph* gp; - Codepoint cp = (Codepoint)c; - if (cp != '\n') { - gp = ctx.font.get_glyph(cp)!; - Rect gb = { - .x = orig.x + line_len + gp.ox, - .y = orig.y + gp.oy + baseline, - .w = gp.w, - .h = gp.h, - }; - Rect gt = { - .x = gp.u, - .y = gp.v, - .w = gp.w, - .h = gp.h, - }; - ctx.push_sprite(gb, gt)!; - line_len += gp.adv; - } else { - orig.y += line_height + line_gap; - line_len = 0; - } - } + ctx.push_string(c_elem.bounds, text)!; }