[ui] Move text handling into gui lib

And add a function to process a passage into a set of views with glyphs.
The views can be flowed: they have flow gravity and their sizes set to
contain all the glyphs within each view (nominally, words). Nothing is
tested yet, and font rendering is currently broken completely.
This commit is contained in:
Bill Currie 2022-12-08 15:33:50 +09:00
parent 136bf882f6
commit 95f55dfc34
9 changed files with 350 additions and 150 deletions

View file

@ -1,7 +1,7 @@
/*
r_text.h
text.h
Renderer text management
Text management
Copyright (C) 2022 Bill Currie <bill@taniwha.org>
@ -28,8 +28,8 @@
*/
#ifndef __r_text_h
#define __r_text_h
#ifndef __QF_ui_text_h
#define __QF_ui_text_h
#include <hb.h>
#include <hb-ft.h>
@ -44,30 +44,54 @@ typedef struct script_component_s {
hb_direction_t direction;
} script_component_t;
typedef struct glyph_component_s {
int id;
typedef struct glyphobj_s {
int glyphid;
int x, y;
} glyph_component_t;
int fontid;
} glyphobj_t;
typedef struct rtext_s {
typedef struct glyphref_s {
uint32_t start;
uint32_t count;
} glyphref_t;
typedef struct glyphset_s {
glyphobj_t *glyphs;
uint32_t count;
} glyphset_t;
typedef struct text_s {
const char *text;
const char *language;
hb_script_t script;
hb_direction_t direction;
} rtext_t;
} text_t;
typedef struct r_hb_featureset_s DARRAY_TYPE (hb_feature_t) r_hb_featureset_t;
enum {
// covers both view and passage text object hierarcies
text_href,
// all the glyhphs in a passage
text_passage,
text_glyphs,
text_script,
text_font,
text_features,
text_type_count
};
typedef struct featureset_s DARRAY_TYPE (hb_feature_t) featureset_t;
struct font_s;
struct rglyph_s;
typedef void rtext_render_t (uint32_t glyphid, int x, int y, void *data);
typedef void text_render_t (uint32_t glyphid, int x, int y, void *data);
typedef struct rshaper_s {
typedef struct shaper_s {
struct font_s *rfont;
hb_font_t *font;
hb_buffer_t *buffer;
r_hb_featureset_t features;
} rshaper_t;
featureset_t features;
} shaper_t;
extern hb_feature_t LigatureOff;
extern hb_feature_t LigatureOn;
@ -76,11 +100,16 @@ extern hb_feature_t KerningOn;
extern hb_feature_t CligOff;
extern hb_feature_t CligOn;
rshaper_t *RText_NewShaper (struct font_s *font);
void RText_DeleteShaper (rshaper_t *shaper);
void RText_AddFeature (rshaper_t *shaper, hb_feature_t feature);
void RText_ShapeText (rshaper_t *shaper, rtext_t *text);
void RText_RenderText (rshaper_t *shaper, rtext_t *text, int x, int y,
rtext_render_t *render, void *data);
struct font_s;
struct passage_s;
#endif//__r_text_h
shaper_t *Text_NewShaper (struct font_s *font);
void Text_DeleteShaper (shaper_t *shaper);
void Text_AddFeature (shaper_t *shaper, hb_feature_t feature);
void Text_ShapeText (shaper_t *shaper, text_t *text);
void Text_RenderText (shaper_t *shaper, text_t *text, int x, int y,
text_render_t *render, void *data);
void Text_Init (void);
struct view_s Text_View (struct font_s *font, struct passage_s *passage);
#endif//__QF_ui_text_h

View file

@ -15,7 +15,8 @@ libs_ui_libQFgui_la_LDFLAGS= $(lib_ldflags)
libs_ui_libQFgui_la_LIBADD= $(gui_deps) $(ui_deps)
libs_ui_libQFgui_la_DEPENDENCIES= $(gui_deps) $(ui_deps)
libs_ui_libQFgui_la_SOURCES= \
libs/ui/font.c
libs/ui/font.c \
libs/ui/text.c
libs_ui_libQFui_la_LDFLAGS= $(lib_ldflags)
libs_ui_libQFui_la_LIBADD= $(ui_deps)

