/* text.c Font text management Copyright (C) 2022 Bill Currie Author: Bill Currie Date: 2022/8/26 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include "QF/mathlib.h" #include "QF/quakefs.h" #include "QF/sys.h" #include "QF/ui/font.h" #include "QF/ui/passage.h" #include "QF/ui/text.h" #include "QF/ui/view.h" #include "compat.h" static void text_features_create (void *_features) { featureset_t *features = _features; *features = (featureset_t) DARRAY_STATIC_INIT (4); } static void text_features_destroy (void *_features) { featureset_t *features = _features; DARRAY_CLEAR (features); } static const component_t text_components[text_type_count] = { [text_href] = { .size = sizeof (hierref_t), .name = "href", }, [text_passage_glyphs] = { .size = sizeof (glyphset_t), .name = "passage glyphs", }, [text_glyphs] = { .size = sizeof (glyphref_t), .name = "glyphs", }, [text_script] = { .size = sizeof (script_component_t), .name = "script", }, [text_font] = { .size = sizeof (font_t *), .name = "font", }, [text_features] = { .size = sizeof (featureset_t), .name = "features", .create = text_features_create, .destroy = text_features_destroy, }, }; ecs_registry_t *text_reg; void Text_Init (void) { text_reg = ECS_NewRegistry (); ECS_RegisterComponents (text_reg, text_components, text_type_count); text_reg->href_comp = text_href; } typedef struct glyphnode_s { struct glyphnode_s *next; uint32_t ent; uint32_t count; glyphobj_t *glyphs; int mins[2]; int maxs[2]; } glyphnode_t; static view_resize_f text_flow_funcs[] = { [text_right_down] = view_flow_right_down, [text_left_down] = view_flow_left_down, [text_down_right] = view_flow_down_right, [text_up_right] = view_flow_up_right, [text_right_up] = view_flow_right_up, [text_left_up] = view_flow_left_up, [text_down_left] = view_flow_down_left, [text_up_left] = view_flow_up_left, }; view_t Text_View (font_t *font, passage_t *passage) { hierarchy_t *h = passage->hierarchy; psg_text_t *text_objects = h->components[passage_type_text_obj]; glyphnode_t *glyph_nodes = 0; glyphnode_t **head = &glyph_nodes; hb_font_t *fnt = hb_ft_font_create (font->face, 0); hb_buffer_t *buffer = hb_buffer_create (); hb_buffer_allocation_successful (buffer); hb_script_t psg_script = HB_SCRIPT_LATIN; text_dir_e psg_direction = HB_DIRECTION_LTR; hb_language_t psg_language = hb_language_from_string ("en", 2); featureset_t passage_features = DARRAY_STATIC_INIT (0); featureset_t *psg_features = &passage_features; uint32_t glyph_count = 0; // passages have only the root, paragraphs, and text objects in their // hierarchies for (uint32_t i = 0; i < h->childCount[0]; i++) { uint32_t paragraph = h->childIndex[0] + i; uint32_t para_ent = h->ent[paragraph]; hb_script_t para_script = psg_script; text_dir_e para_direction = psg_direction; hb_language_t para_language = psg_language; featureset_t *para_features = psg_features;; if (Ent_HasComponent (para_ent, text_script, text_reg)) { script_component_t *s = Ent_GetComponent (para_ent, text_script, text_reg); para_script = s->script; para_language = s->language; para_direction = s->direction; } if (Ent_HasComponent (para_ent, text_features, text_reg)) { para_features = Ent_GetComponent (para_ent, text_features, text_reg); } for (uint32_t j = 0; j < h->childCount[paragraph]; j++) { uint32_t textind = h->childIndex[paragraph] + j; uint32_t text_ent = h->ent[textind]; psg_text_t *textobj = &text_objects[textind]; const char *str = passage->text + textobj->text; uint32_t len = textobj->size; hb_script_t txt_script = para_script; text_dir_e txt_direction = para_direction; hb_language_t txt_language = para_language; featureset_t *txt_features = para_features;; if (Ent_HasComponent (text_ent, text_script, text_reg)) { script_component_t *s = Ent_GetComponent (text_ent, text_script, text_reg); txt_script = s->script; txt_language = s->language; txt_direction = s->direction; } if (Ent_HasComponent (text_ent, text_features, text_reg)) { txt_features = Ent_GetComponent (text_ent, text_features, text_reg); } hb_buffer_reset (buffer); hb_buffer_set_direction (buffer, txt_direction | HB_DIRECTION_LTR); hb_buffer_set_script (buffer, txt_script); hb_buffer_set_language (buffer, txt_language); hb_buffer_add_utf8 (buffer, str, len, 0, len); hb_shape (fnt, buffer, txt_features->a, txt_features->size); unsigned c; __auto_type glyphInfo = hb_buffer_get_glyph_infos (buffer, &c); __auto_type glyphPos = hb_buffer_get_glyph_positions (buffer, &c); *head = alloca (sizeof (glyphnode_t)); **head = (glyphnode_t) { .ent = h->ent[textind], .count = c, .glyphs = alloca (c * sizeof (glyphobj_t)), }; glyph_count += c; int x = 0, y = 0; for (unsigned k = 0; k < c; k++) { uint32_t glyphid = glyphInfo[k].codepoint; vec2i_t bearing = font->glyph_bearings[glyphid]; int xp = x + glyphPos[k].x_offset / 64; int yp = y + glyphPos[k].y_offset / 64; int xa = glyphPos[k].x_advance / 64; int ya = glyphPos[k].y_advance / 64; xp += bearing[0]; yp -= bearing[1]; int xm = xp + font->glyph_rects[glyphid].width; int ym = yp + font->glyph_rects[glyphid].height; (*head)->mins[0] = min ((*head)->mins[0], xp); (*head)->mins[1] = min ((*head)->mins[1], yp); (*head)->maxs[0] = max ((*head)->maxs[0], xm); (*head)->maxs[1] = max ((*head)->maxs[1], ym); (*head)->glyphs[k] = (glyphobj_t) { .glyphid = glyphid, .x = xp + bearing[0], .y = yp - bearing[1], .fontid = font->fontid, }; x += xa; y += ya; } head = &(*head)->next; } } view_t passage_view = View_New (text_reg, nullview); glyphref_t passage_ref = {}; glyphobj_t *glyphs = malloc (glyph_count * sizeof (glyphobj_t)); glyphnode_t *g = glyph_nodes; // paragraph flow int psg_vertical = !!(psg_direction & 2); for (uint32_t i = 0; i < h->childCount[0]; i++) { uint32_t paragraph = h->childIndex[0] + i; view_t paraview = View_New (text_reg, passage_view); glyphref_t pararef = { .start = passage_ref.count }; for (uint32_t j = 0; j < h->childCount[paragraph]; j++, g = g->next) { view_t textview = View_New (text_reg, paraview); glyphref_t glyph_ref = { .start = passage_ref.count, .count = g->count, }; memcpy (glyphs + passage_ref.count, g->glyphs, g->count * sizeof (glyphobj_t)); Ent_SetComponent (textview.id, text_glyphs, text_reg, &glyph_ref); View_SetPos (textview, g->mins[0], g->mins[1]); View_SetLen (textview, g->maxs[0] - g->mins[0], g->maxs[1] - g->mins[1]); View_SetGravity (textview, grav_flow); passage_ref.count += g->count; } pararef.count = passage_ref.count - pararef.start; Ent_SetComponent (paraview.id, text_glyphs, text_reg, ¶ref); uint32_t para_ent = h->ent[paragraph]; text_dir_e para_direction = psg_direction; if (Ent_HasComponent (para_ent, text_script, text_reg)) { script_component_t *s = Ent_GetComponent (para_ent, text_script, text_reg); para_direction = s->direction; } View_SetOnResize (paraview, text_flow_funcs[para_direction]); View_SetResize (paraview, psg_vertical, !psg_vertical); } Ent_SetComponent (passage_view.id, text_glyphs, text_reg, &passage_ref); glyphset_t glyphset = { .glyphs = glyphs, .count = glyph_count, }; Ent_SetComponent (passage_view.id, text_passage_glyphs, text_reg, &glyphset); return passage_view; } void Text_SetScript (int textid, const char *lang, hb_script_t script, text_dir_e dir) { script_component_t scr = { .language = hb_language_from_string (lang, strlen (lang)), .script = script, .direction = dir, }; Ent_SetComponent (textid, text_script, text_reg, &scr); } void Text_SetFont (int textid, font_t *font) { Ent_SetComponent (textid, text_font, text_reg, &font); } void Text_SetFeatures (int textid, featureset_t *features) { if (Ent_HasComponent (textid, text_features, text_reg)) { Ent_RemoveComponent (textid, text_features, text_reg); } featureset_t *f = Ent_SetComponent (textid, text_features, text_reg, 0); DARRAY_RESIZE (f, features->size); memcpy (f->a, features->a, features->size * sizeof (hb_feature_t)); } void Text_AddFeature (int textid, hb_feature_t feature) { if (!Ent_HasComponent (textid, text_features, text_reg)) { Ent_SetComponent (textid, text_features, text_reg, 0); } featureset_t *f = Ent_GetComponent (textid, text_features, text_reg); DARRAY_APPEND (f, feature); }