aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2021-12-08 03:37:27 +0100
committerdec05eba <dec05eba@protonmail.com>2021-12-08 03:37:27 +0100
commit319accba370d3f0872501de6386833fea140fbac (patch)
treef024ac1070837693b39b6624eea3bd104f8a0f0b
parent864ee5f167d1e2dda9bfce24ef617d71ce49bfd8 (diff)
Add richtext, support multi language
m---------depends/mgl0
-rw-r--r--include/mgui/richtext.h38
-rw-r--r--include/mgui/widget.h1
-rw-r--r--src/mgui/richtext.c266
-rw-r--r--src/mgui/scrollview.c6
-rw-r--r--src/mgui/widget.c12
-rw-r--r--src/resource_loader.c12
-rw-r--r--tests/main.c7
8 files changed, 333 insertions, 9 deletions
diff --git a/depends/mgl b/depends/mgl
-Subproject a77da0acc88c7fb861043fd0dcb9cc6536e591f
+Subproject e52886f8bea55fb3c9ff973b16ed812549cd1f2
diff --git a/include/mgui/richtext.h b/include/mgui/richtext.h
new file mode 100644
index 0000000..52706f5
--- /dev/null
+++ b/include/mgui/richtext.h
@@ -0,0 +1,38 @@
+#ifndef MGUI_RICHTEXT_H
+#define MGUI_RICHTEXT_H
+
+#include "widget.h"
+#include <stddef.h>
+#include <stdbool.h>
+
+typedef struct mgl_vertex mgl_vertex;
+
+typedef struct {
+ mgl_vertex *vertices;
+ size_t vertices_capacity;
+ size_t vertex_count;
+} mgui_richtext_vertex_data;
+
+typedef struct {
+ mgui_widget widget;
+ char *str;
+ size_t str_size;
+ unsigned int character_size;
+ mgl_vec2i position;
+ mgl_vec2i render_size;
+ int width;
+ mgui_richtext_vertex_data vertex_data[2];
+ bool dirty;
+} mgui_richtext;
+
+mgui_richtext* mgui_richtext_create(const char *str, size_t size, unsigned char character_size);
+mgui_widget* mgui_richtext_to_widget(mgui_richtext *list);
+mgui_richtext* mgui_widget_to_richtext(mgui_widget *widget);
+
+void mgui_richtext_set_position(mgui_richtext *self, mgl_vec2i position);
+void mgui_richtext_set_width(mgui_richtext *self, int width);
+void mgui_richtext_on_event(mgui_richtext *self, mgl_window *window, mgl_event *event);
+/* Returns the size of the widget */
+mgl_vec2i mgui_richtext_draw(mgui_richtext *self, mgl_window *window);
+
+#endif /* MGUI_RICHTEXT_H */
diff --git a/include/mgui/widget.h b/include/mgui/widget.h
index 2edc3b1..ee3a9f1 100644
--- a/include/mgui/widget.h
+++ b/include/mgui/widget.h
@@ -12,6 +12,7 @@ typedef enum {
MGUI_WIDGET_SCROLLVIEW,
MGUI_WIDGET_BUTTON,
MGUI_WIDGET_LABEL,
+ MGUI_WIDGET_RICHTEXT,
MGUI_WIDGET_IMAGE
} mgui_widget_type;
diff --git a/src/mgui/richtext.c b/src/mgui/richtext.c
new file mode 100644
index 0000000..2f7c163
--- /dev/null
+++ b/src/mgui/richtext.c
@@ -0,0 +1,266 @@
+#include "../../include/mgui/richtext.h"
+#include "../../include/resource_loader.h"
+#include "../../include/common.h"
+#include "../../include/alloc.h"
+#include <mgl/mgl.h>
+#include <mgl/window/event.h>
+#include <mgl/graphics/vertex.h>
+#include <mgl/graphics/font.h>
+#include <mgl/graphics/texture.h>
+#include <mgl/system/utf8.h>
+#include <string.h>
+#include <assert.h>
+
+#define TAB_WIDTH 4
+#define LATIN_INDEX 0
+#define CJK_INDEX 1
+#define NUM_VERTEX_DATA 2
+
+/* TODO: Scale richtext by scale setting */
+/* TODO: Split multiple lines into multiple vertex draw calls and do not render the vertices outside the scissor */
+/* TODO: Cleanup vertex when deleting mgui_richtext widget */
+
+static int round_float(float value) {
+ return value + 0.5f;
+}
+
+static int max_int(int a, int b) {
+ return a >= b ? a : b;
+}
+
+/* TODO: Is there a more efficient way to do this? maybe japanese characters have a specific bit-pattern? */
+static bool is_japanese_codepoint(uint32_t codepoint) {
+ return (codepoint >= 0x2E80 && codepoint <= 0x2FD5) /* Kanji radicals */
+ || (codepoint >= 0x3000 && codepoint <= 0x303F) /* Punctuation */
+ || (codepoint >= 0x3041 && codepoint <= 0x3096) /* Hiragana */
+ || (codepoint >= 0x30A0 && codepoint <= 0x30FF) /* Katakana */
+ || (codepoint >= 0x31F0 && codepoint <= 0x31FF) /* Miscellaneous symbols and characters 1 */
+ || (codepoint >= 0x3220 && codepoint <= 0x3243) /* Miscellaneous symbols and characters 2 */
+ || (codepoint >= 0x3280 && codepoint <= 0x337F) /* Miscellaneous symbols and characters 3 */
+ || (codepoint >= 0x3400 && codepoint <= 0x4DB5) /* Kanji 1 */
+ || (codepoint >= 0x4E00 && codepoint <= 0x9FCB) /* Kanji 2 */
+ || (codepoint >= 0xF900 && codepoint <= 0xFA6A) /* Kanji 3 */
+ || (codepoint >= 0xFF01 && codepoint <= 0xFF5E) /* Alphanumeric and punctuation (full width) */
+ || (codepoint >= 0xFF5F && codepoint <= 0xFF9F); /* Katakana and punctuation (half width) */
+}
+
+static bool is_korean_codepoint(uint32_t codepoint) {
+ return codepoint >= 0xAC00 && codepoint <= 0xD7A3;
+}
+
+/* TODO: Is there a more efficient way to do this? maybe chinese characters have a specific bit-pattern? */
+static bool is_chinese_codepoint(uint32_t codepoint) {
+ return (codepoint >= 0x4E00 && codepoint <= 0x9FFF) /* CJK Unified Ideographs */
+ || (codepoint >= 0x3400 && codepoint <= 0x4DBF) /* CJK Unified Ideographs Extension A */
+ || (codepoint >= 0x20000 && codepoint <= 0x2A6DF) /* CJK Unified Ideographs Extension B */
+ || (codepoint >= 0x2A700 && codepoint <= 0x2B73F) /* CJK Unified Ideographs Extension C */
+ || (codepoint >= 0x2B740 && codepoint <= 0x2B81F) /* CJK Unified Ideographs Extension D */
+ || (codepoint >= 0x2B820 && codepoint <= 0x2CEAF) /* CJK Unified Ideographs Extension E */
+ || (codepoint >= 0xF900 && codepoint <= 0xFAFF) /* CJK Compatibility Ideographs */
+ || (codepoint >= 0x2F800 && codepoint <= 0x2FA1F); /* CJK Compatibility Ideographs Supplement */
+}
+
+/* TODO: Merge chinese, japanese and korean codepoints into one function since they share ranges */
+static bool is_cjk_codepoint(uint32_t codepoint) {
+ return is_chinese_codepoint(codepoint) || is_japanese_codepoint(codepoint) || is_korean_codepoint(codepoint);
+}
+
+static void mgui_richtext_vertices_ensure_vertex(mgui_richtext *self, size_t vertex_index, size_t new_capacity) {
+ mgui_richtext_vertex_data *vertex_data = &self->vertex_data[vertex_index];
+ if(vertex_data->vertices_capacity >= new_capacity)
+ return;
+
+ size_t capacity = vertex_data->vertices_capacity;
+ if(capacity == 0)
+ capacity = 8;
+
+ while(capacity < new_capacity) {
+ capacity = capacity + (capacity >> 1); /* capacity *= 1.5 */
+ }
+
+ vertex_data->vertices = mgui_realloc(vertex_data->vertices, capacity);
+ vertex_data->vertices_capacity = capacity;
+}
+
+static void mgui_richtext_vertices_append(mgui_richtext *self, size_t vertex_index, const mgl_vertex *vertex) {
+ mgui_richtext_vertex_data *vertex_data = &self->vertex_data[vertex_index];
+ mgui_richtext_vertices_ensure_vertex(self, vertex_index, (vertex_data->vertex_count + 1) * sizeof(mgl_vertex));
+ vertex_data->vertices[vertex_data->vertex_count] = *vertex;
+ ++vertex_data->vertex_count;
+}
+
+static void mgui_richtext_vertices_clear(mgui_richtext *self, size_t vertex_index) {
+ self->vertex_data[vertex_index].vertex_count = 0;
+}
+
+mgui_richtext* mgui_richtext_create(const char *str, size_t size, unsigned char character_size) {
+ mgui_richtext *richtext = mgui_alloc(sizeof(mgui_richtext));
+ mgui_widget_init(&richtext->widget, MGUI_WIDGET_RICHTEXT);
+ richtext->str = mgui_alloc(size);
+ richtext->str_size = size;
+ richtext->character_size = character_size;
+ memcpy(richtext->str, str, size);
+ richtext->position = (mgl_vec2i){ 0, 0 };
+ richtext->render_size = (mgl_vec2i){ 0, 0 };
+ richtext->width = 0;
+ for(size_t i = 0; i < NUM_VERTEX_DATA; ++i) {
+ richtext->vertex_data[i].vertices = NULL;
+ richtext->vertex_data[i].vertices_capacity = 0;
+ richtext->vertex_data[i].vertex_count = 0;
+ }
+ richtext->dirty = true;
+ return richtext;
+}
+
+mgui_widget* mgui_richtext_to_widget(mgui_richtext *list) {
+ return &list->widget;
+}
+
+mgui_richtext* mgui_widget_to_richtext(mgui_widget *widget) {
+ assert(widget->type == MGUI_WIDGET_RICHTEXT);
+ return (mgui_richtext*)widget;
+}
+
+void mgui_richtext_set_position(mgui_richtext *self, mgl_vec2i position) {
+ self->position = position;
+}
+
+void mgui_richtext_set_width(mgui_richtext *self, int width) {
+ self->width = width;
+}
+
+void mgui_richtext_on_event(mgui_richtext *self, mgl_window *window, mgl_event *event) {
+ /* TODO: Implement */
+ (void)self;
+ (void)window;
+ (void)event;
+}
+
+static void mgui_richtext_append_glyph(mgui_richtext *self, size_t vertex_index, mgl_vec2f position, mgl_color color, mgl_font_glyph *glyph) {
+ const mgl_vertex top_left_vertex = {
+ .position = (mgl_vec2f){ round_float(position.x + glyph->position.x), round_float(position.y + glyph->position.y) },
+ .texcoords = (mgl_vec2f){ round_float(glyph->texture_position.x), round_float(glyph->texture_position.y) },
+ .color = color
+ };
+
+ const mgl_vertex top_right_vertex = {
+ .position = (mgl_vec2f){ round_float(position.x + glyph->position.x + glyph->size.x), round_float(position.y + glyph->position.y) },
+ .texcoords = (mgl_vec2f){ round_float(glyph->texture_position.x) + round_float(glyph->texture_size.x), round_float(glyph->texture_position.y) },
+ .color = color
+ };
+
+ const mgl_vertex bottom_left_vertex = {
+ .position = (mgl_vec2f){ round_float(position.x + glyph->position.x), round_float(position.y + glyph->position.y + glyph->size.y) },
+ .texcoords = (mgl_vec2f){ round_float(glyph->texture_position.x), round_float(glyph->texture_position.y + glyph->texture_size.y) },
+ .color = color
+ };
+
+ const mgl_vertex bottom_right_vertex = {
+ .position = (mgl_vec2f){ round_float(position.x + glyph->position.x + glyph->size.x), round_float(position.y + glyph->position.y + glyph->size.y) },
+ .texcoords = (mgl_vec2f){ round_float(glyph->texture_position.x + glyph->texture_size.x), round_float(glyph->texture_position.y + glyph->texture_size.y) },
+ .color = color
+ };
+
+ mgui_richtext_vertices_append(self, vertex_index, &top_right_vertex);
+ mgui_richtext_vertices_append(self, vertex_index, &top_left_vertex);
+ mgui_richtext_vertices_append(self, vertex_index, &bottom_left_vertex);
+ mgui_richtext_vertices_append(self, vertex_index, &bottom_left_vertex);
+ mgui_richtext_vertices_append(self, vertex_index, &bottom_right_vertex);
+ mgui_richtext_vertices_append(self, vertex_index, &top_right_vertex);
+}
+
+static void mgui_richtext_update(mgui_richtext *self) {
+ for(size_t i = 0; i < NUM_VERTEX_DATA; ++i) {
+ mgui_richtext_vertices_clear(self, i);
+ }
+
+ mgl_font *font = NULL;
+ mgl_vec2f position = (mgl_vec2f){ 0.0f, 0.0f };
+ mgl_font_glyph glyph;
+ uint32_t prev_codepoint = 0;
+ size_t codepoint_index = 0;
+ mgl_color color = (mgl_color){ 255, 255, 255, 255 };
+ int vertex_index = -1;
+ self->render_size = (mgl_vec2i){ 0, self->character_size };
+
+ const mgui_font_type font_types[NUM_VERTEX_DATA] = {
+ MGUI_FONT_LATIN,
+ MGUI_FONT_CJK
+ };
+
+ for(size_t i = 0; i < self->str_size;) {
+ unsigned char *cp = (unsigned char*)&self->str[i];
+ uint32_t codepoint;
+ size_t clen;
+ if(!mgl_utf8_decode(cp, self->str_size - i, &codepoint, &clen)) {
+ codepoint = *cp;
+ clen = 1;
+ }
+
+ int new_vertex_index;
+ if(is_cjk_codepoint(codepoint)) {
+ new_vertex_index = CJK_INDEX;
+ } else {
+ new_vertex_index = LATIN_INDEX;
+ }
+
+ if(new_vertex_index != vertex_index) {
+ vertex_index = new_vertex_index;
+ font = mgui_get_font(font_types[vertex_index], self->character_size);
+ }
+
+ if(codepoint == '\t') {
+ if(mgl_font_get_glyph(font, ' ', &glyph) == 0) {
+ position.x += (glyph.advance * TAB_WIDTH);
+ self->render_size.x = max_int(self->render_size.x, position.x);
+ }
+ } else if(codepoint == '\n') {
+ position.x = 0;
+ position.y += self->character_size;
+ self->render_size.y += self->character_size;
+ } else {
+ if(mgl_font_get_glyph(font, codepoint, &glyph) == 0) {
+ if(position.x + glyph.size.x > self->width) {
+ //position.x = 0;
+ //position.y += self->character_size;
+ }
+
+ mgui_richtext_append_glyph(self, vertex_index, position, color, &glyph);
+ position.x += glyph.advance + mgl_font_get_kerning(font, prev_codepoint, codepoint);
+ self->render_size.x = max_int(self->render_size.x, position.x);
+ }
+ }
+
+ i += clen;
+ ++codepoint_index;
+ prev_codepoint = codepoint;
+ }
+}
+
+mgl_vec2i mgui_richtext_draw(mgui_richtext *self, mgl_window *window) {
+ /* TODO: Do not update if not visible on screen? */
+ if(self->dirty) {
+ self->dirty = false;
+ mgui_richtext_update(self);
+ }
+
+ if(mgui_rectangle_intersects_with_scissor(self->position, self->render_size, window)) {
+ const mgui_font_type font_types[NUM_VERTEX_DATA] = {
+ MGUI_FONT_LATIN,
+ MGUI_FONT_CJK
+ };
+
+ for(size_t i = 0; i < NUM_VERTEX_DATA; ++i) {
+ if(self->vertex_data[i].vertex_count == 0)
+ continue;
+
+ const mgl_font *font = mgui_get_font(font_types[i], self->character_size);
+ mgl_texture_use(&font->texture);
+ mgl_vertices_draw(mgl_get_context(), self->vertex_data[i].vertices, self->vertex_data[i].vertex_count, MGL_PRIMITIVE_TRIANGLES, (mgl_vec2f){ self->position.x, self->position.y });
+ }
+
+ mgl_texture_use(NULL);
+ }
+
+ return self->render_size;
+}
diff --git a/src/mgui/scrollview.c b/src/mgui/scrollview.c
index ae9ad97..e5b8bb8 100644
--- a/src/mgui/scrollview.c
+++ b/src/mgui/scrollview.c
@@ -35,6 +35,7 @@ mgui_scrollview* mgui_widget_to_scrollview(mgui_widget *widget) {
void mgui_scrollview_set_child(mgui_scrollview *self, mgui_widget *child) {
assert(child != mgui_scrollview_to_widget(self));
self->child = child;
+ mgui_scrollview_set_width(self, self->size.x);
}
void mgui_scrollview_set_position(mgui_scrollview *self, mgl_vec2i position) {
@@ -43,12 +44,13 @@ void mgui_scrollview_set_position(mgui_scrollview *self, mgl_vec2i position) {
void mgui_scrollview_set_size(mgui_scrollview *self, mgl_vec2i size) {
self->size = size;
+ mgui_scrollview_set_width(self, self->size.x);
}
void mgui_scrollview_set_width(mgui_scrollview *self, int width) {
/* TODO: Call for child so text can wordwrap? but only if wordwrap is enabled in this scrollview */
- (void)self;
- (void)width;
+ if(self->child)
+ mgui_widget_set_width(self->child, width);
}
void mgui_scrollview_on_event(mgui_scrollview *self, mgl_window *window, mgl_event *event) {
diff --git a/src/mgui/widget.c b/src/mgui/widget.c
index e9c8425..8a49715 100644
--- a/src/mgui/widget.c
+++ b/src/mgui/widget.c
@@ -3,6 +3,7 @@
#include "../../include/mgui/scrollview.h"
#include "../../include/mgui/button.h"
#include "../../include/mgui/label.h"
+#include "../../include/mgui/richtext.h"
#include "../../include/mgui/image.h"
/* TODO: Use margin */
@@ -33,6 +34,9 @@ void mgui_widget_set_position(mgui_widget *self, mgl_vec2i position) {
case MGUI_WIDGET_LABEL:
mgui_label_set_position(mgui_widget_to_label(self), position);
break;
+ case MGUI_WIDGET_RICHTEXT:
+ mgui_richtext_set_position(mgui_widget_to_richtext(self), position);
+ break;
case MGUI_WIDGET_IMAGE:
mgui_image_set_position(mgui_widget_to_image(self), position);
break;
@@ -53,6 +57,9 @@ void mgui_widget_set_width(mgui_widget *self, int width) {
case MGUI_WIDGET_LABEL:
mgui_label_set_width(mgui_widget_to_label(self), width);
break;
+ case MGUI_WIDGET_RICHTEXT:
+ mgui_richtext_set_width(mgui_widget_to_richtext(self), width);
+ break;
case MGUI_WIDGET_IMAGE:
mgui_image_set_width(mgui_widget_to_image(self), width);
break;
@@ -73,6 +80,9 @@ void mgui_widget_on_event(mgui_widget *self, mgl_window *window, mgl_event *even
case MGUI_WIDGET_LABEL:
mgui_label_on_event(mgui_widget_to_label(self), window, event);
break;
+ case MGUI_WIDGET_RICHTEXT:
+ mgui_richtext_on_event(mgui_widget_to_richtext(self), window, event);
+ break;
case MGUI_WIDGET_IMAGE:
mgui_image_on_event(mgui_widget_to_image(self), window, event);
break;
@@ -89,6 +99,8 @@ mgl_vec2i mgui_widget_draw(mgui_widget *self, mgl_window *window) {
return mgui_button_draw(mgui_widget_to_button(self), window);
case MGUI_WIDGET_LABEL:
return mgui_label_draw(mgui_widget_to_label(self), window);
+ case MGUI_WIDGET_RICHTEXT:
+ return mgui_richtext_draw(mgui_widget_to_richtext(self), window);
case MGUI_WIDGET_IMAGE:
return mgui_image_draw(mgui_widget_to_image(self), window);
}
diff --git a/src/resource_loader.c b/src/resource_loader.c
index d589373..2eb0c01 100644
--- a/src/resource_loader.c
+++ b/src/resource_loader.c
@@ -22,24 +22,28 @@ mgl_font* mgui_get_font(mgui_font_type type, unsigned int character_size) {
if(!font_file) {
const char *font_paths[2];
size_t num_font_paths = 0;
+ const char *font_name = "";
switch(type) {
case MGUI_FONT_LATIN: {
font_paths[0] = "/usr/share/fonts/noto/NotoSans-Regular.ttf";
font_paths[1] = "/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf";
num_font_paths = 2;
+ font_name = "NotoSans-Regular.ttf";
break;
}
case MGUI_FONT_LATIN_BOLD: {
font_paths[0] = "/usr/share/fonts/noto/NotoSans-Bold.ttf";
font_paths[1] = "/usr/share/fonts/truetype/noto/NotoSans-Bold.ttf";
num_font_paths = 2;
+ font_name = "NotoSans-Bold.ttf";
break;
}
case MGUI_FONT_CJK: {
- font_paths[0] = "/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttf";
- font_paths[1] = "/usr/share/fonts/truetype/noto-cjk/NotoSansCJK-Regular.ttf";
+ font_paths[0] = "/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc";
+ font_paths[1] = "/usr/share/fonts/truetype/noto-cjk/NotoSansCJK-Regular.ttc";
num_font_paths = 2;
+ font_name = "NotoSansCJK-Regular.ttc";
break;
}
}
@@ -60,13 +64,13 @@ mgl_font* mgui_get_font(mgui_font_type type, unsigned int character_size) {
}
if(!successfully_loaded_file)
- fprintf(stderr, "mgui warning: mgui_get_font failed to load font %d\n", (int)type);
+ fprintf(stderr, "mgui warning: mgui_get_font failed to load font %s\n", font_name);
font_file_cache[type] = new_font_file;
font_file = new_font_file;
}
- if(!font_file)
+ if(!font_file->data)
return NULL;
mgl_font *font = font_cache[type][character_size];
diff --git a/tests/main.c b/tests/main.c
index eb1c24f..e1aa6f1 100644
--- a/tests/main.c
+++ b/tests/main.c
@@ -3,6 +3,7 @@
#include "../include/mgui/scrollview.h"
#include "../include/mgui/button.h"
#include "../include/mgui/label.h"
+#include "../include/mgui/richtext.h"
#include <mgl/mgl.h>
#include <mgl/window/window.h>
#include <mgl/window/event.h>
@@ -16,8 +17,8 @@ static mgui_list* create_list_item(const char *title, const char *description) {
mgui_list *list = mgui_list_create(MGUI_LIST_VERTICAL);
mgui_list_append(container, mgui_list_to_widget(list));
- mgui_list_append(list, mgui_label_to_widget(mgui_label_create(title, strlen(title), 30)));
- mgui_list_append(list, mgui_label_to_widget(mgui_label_create(description, strlen(description), 24)));
+ mgui_list_append(list, mgui_richtext_to_widget(mgui_richtext_create(title, strlen(title), 30)));
+ mgui_list_append(list, mgui_richtext_to_widget(mgui_richtext_create(description, strlen(description), 24)));
return container;
}
@@ -36,7 +37,7 @@ int main() {
mgui_scrollview_set_child(scrollview, mgui_list_to_widget(list));
for(int i = 0; i < 30; ++i) {
char text[256];
- snprintf(text, sizeof(text), "hello world %d", i);
+ snprintf(text, sizeof(text), "hello world 東京 %d", i);
mgui_list_append(list, mgui_list_to_widget(create_list_item("User", text)));
}