282
libs/ui/text.c Normal file
View file

@ -0,0 +1,282 @@
/*
text.c
Font text management
Copyright (C) 2022 Bill Currie <bill@taniwha.org>
Author: Bill Currie <bill@taniwha.org>
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 <math.h>
#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 const component_t text_components[text_type_count] = {
[text_href] = {
.size = sizeof (hierref_t),
.name = "href",
},
[text_passage] = {
.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,
},
};
static ecs_registry_t *text_reg;
void
Text_Init (void)
{
text_reg = ECS_NewRegistry ();
ECS_RegisterComponents (text_reg, text_components, text_type_count);
}
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;
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);
/*XXX*/hb_script_t script = HB_SCRIPT_LATIN;
/*XXX*/hb_direction_t direction = HB_DIRECTION_LTR;
/*XXX*/hb_language_t language = hb_language_from_string ("en", 2);
hb_buffer_set_direction (buffer, direction);
hb_buffer_set_script (buffer, script);
hb_buffer_set_language (buffer, language);
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;
for (uint32_t j = 0; j < h->childCount[paragraph]; j++) {
uint32_t textind = h->childIndex[0] + j;
psg_text_t *textobj = &text_objects[textind];
const char *str = passage->text + textobj->text;
uint32_t len = textobj->size;
hb_buffer_reset (buffer);
hb_buffer_add_utf8 (buffer, str, len, 0, len);
hb_shape (fnt, buffer, 0, 0);
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],
};
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;
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, &pararef);
}
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, text_reg, &glyphset);
return passage_view;
}
shaper_t *
Text_NewShaper (font_t *font)
{
shaper_t *shaper = malloc (sizeof (shaper_t));
shaper->rfont = font;
shaper->font = hb_ft_font_create (font->face, 0);
hb_ft_font_set_funcs (shaper->font);
shaper->buffer = hb_buffer_create ();
shaper->features = (featureset_t) DARRAY_STATIC_INIT (4);
hb_buffer_allocation_successful (shaper->buffer);
return shaper;
}
void
Text_DeleteShaper (shaper_t *shaper)
{
hb_buffer_destroy (shaper->buffer);
hb_font_destroy (shaper->font);
DARRAY_CLEAR (&shaper->features);
free (shaper);
}
void
Text_AddFeature (shaper_t *shaper, hb_feature_t feature)
{
DARRAY_APPEND (&shaper->features, feature);
}
void
Text_ShapeText (shaper_t *shaper, text_t *text)
{
hb_buffer_reset (shaper->buffer);
hb_buffer_set_direction (shaper->buffer, text->direction);
hb_buffer_set_script (shaper->buffer, text->script);
const char *lang = text->language;
hb_buffer_set_language (shaper->buffer,
hb_language_from_string (lang, strlen (lang)));
size_t length = strlen (text->text);
hb_buffer_add_utf8 (shaper->buffer, text->text, length, 0, length);
hb_shape (shaper->font, shaper->buffer,
shaper->features.a, shaper->features.size);
}
void
Text_RenderText (shaper_t *shaper, text_t *text, int x, int y,
text_render_t *render, void *data)
{
Text_ShapeText (shaper, text);
unsigned count;
__auto_type glyphInfo = hb_buffer_get_glyph_infos (shaper->buffer, &count);
__auto_type glyphPos = hb_buffer_get_glyph_positions (shaper->buffer,
&count);
font_t *font = shaper->rfont;
for (unsigned i = 0; i < count; i++) {
vec2i_t bearing = font->glyph_bearings[glyphInfo[i].codepoint];
int xp = x + glyphPos[i].x_offset / 64;
int yp = y + glyphPos[i].y_offset / 64;
int xa = glyphPos[i].x_advance / 64;
int ya = glyphPos[i].y_advance / 64;
xp += bearing[0];
yp -= bearing[1];
render (glyphInfo[i].codepoint, xp, yp, data);
x += xa;
y += ya;
}
}

