commit 528b74094b0aa1d585efe5ad28903758d467e8a6
parent 0e40d68a8b98ca331bfad96d3293d9c01c5875ca
Author: lumidify <nobody@lumidify.org>
Date: Sun, 17 May 2020 14:56:19 +0200
Improve bidi text rendering
Diffstat:
7 files changed, 275 insertions(+), 115 deletions(-)
diff --git a/NOTES b/NOTES
@@ -30,3 +30,9 @@ linked list or something similar in sorted order;
when looping over characters to draw them, keep a
pointer to the current tag, then change it to the
next one if the end of its bound comes
+
+Create a version of ltk_fatal that exits the actual program
+and creates a "failsafe" error window (just using basic
+XDrawString, etc.) to show errors (like index out of bounds,
+etc.). Still print the error, of course, in case creating
+a window doesn't work.
diff --git a/array.h b/array.h
@@ -43,7 +43,9 @@ void ltk_array_insert_##name(struct ltk_array_##name *ar, size_t index, \
void ltk_array_resize_##name(struct ltk_array_##name *ar, size_t size); \
void ltk_array_destroy_##name(struct ltk_array_##name *ar); \
void ltk_array_clear_##name(struct ltk_array_##name *ar); \
-void ltk_array_append_##name(struct ltk_array_##name *ar, type elem);
+void ltk_array_append_##name(struct ltk_array_##name *ar, type elem); \
+void ltk_array_destroy_deep_##name(struct ltk_array_##name *ar, \
+ void (*destroy_func)(type));
#define LTK_ARRAY_INIT_IMPL(name, type) \
struct ltk_array_##name * \
@@ -137,6 +139,15 @@ ltk_array_destroy_##name(struct ltk_array_##name *ar) { \
free(ar->buf); \
ar->buf = NULL; \
free(ar); \
+} \
+ \
+void \
+ltk_array_destroy_deep_##name(struct ltk_array_##name *ar, \
+ void (*destroy_func)(type)) { \
+ for (int i = 0; i < ar->len; i++) { \
+ destroy_func(ar->buf[i]); \
+ } \
+ ltk_array_destroy_##name(ar); \
}
#endif /* _LTK_ARRAY_H_ */
diff --git a/test1.c b/test1.c
@@ -39,9 +39,9 @@ int main(int argc, char *argv[])
//ltk_grid_widget(button3, grid1, 1, 0, 1, 1, LTK_STICKY_TOP | LTK_STICKY_BOTTOM | LTK_STICKY_RIGHT);
//LtkButton *button4 = ltk_create_button(window1, "I'm a button!", NULL);
//LtkButton *button4 = ltk_create_button(window1, "پَیدایش", NULL);
- LtkTextEdit *edit = ltk_create_text_edit(window1, "ہمارے بارے میں blablabla");
+ LtkTextEdit *edit = ltk_create_text_edit(window1, "ہمارے بارے میں blabla bla");
LtkButton *button4 = ltk_create_button(window1, "ہمارے بارے میں blablabla", &bob3, edit);
ltk_grid_widget(button4, grid1, 1, 0, 1, 1, LTK_STICKY_TOP | LTK_STICKY_BOTTOM | LTK_STICKY_RIGHT);
- ltk_grid_widget(edit, grid1, 1, 1, 1, 1, LTK_STICKY_LEFT | LTK_STICKY_BOTTOM | LTK_STICKY_TOP);
+ ltk_grid_widget(edit, grid1, 1, 1, 1, 1, LTK_STICKY_LEFT | LTK_STICKY_BOTTOM | LTK_STICKY_TOP | LTK_STICKY_RIGHT);
ltk_mainloop();
}
diff --git a/text_buffer.c b/text_buffer.c
@@ -44,85 +44,151 @@ LTK_ARRAY_INIT_IMPL(uint32, uint32_t)
LTK_ARRAY_INIT_IMPL(script, hb_script_t)
LTK_ARRAY_INIT_IMPL(level, FriBidiLevel)
LTK_ARRAY_INIT_IMPL(int, int)
+LTK_ARRAY_INIT_IMPL(line, struct ltk_soft_line *)
-/* based on http://codemadness.org/git/dwm-font/file/drw.c.html#l315 */
-/* FIXME: rename this once everything is cleaned up (currently conflicts with the
- old render function */
-XImage *
-ltk_render_text_line_new(
- struct ltk_text_line *tl,
- int max_width,
- Display *dpy,
- Window window,
- GC gc,
- Colormap colormap,
- XColor fg,
- XColor bg)
-{
- XImage *img;
- int par_is_rtl = FRIBIDI_IS_RTL(tl->dir);
- int cur_y = 0;
- int cur_x = par_is_rtl ? max_width : 0;
- ltk_array_clear_int(tl->wrap_indeces);
- ltk_array_append_uint32(tl->wrap_indeces, 0);
+void
+ltk_soft_line_destroy(struct ltk_soft_line *sl) {
+ if (sl->img) XDestroyImage(sl->img);
+ free(sl);
+}
+
+struct ltk_array_line *
+ltk_text_line_wrap(struct ltk_text_line *tl, int max_width) {
+ /* FIXME: if max_width == -1, don't wrap */
+ struct ltk_array_line *soft_lines = ltk_array_create_line(1);
+ int par_is_rtl = tl->dir == HB_DIRECTION_RTL;
- /* FIXME: wrap bidi text properly */
- /* FIXME: THIS IS UGLY */
struct ltk_text_run *cur = par_is_rtl ? tl->last_run : tl->first_run;
+ LtkGlyph *glyph;
+ struct ltk_soft_line *sl = malloc(sizeof(struct ltk_soft_line));
+ if (!sl) goto error;
+ sl->glyph_index = cur->dir == HB_DIRECTION_RTL ? cur->len - 1 : 0;
+ sl->run = cur;
+ sl->len = 0;
+ sl->w = 0;
+ ltk_array_append_line(soft_lines, sl);
+ int last_linebreak = par_is_rtl ? tl->w : 0;
+ int cur_start = 0;
+ /* FIXME: also calculate max height of each line */
while (cur) {
- if (par_is_rtl) {
- for (int i = cur->num_glyphs - 1; i >= 0; i--) {
- cur_x -= cur->glyphs[i].x_advance;
- if (cur_x < 0) {
- int j = 0;
- for (j = i; j < cur->num_glyphs; j++) {
- if (cur->glyphs[j].cluster != cur->glyphs[i].cluster) {
- /* must decrease one again so the actual
- last character is used */
- j--;
+ printf("%d %d\n", cur->len, cur->num_glyphs);
+ if (cur->len == 1) {
+ printf("%d\n", tl->log_buf->buf[cur->glyphs[0].cluster]);
+ }
+ if (sl->w + cur->w <= max_width) {
+ sl->w += cur->w;
+ sl->len += cur->len;
+ cur = par_is_rtl ? cur->last : cur->next;
+ continue;
+ }
+ if (cur->dir == HB_DIRECTION_RTL) {
+ cur_start = cur->glyphs[cur->len - 1].x_abs + cur->glyphs[cur->len - 1].info->w;
+ int i = cur->len - 1;
+ while (i >= 0) {
+ glyph = &cur->glyphs[i];
+ int cur_w = sl->w + cur_start - glyph->x_abs;
+ if (cur_w > max_width) {
+ /* FIXME: fix behavior when line isn't wide enough for single char */
+ for (int j = i; j < cur->len; j++) {
+ if (cur->glyphs[j].cluster != glyph->cluster || j == cur->len - 1) {
+ int new_index;
+ if (j == cur->len - 1 &&
+ cur->glyphs[j].cluster == glyph->cluster) {
+ new_index = j;
+ i = j;
+ last_linebreak = cur_start;
+ } else {
+ new_index = j - 1;
+ i = j - 1;
+ last_linebreak = cur->glyphs[j].x_abs;
+ sl->len++;
+ }
+ sl->w += cur_start - last_linebreak;
+ cur_start = last_linebreak;
+ sl = malloc(sizeof(struct ltk_soft_line));
+ if (!sl) goto error;
+ sl->glyph_index = new_index;
+ sl->run = cur;
+ sl->len = 0;
+ sl->w = 0;
+ ltk_array_append_line(soft_lines, sl);
break;
+ } else {
+ sl->len--;
}
}
- i = j;
- /* FIXME: handle case that this is the same as the last index */
- ltk_array_append_uint32(tl->wrap_indeces, cur->glyphs[i].cluster);
- cur_x = max_width;
+ } else {
+ sl->len++;
+ i--;
}
}
+ if (sl->run == cur)
+ sl->w = last_linebreak - cur->glyphs[0].x_abs;
+ else
+ sl->w += cur->w;
} else {
- for (int i = 0; i < cur->num_glyphs; i++) {
- cur_x += cur->glyphs[i].x_advance;
- if (cur_x > max_width) {
- int j = 0;
- for (j = i; j >= 0; j--) {
- if (cur->glyphs[j].cluster != cur->glyphs[i].cluster) {
- /* must increase one again so the actual
- next character is used */
- j++;
+ cur_start = cur->glyphs[0].x_abs;
+ int i = 0;
+ while (i < cur->len) {
+ glyph = &cur->glyphs[i];
+ int cur_w = sl->w + glyph->x_abs + glyph->info->w - cur_start;
+ if (cur_w > max_width) {
+ for (int j = i; j >= 0; j--) {
+ if (cur->glyphs[j].cluster != glyph->cluster || j == 0) {
+ int new_index;
+ if (j == 0 && cur->glyphs[j].cluster == glyph->cluster) {
+ new_index = j;
+ i = j;
+ last_linebreak = cur_start;
+ } else {
+ new_index = j + 1;
+ i = j + 1;
+ last_linebreak = cur->glyphs[j + 1].x_abs;
+ sl->len++;
+ }
+ sl->w += last_linebreak - cur_start;
+ cur_start = last_linebreak;
+ sl = malloc(sizeof(struct ltk_soft_line));
+ if (!sl) goto error;
+ sl->glyph_index = new_index;
+ sl->run = cur;
+ sl->len = 0;
+ sl->w = 0;
+ ltk_array_append_line(soft_lines, sl);
break;
+ } else {
+ sl->len--;
}
}
- i = j;
- /* FIXME: handle case that this is the same as the last index */
- ltk_array_append_uint32(tl->wrap_indeces, cur->glyphs[i].cluster);
- cur_x = 0;
+ } else {
+ sl->len++;
+ i++;
}
+
}
+ if (sl->run == cur)
+ sl->w = cur->glyphs[cur->len - 1].x_abs + cur->glyphs[cur->len - 1].info->w - last_linebreak;
+ else
+ sl->w += cur->w;
}
cur = par_is_rtl ? cur->last : cur->next;
}
+ return soft_lines;
+error:
+ (void)fprintf(stderr, "Error allocating memory while wrapping text line.\n");
+ exit(1);
+}
- XWindowAttributes attrs;
- XGetWindowAttributes(dpy, window, &attrs);
- int depth = attrs.depth;
- img = XCreateImage(dpy, CopyFromParent, depth, ZPixmap, 0, NULL, max_width, (tl->h + tl->line_gap) * tl->wrap_indeces->len, 32, 0);
+XImage *
+ltk_create_ximage(Display *dpy, int w, int h, int depth, XColor bg) {
+ XImage *img = XCreateImage(dpy, CopyFromParent, depth, ZPixmap, 0, NULL, w, h, 32, 0);
img->data = calloc(img->bytes_per_line, img->height);
XInitImage(img);
int b;
- for (int i = 0; i < tl->h * tl->wrap_indeces->len; i++) {
+ for (int i = 0; i < h; i++) {
b = img->bytes_per_line * i;
- for (int j = 0; j < max_width; j++) {
+ for (int j = 0; j < w; j++) {
img->data[b++] = bg.blue / 257;
img->data[b++] = bg.green / 257;
img->data[b++] = bg.red / 257;
@@ -130,55 +196,112 @@ ltk_render_text_line_new(
}
}
- cur = par_is_rtl ? tl->last_run : tl->first_run;
- int x, y;
+ return img;
+}
+
+/* based on http://codemadness.org/git/dwm-font/file/drw.c.html#l315 */
+void
+ltk_draw_glyph(LtkGlyph *glyph, XImage *img, int x, int y, XColor fg) {
double a;
- int cur_line_x = 0;
- int cur_line = 0;
+ int b;
+ for (int i = 0; i < glyph->info->h; i++) {
+ for (int j = 0; j < glyph->info->w; j++) {
+ if (y + i >= img->height || x + j >= img->width)
+ continue;
+ b = (y + i) * img->bytes_per_line + (x + j) * 4;
+ a = glyph->info->alphamap[i * glyph->info->w + j] / 255.0;
+ img->data[b] = (fg.blue * a + (1 - a) * (uint16_t)img->data[b] * 257) / 257;
+ img->data[b + 1] = (fg.green * a + (1 - a) * (uint16_t)img->data[b + 1] * 257) / 257;
+ img->data[b + 2] = (fg.red * a + (1 - a) * (uint16_t)img->data[b + 2] * 257) / 257;
+ }
+ }
+}
+
+/* FIXME: rename this once everything is cleaned up (currently conflicts with the
+ old render function */
+struct ltk_array_line *
+ltk_render_text_line_new(
+ struct ltk_text_line *tl,
+ int max_width,
+ Display *dpy,
+ Window window,
+ GC gc,
+ Colormap colormap,
+ XColor fg,
+ XColor bg)
+{
LtkGlyph *glyph;
- /* FIXME: ints are compared with size_t's in various places. Maybe I should fix that. */
- /* FIXME: how should an empty line be handled? This doesn't use a do-for
- loop in case tl->first_run is NULL, but I should probably decide what
- to do in that case */
- int index;
- while (cur) {
- for (int k = 0; k < cur->num_glyphs; k++) {
- index = par_is_rtl ? cur->num_glyphs - k - 1 : k;
- glyph = &cur->glyphs[index];
- x = par_is_rtl ? max_width - ((tl->w - glyph->x_abs) - cur_line_x) : glyph->x_abs - cur_line_x;
- /* FIXME: use the computed indeces from above */
- if (par_is_rtl && x < 0) {
- cur_line++;
- cur_line_x = (tl->w - glyph->x_abs - glyph->info->w);
- x = max_width - ((tl->w - glyph->x_abs) - cur_line_x);
- } else if (!par_is_rtl && x + glyph->info->w > max_width) {
- cur_line++;
- cur_line_x = glyph->x_abs;
- x = glyph->x_abs - cur_line_x;
- }
- y = glyph->y_abs + (tl->h + tl->line_gap) * cur_line;
- /* FIXME: remove this when everything's fixed */
- if (x < 0)
- x = 0;
- if (x > max_width - glyph->info->w)
- x = max_width - glyph->info->w;
- if (y < 0)
- y = 0;
- if (y > img->height - glyph->info->h)
- y = img->height - glyph->info->h;
- for (int i = 0; i < glyph->info->h; i++) {
- for (int j = 0; j < glyph->info->w; j++) {
- b = (y + i) * img->bytes_per_line + (x + j) * 4;
- a = glyph->info->alphamap[i * glyph->info->w + j] / 255.0;
- img->data[b] = (fg.blue * a + (1 - a) * (uint16_t)img->data[b] * 257) / 257;
- img->data[b + 1] = (fg.green * a + (1 - a) * (uint16_t)img->data[b + 1] * 257) / 257;
- img->data[b + 2] = (fg.red * a + (1 - a) * (uint16_t)img->data[b + 2] * 257) / 257;
+ int par_is_rtl = tl->dir == HB_DIRECTION_RTL;
+ /* FIXME: can't soft_lines just be a normal array instead of pointer array? */
+ struct ltk_array_line *soft_lines = ltk_text_line_wrap(tl, max_width);
+
+ XWindowAttributes attrs;
+ XGetWindowAttributes(dpy, window, &attrs);
+ int depth = attrs.depth;
+
+ for (int i = 0; i < soft_lines->len; i++) {
+ struct ltk_soft_line *sl = soft_lines->buf[i];
+ sl->img = ltk_create_ximage(dpy, sl->w, tl->h, depth, bg);
+ struct ltk_text_run *cur = sl->run;
+ size_t cur_len = 0;
+ int cur_border = par_is_rtl ? sl->w : 0;
+ while (cur && cur_len < sl->len) {
+ int start_index;
+ /* FIXME: the borders here aren't correct since they are
+ x_abs + glyph->info->w, which may not be the same thing
+ as the actual x_abs of the bordering glyph */
+ if (cur->dir == HB_DIRECTION_RTL) {
+ start_index = cur == sl->run ? sl->glyph_index : cur->len - 1;
+ int local_border = cur->glyphs[start_index].x_abs + cur->glyphs[start_index].info->w;
+ int end_index;
+ if (start_index + 1 < sl->len - cur_len) {
+ end_index = 0;
+ } else {
+ end_index = start_index - (sl->len - cur_len) + 1;
+ }
+ for (int i = start_index; i >= 0 && cur_len < sl->len; i--) {
+ cur_len++;
+ int x;
+ if (par_is_rtl) {
+ x = cur_border - (local_border - cur->glyphs[i].x_abs);
+ } else {
+ x = cur_border + (cur->glyphs[i].x_abs - cur->glyphs[end_index].x_abs);
+ }
+ ltk_draw_glyph(&cur->glyphs[i], sl->img, x, cur->glyphs[i].y_abs, fg);
+ }
+ if (par_is_rtl)
+ cur_border -= local_border - cur->glyphs[0].x_abs;
+ else
+ cur_border += local_border - cur->glyphs[0].x_abs;
+ } else {
+ start_index = cur == sl->run ? sl->glyph_index : 0;
+ int local_border = cur->glyphs[start_index].x_abs;
+ int end_index;
+ if (cur->len - start_index < sl->len - cur_len) {
+ end_index = cur->len - 1;
+ } else {
+ end_index = start_index + sl->len - cur_len - 1;
+ }
+ for (int i = start_index; i < cur->len && cur_len < sl->len; i++) {
+ cur_len++;
+ int x;
+ if (par_is_rtl) {
+ x = cur_border - (cur->glyphs[end_index].x_abs + cur->glyphs[end_index].info->w - cur->glyphs[i].x_abs);
+ } else {
+ x = cur_border + (cur->glyphs[i].x_abs - local_border);
+ }
+ ltk_draw_glyph(&cur->glyphs[i], sl->img, x, cur->glyphs[i].y_abs, fg);
}
+ if (par_is_rtl)
+ cur_border -= cur->glyphs[cur->len - 1].x_abs + cur->glyphs[cur->len - 1].info->w - local_border;
+ else
+ cur_border += cur->glyphs[cur->len - 1].x_abs + cur->glyphs[cur->len - 1].info->w - local_border;
}
+ cur = par_is_rtl ? cur->last : cur->next;
}
- cur = par_is_rtl ? cur->last : cur->next;
}
- return img;
+
+ return soft_lines;
}
/* Begin stuff stolen from raqm */
@@ -566,10 +689,15 @@ ltk_text_line_destroy_runs(struct ltk_text_run *runs) {
static void
ltk_text_line_recalculate(LtkTextManager *tm, struct ltk_text_line *tl) {
+ FriBidiCharType par_dir = FRIBIDI_TYPE_ON;
fribidi_log2vis(
tl->log_buf->buf, tl->log_buf->len,
- &tl->dir, tl->vis_buf->buf, tl->log2vis->buf, tl->vis2log->buf, tl->bidi_levels->buf
+ &par_dir, tl->vis_buf->buf, tl->log2vis->buf, tl->vis2log->buf, tl->bidi_levels->buf
);
+ if (FRIBIDI_IS_RTL(par_dir))
+ tl->dir = HB_DIRECTION_RTL;
+ else
+ tl->dir = HB_DIRECTION_LTR;
struct ltk_text_run *old_runs = tl->first_run;
ltk_text_line_itemize(tl);
struct ltk_text_run *cur = tl->first_run;
@@ -630,7 +758,6 @@ ltk_text_line_create(void) {
line->cur_run = NULL;
line->next = NULL;
line->height = 0;
- line->dir = FRIBIDI_TYPE_ON;
line->len = 0;
line->cursor_pos = 0;
/* FIXME */
diff --git a/text_buffer.h b/text_buffer.h
@@ -55,11 +55,16 @@ struct ltk_text_run {
hb_direction_t dir;
};
-struct ltk_line_break {
- size_t index;
+struct ltk_soft_line {
+ size_t glyph_index;
+ size_t len;
+ int w;
struct ltk_text_run *run;
+ XImage *img;
};
+LTK_ARRAY_INIT_DECL(line, struct ltk_soft_line *)
+
struct ltk_text_line {
struct ltk_array_uint32 *log_buf; /* buffer of the logical text */
struct ltk_array_script *scripts;
@@ -73,7 +78,7 @@ struct ltk_text_line {
struct ltk_text_run *cur_run; /* current node in the linked list of runs */
struct ltk_text_line *next; /* next text line in the buffer */
unsigned int height; /* height of the line (including wrapping) */
- FriBidiCharType dir; /* overall paragraph direction */
+ hb_direction_t dir; /* overall paragraph direction */
size_t len;
uint16_t font_size;
int y_max;
@@ -90,7 +95,8 @@ struct ltk_text_buffer {
unsigned int line_gap;
};
-XImage *ltk_render_text_line_new(struct ltk_text_line *tl, int max_width,
+void ltk_soft_line_destroy(struct ltk_soft_line *sl);
+struct ltk_array_line *ltk_render_text_line_new(struct ltk_text_line *tl, int max_width,
Display *dpy, Window window, GC gc, Colormap colormap, XColor fg, XColor bg);
void ltk_text_line_insert_text(struct ltk_text_line *tl, uint32_t *text, size_t len);
struct ltk_text_line *ltk_text_line_create(void);
diff --git a/text_edit.c b/text_edit.c
@@ -44,9 +44,17 @@ ltk_draw_text_edit(LtkTextEdit *te) {
XColor bg = ltk_global->theme->window->bg;
LtkRect rect = te->widget.rect;
LtkWindow *window = te->widget.window;
- if (!te->img)
- te->img = ltk_render_text_line_new(te->tl, rect.w, ltk_global->display, window->xwindow, window->gc, ltk_global->colormap, fg, bg);
- XPutImage(ltk_global->display, window->xwindow, window->gc, te->img, 0, 0, rect.x, rect.y, te->img->width, te->img->height);
+ if (!te->soft_lines)
+ te->soft_lines = ltk_render_text_line_new(te->tl, rect.w, ltk_global->display, window->xwindow, window->gc, ltk_global->colormap, fg, bg);
+ XSetForeground(ltk_global->display, window->gc, fg.pixel);
+ XFillRectangle(ltk_global->display, window->xwindow, window->gc, rect.x, rect.y, rect.w, rect.h);
+ for (int i = 0; i < te->soft_lines->len; i++) {
+ XImage *img = te->soft_lines->buf[i]->img;
+ int x = rect.x;
+ if (te->tl->dir == HB_DIRECTION_RTL)
+ x += rect.w - img->width;
+ XPutImage(ltk_global->display, window->xwindow, window->gc, img, 0, 0, x, rect.y + te->tl->h * i, img->width, img->height);
+ }
}
LtkTextEdit *
@@ -57,21 +65,23 @@ ltk_create_text_edit(LtkWindow *window, const char *text) {
ltk_fill_widget_defaults(&te->widget, window, <k_draw_text_edit, <k_destroy_text_edit, 1);
te->tl = ltk_text_line_create();
ltk_text_line_insert_utf8(te->tl, text);
- te->img = NULL;
+ te->soft_lines = NULL;
return te;
}
void
ltk_text_edit_insert_text(LtkTextEdit *te, const char *text) {
ltk_text_line_insert_utf8(te->tl, text);
- if (te->img) XDestroyImage(te->img);
- te->img = NULL;
+ if (te->soft_lines)
+ ltk_array_destroy_deep_line(te->soft_lines, <k_soft_line_destroy);
+ te->soft_lines = NULL;
/* FIXME: Need to "queue redraw" for whole window */
ltk_draw_text_edit(te);
}
void ltk_destroy_text_edit(LtkTextEdit *te) {
ltk_text_line_destroy(te->tl);
- if (te->img) XDestroyImage(te->img);
+ if (te->soft_lines)
+ ltk_array_destroy_deep_line(te->soft_lines, <k_soft_line_destroy);
free(te);
}
diff --git a/text_edit.h b/text_edit.h
@@ -26,8 +26,8 @@
typedef struct {
LtkWidget widget;
- XImage *img;
struct ltk_text_line *tl;
+ struct ltk_array_line *soft_lines;
} LtkTextEdit;
/* FIXME: standardize ltk_<widget>_destroy, etc. instead of ltk_destroy_<widget> */