commit - 5c30449123bfdaea256bb979bfd6955bf21cda49
commit + b10a8c42ca51a9080cc5df95ecd26eee613eaf87
blob - 2ff3486fbd247746fb4e110747f2f0b72b2092d0
blob + 5d8fdb1ce2fe947b2e679eaf92e1ee92cc7b8952
--- regress/ui_layout/ui_layout_test.c
+++ regress/ui_layout/ui_layout_test.c
#include <string.h>
#include "buf.h"
+#include "utf8.h"
#include "ui_layout.h"
#include "ui_palette.h"
#include "regress.h"
static int
-line_is(const struct ui_line *line, const char *want)
+line_bytes_are(const struct ui_line *line, const void *want, size_t wantlen)
{
struct buf buf;
size_t i;
buf_init(&buf);
for (i = 0; i < line->nspans; i++)
buf_add(&buf, line->spans[i].text, line->spans[i].len);
- buf_addc(&buf, '\0');
- ok = strcmp((const char *)buf.data, want) == 0;
+ ok = buf.len == wantlen && memcmp(buf.data, want, wantlen) == 0;
buf_free(&buf);
return (ok);
}
static int
+line_is(const struct ui_line *line, const char *want)
+{
+ return (line_bytes_are(line, want, strlen(want)));
+}
+
+static int
+line_is_visible_utf8(const struct ui_line *line)
+{
+ unsigned char enc[4];
+ uint32_t cp;
+ size_t i, j, adv, enclen;
+
+ for (i = 0; i < line->nspans; i++) {
+ const struct ui_span *span = &line->spans[i];
+
+ for (j = 0; j < span->len; j += adv) {
+ adv = utf8_decode((const unsigned char *)span->text + j,
+ span->len - j, &cp);
+ enclen = utf8_encode(cp, enc);
+ if (adv != enclen || memcmp(span->text + j, enc, enclen) != 0 ||
+ cp == 0 || utf8_width(cp) < 0)
+ return (0);
+ }
+ }
+ return (1);
+}
+
+static int
line_attrs_are(const struct ui_line *line, int attr)
{
size_t i;
{
static const char wide_combining[] =
"A\xcc\x81\xe4\xb8\xad" "B";
+ static const unsigned char hostile[] = {
+ 'A', '\0', '\t', '\x1b', 0xc2, 0x85, 0xf0, '(', 0x8c, '(', 'B'
+ };
+ static const char normalized[] =
+ "A\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd"
+ "\xef\xbf\xbd(\xef\xbf\xbd(B";
struct ui_layout layout = { 0 };
int attr = 0x1234;
layout.lines[0].spans[1].width == 4);
CHECK(layout.lines[0].fill_width == 6);
+ /*
+ * Hostile transcript bytes become visible UTF-8 cells before storage:
+ * embedded NUL, C0/C1 controls, and each malformed byte are U+FFFD.
+ */
+ ui_layout_reset(&layout);
+ ui_layout_plain(&layout, hostile, sizeof(hostile), 40, attr, NULL,
+ NULL, 0);
+ CHECK(layout.nlines == 1);
+ CHECK(line_bytes_are(&layout.lines[0], normalized,
+ sizeof(normalized) - 1));
+ CHECK(line_width(&layout.lines[0]) == 10);
+ CHECK(line_is_visible_utf8(&layout.lines[0]));
+
+ /* Direct styled spans (the assistant-markdown path) share the boundary. */
+ ui_layout_reset(&layout);
+ {
+ struct ui_line *line = ui_layout_line(&layout);
+
+ ui_layout_span(line, attr, hostile, sizeof(hostile));
+ }
+ CHECK(layout.nlines == 1);
+ CHECK(line_bytes_are(&layout.lines[0], normalized,
+ sizeof(normalized) - 1));
+ CHECK(line_width(&layout.lines[0]) == 10);
+ CHECK(line_is_visible_utf8(&layout.lines[0]));
+
/* Re-rendering at a narrower pane pins resize and drawer reflow. */
ui_layout_reset(&layout);
ui_layout_plain(&layout, "abcdef", 6, 5, attr, "> ", " ", 1);
blob - d4568d6e529f82144f31100410917050e0ef7a0f
blob + bac1fe598e0384069d9d1cf6244497a9fab59110
--- src/fugu-tty/ui.c
+++ src/fugu-tty/ui.c
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#define _XOPEN_SOURCE_EXTENDED
+
#include <sys/ioctl.h>
#include <ctype.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
+#include <wchar.h>
#include "log.h"
#include "xmalloc.h"
for (j = 0; j < d.lines[i].nspans; j++) {
struct md_span *s = &d.lines[i].spans[j];
- ui_layout_span(l, md_attr(u, s->attr), s->text, s->len,
- s->width);
+ ui_layout_span(l, md_attr(u, s->attr), s->text, s->len);
}
}
md_free(&d);
return (th < 1 ? 1 : th);
}
+/* Paint normalized UTF-8 through curses' wide-character interface. */
+static int
+draw_utf8_cells(const void *vtext, size_t len, int cells)
+{
+ const unsigned char *text = vtext;
+ wchar_t wc;
+ uint32_t cp;
+ size_t adv, i;
+ int used = 0, w;
+
+ for (i = 0; i < len; i += adv) {
+ adv = utf8_decode(text + i, len - i, &cp);
+ w = utf8_width(cp);
+ if (cp == 0 || w < 0) {
+ cp = UTF8_REPLACEMENT;
+ w = 1;
+ }
+ if (w > cells - used)
+ break;
+ wc = (wchar_t)cp;
+ if (addnwstr(&wc, 1) == ERR)
+ break;
+ used += w;
+ }
+ return (used);
+}
+
static void
draw_line(const struct ui *u, int row, const struct ui_line *l, int xoff)
{
move(row, col);
attrset(s->attr);
- addnstr(s->text, (int)s->len);
- col += s->width;
+ col += draw_utf8_cells(s->text, s->len, u->cols - col);
}
attrset(A_NORMAL);
}
adv = utf8_decode(disp.data + i, disp.len - i, &cp);
w = utf8_width(cp);
- if (w < 0)
- w = 0;
+ if (cp == 0 || w < 0)
+ w = 1;
sc = (int)scanned - (int)start;
if (sc >= 0 && sc + w <= u->cols) {
move(row, sc);
- addnstr((char *)disp.data + i, (int)adv);
+ (void)draw_utf8_cells(disp.data + i, adv, w);
}
scanned += w;
i += adv;
blob - 7e7e63c09138ee4872a6f61b1188b07b44368e93
blob + e930cf754a8196847eda80497a1253fa5efae8c6
--- src/fugu-tty/ui_layout.c
+++ src/fugu-tty/ui_layout.c
if (cp == '\n')
return (0);
w = utf8_width(cp);
+ if (cp == 0 || w < 0)
+ w = 1;
if (w > 0)
return (w);
i += adv;
}
void
-ui_layout_span(struct ui_line *line, int attr, const void *text, size_t len,
- int width)
+ui_layout_span(struct ui_line *line, int attr, const void *vtext, size_t len)
{
+ const unsigned char *text = vtext;
struct ui_span *span;
+ struct buf normalized;
+ unsigned char enc[4];
+ uint32_t cp;
+ size_t adv, enclen, i;
+ int w, width = 0;
+ buf_init(&normalized);
+ for (i = 0; i < len; i += adv) {
+ adv = utf8_decode(text + i, len - i, &cp);
+ w = utf8_width(cp);
+ if (cp == 0 || w < 0) {
+ cp = UTF8_REPLACEMENT;
+ w = 1;
+ }
+ enclen = utf8_encode(cp, enc);
+ buf_add(&normalized, enc, enclen);
+ width += w;
+ }
+
line->spans = xreallocarray(line->spans, line->nspans + 1,
sizeof(*line->spans));
span = &line->spans[line->nspans++];
span->attr = attr;
span->width = width;
- span->len = len;
- span->text = xmalloc(len + 1);
- memcpy(span->text, text, len);
- span->text[len] = '\0';
+ span->len = normalized.len;
+ span->text = xmalloc(normalized.len + 1);
+ if (normalized.len > 0)
+ memcpy(span->text, normalized.data, normalized.len);
+ span->text[normalized.len] = '\0';
+ buf_free(&normalized);
}
/* Wrap plain UTF-8 text to width columns, one attribute, into new lines. */
leadlen = (size_t)lead_limit;
if (leadlen > 0) {
- ui_layout_span(line, attr, lead, leadlen,
- (int)leadlen);
+ ui_layout_span(line, attr, lead, leadlen);
col = (int)leadlen;
}
}
break_len = 0;
break_width = 0;
while (i < len) {
+ unsigned char enc[4];
uint32_t cp;
- size_t adv;
+ size_t adv, enclen;
int w;
adv = utf8_decode(text + i, len - i, &cp);
break;
}
w = utf8_width(cp);
- if (w < 0)
- w = 0;
+ if (cp == 0 || w < 0) {
+ cp = UTF8_REPLACEMENT;
+ w = 1;
+ }
if (col + w > width) {
if (segw > 0) {
if (last_break != (size_t)-1) {
i += adv;
continue;
}
- buf_add(&seg, text + i, adv);
+ enclen = utf8_encode(cp, enc);
+ buf_add(&seg, enc, enclen);
segw += w;
col += w;
- if (cp == ' ' && seg.len > adv) {
+ if (cp == ' ' && seg.len > enclen) {
last_break = i;
- break_len = seg.len - adv;
+ break_len = seg.len - enclen;
break_width = segw - w;
}
i += adv;
}
if (seg.len > 0)
- ui_layout_span(line, attr, seg.data, seg.len, segw);
+ ui_layout_span(line, attr, seg.data, seg.len);
buf_free(&seg);
} while (i < len);
}
blob - 85c490f435a7c16cfa3993077783f5fb53324520
blob + 2ca324091c9504b1a0b68bf62b5670cca635284d
--- src/fugu-tty/ui_layout.h
+++ src/fugu-tty/ui_layout.h
void ui_layout_reset(struct ui_layout *);
void ui_layout_free(struct ui_layout *);
struct ui_line *ui_layout_line(struct ui_layout *);
-void ui_layout_span(struct ui_line *, int, const void *, size_t,
- int);
+void ui_layout_span(struct ui_line *, int, const void *, size_t);
void ui_layout_plain(struct ui_layout *, const void *, size_t, int,
int, const char *, const char *, int);
int ui_layout_visible_fill(const struct ui_line *, int, int);