View file

@ -32,7 +32,6 @@ video_renderer_common_sources = \
libs/video/renderer/r_ent.c \
libs/video/renderer/r_iqm.c \
libs/video/renderer/r_sprite.c \
libs/video/renderer/r_text.c \
libs/video/renderer/vid_common.c
renderer_libs= \

View file

@ -60,7 +60,6 @@
#include "QF/ui/view.h"
#include "compat.h"
#include "r_text.h"
#include "r_internal.h"
#include "varrays.h"
@ -1076,7 +1075,7 @@ gl_Draw_AddFont (font_t *rfont)
font->texid = GL_LoadTex ("", 0, &tex);
return fontid;
}
#if 0
typedef struct {
int fontid;
byte color[4];
@ -1106,10 +1105,11 @@ gl_render_glyph (uint32_t glyphid, int x, int y, void *_rgctx)
qfglTexCoord2f (u * s, (v + h) * t);
qfglVertex2f (x, y + h);
}
#endif
void
gl_Draw_FontString (int x, int y, int fontid, const char *str)
{
#if 0
if (fontid < 0 || (unsigned) fontid > gl_fonts.size) {
return;
}
@ -1134,4 +1134,5 @@ gl_Draw_FontString (int x, int y, int fontid, const char *str)
qfglEnd ();
qfglColor4ubv (color_white);
#endif
}

View file

@ -55,7 +55,6 @@
#include "QF/GLSL/qf_textures.h"
#include "QF/GLSL/qf_vid.h"
#include "r_text.h"
#include "r_internal.h"
typedef struct cachepic_s {
@ -930,7 +929,7 @@ glsl_Draw_AddFont (font_t *rfont)
font->texid = GLSL_LoadTex ("", 1, &tex);
return fontid;
}
#if 0
typedef struct {
vrect_t *glyph_rects;
dstring_t *batch;
@ -984,10 +983,11 @@ glsl_render_glyph (uint32_t glyphid, int x, int y, void *_rgctx)
QuatCopy (rgctx->color, verts[4].color);
QuatCopy (rgctx->color, verts[5].color);
}
#endif
void
glsl_Draw_FontString (int x, int y, int fontid, const char *str)
{
#if 0
if (fontid < 0 || (unsigned) fontid > glsl_fonts.size) {
return;
}
@ -1030,4 +1030,5 @@ glsl_Draw_FontString (int x, int y, int fontid, const char *str)
glyph_queue->size = 0;
qfeglBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
qfeglUseProgram (quake_2d.program);
#endif
}

View file

@ -1,115 +0,0 @@
/*
r_text.c
Renderer font text management
Copyright (C) 2022 Bill Currie <bill@taniwha.org>
Author: Bill Currie <bill@taniwha.org>
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 <math.h>
#include "QF/quakefs.h"
#include "QF/sys.h"
#include "QF/ui/font.h"
#include "r_text.h"
#include "compat.h"
rshaper_t *
RText_NewShaper (font_t *font)
{
rshaper_t *shaper = malloc (sizeof (rshaper_t));
shaper->rfont = font;
shaper->font = hb_ft_font_create (font->face, 0);
hb_ft_font_set_funcs (shaper->font);
shaper->buffer = hb_buffer_create ();
shaper->features = (r_hb_featureset_t) DARRAY_STATIC_INIT (4);
hb_buffer_allocation_successful (shaper->buffer);
return shaper;
}
void
RText_DeleteShaper (rshaper_t *shaper)
{
hb_buffer_destroy (shaper->buffer);
hb_font_destroy (shaper->font);
DARRAY_CLEAR (&shaper->features);
free (shaper);
}
void
RText_AddFeature (rshaper_t *shaper, hb_feature_t feature)
{
DARRAY_APPEND (&shaper->features, feature);
}
void
RText_ShapeText (rshaper_t *shaper, rtext_t *text)
{
hb_buffer_reset (shaper->buffer);
hb_buffer_set_direction (shaper->buffer, text->direction);
hb_buffer_set_script (shaper->buffer, text->script);
const char *lang = text->language;
hb_buffer_set_language (shaper->buffer,
hb_language_from_string (lang, strlen (lang)));
size_t length = strlen (text->text);
hb_buffer_add_utf8 (shaper->buffer, text->text, length, 0, length);
hb_shape (shaper->font, shaper->buffer,
shaper->features.a, shaper->features.size);
}
void
RText_RenderText (rshaper_t *shaper, rtext_t *text, int x, int y,
rtext_render_t *render, void *data)
{
RText_ShapeText (shaper, text);
unsigned count;
__auto_type glyphInfo = hb_buffer_get_glyph_infos (shaper->buffer, &count);
__auto_type glyphPos = hb_buffer_get_glyph_positions (shaper->buffer,
&count);
font_t *font = shaper->rfont;
for (unsigned i = 0; i < count; i++) {
vec2i_t bearing = font->glyph_bearings[glyphInfo[i].codepoint];
int xp = x + glyphPos[i].x_offset / 64;
int yp = y + glyphPos[i].y_offset / 64;
int xa = glyphPos[i].x_advance / 64;
int ya = glyphPos[i].y_advance / 64;
xp += bearing[0];
yp -= bearing[1];
render (glyphInfo[i].codepoint, xp, yp, data);
x += xa;
y += ya;
}
}

View file

@ -43,7 +43,6 @@
#include "d_iface.h"
#include "d_local.h"
#include "r_text.h"
#include "r_internal.h"
#include "vid_internal.h"
@ -1012,7 +1011,7 @@ Draw_AddFont (struct font_s *rfont)
font->font = rfont;
return fontid;
}
#if 0
typedef struct {
vrect_t *glyph_rects;
byte *bitmap;
@ -1046,10 +1045,11 @@ sw_render_glyph (uint32_t glyphid, int x, int y, void *_rgctx)
dst += d_rowbytes;
}
}
#endif
void
Draw_FontString (int x, int y, int fontid, const char *str)
{
#if 0
if (fontid < 0 || (unsigned) fontid > sw_fonts.size) {
return;
}
@ -1072,4 +1072,5 @@ Draw_FontString (int x, int y, int fontid, const char *str)
rshaper_t *shaper = RText_NewShaper (rfont);
RText_RenderText (shaper, &text, x, y, sw_render_glyph, &rgctx);
RText_DeleteShaper (shaper);
#endif
}

View file

@ -69,7 +69,6 @@
#include "QF/ui/font.h"
#include "QF/ui/view.h"
#include "r_text.h"
#include "r_internal.h"
#include "vid_vulkan.h"
@ -1516,7 +1515,7 @@ Vulkan_Draw_AddFont (font_t *rfont, vulkan_ctx_t *ctx)
return fontid;
}
#if 0
typedef struct {
drawframe_t *dframe;
descbatch_t *batch;
@ -1540,11 +1539,12 @@ vulkan_render_glyph (uint32_t glyphid, int x, int y, void *_rgctx)
inst->position[0] = x;
inst->position[1] = y;
}
#endif
void
Vulkan_Draw_FontString (int x, int y, int fontid, const char *str,
vulkan_ctx_t *ctx)
{
#if 0
drawctx_t *dctx = ctx->draw_context;
if (fontid < 0 || (unsigned) fontid > dctx->fonts.size) {
return;
@ -1573,6 +1573,7 @@ Vulkan_Draw_FontString (int x, int y, int fontid, const char *str,
rshaper_t *shaper = RText_NewShaper (dctx->fonts.a[fontid].font);
RText_RenderText (shaper, &text, x, y, vulkan_render_glyph, &rgctx);
RText_DeleteShaper (shaper);
#endif
}
void