Merge pull request #26813 from VanaIgr/screen-pos-speedup

perf: make screen size and position calculations more efficient

N/A patches for version.c:
vim-patch:9.1.0037: Calling get_breakindent_win() repeatedly when computing virtcol
vim-patch:9.1.0038: Unnecessary loop in getvcol()
This commit is contained in:
zeertzjq 2024-01-22 10:00:11 +08:00 committed by GitHub
commit 8c6de9147c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 1103 additions and 555 deletions

View File

@ -137,6 +137,8 @@ The following new APIs and features were added.
• Treesitter highlighting now parses injections incrementally during • Treesitter highlighting now parses injections incrementally during
screen redraws only for the line range being rendered. This significantly screen redraws only for the line range being rendered. This significantly
improves performance in large files with many injections. improves performance in large files with many injections.
• 'breakindent' performance is significantly improved for wrapped lines.
• Cursor movement, insertion with [count] and |screenpos()| are now faster.
• |vim.iter()| provides a generic iterator interface for tables and Lua • |vim.iter()| provides a generic iterator interface for tables and Lua
iterators |for-in|. iterators |for-in|.

View File

@ -257,6 +257,7 @@ bool arabic_maycombine(int two)
} }
/// Check whether we are dealing with Arabic combining characters. /// Check whether we are dealing with Arabic combining characters.
/// Returns false for negative values.
/// Note: these are NOT really composing characters! /// Note: these are NOT really composing characters!
/// ///
/// @param one First character. /// @param one First character.

View File

@ -141,17 +141,18 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a
} }
} }
chartabsize_T cts; CharsizeArg arg;
init_chartabsize_arg(&cts, curwin, pos->lnum, 0, line, line); CSType cstype = init_charsize_arg(&arg, curwin, pos->lnum, line);
while (cts.cts_vcol <= wcol && *cts.cts_ptr != NUL) { StrCharInfo ci = utf_ptr2StrCharInfo(line);
// Count a tab for what it's worth (if list mode not on) col = 0;
csize = win_lbr_chartabsize(&cts, &head); while (col <= wcol && *ci.ptr != NUL) {
MB_PTR_ADV(cts.cts_ptr); CharSize cs = win_charsize(cstype, col, ci.ptr, ci.chr.value, &arg);
cts.cts_vcol += csize; csize = cs.width;
head = cs.head;
col += cs.width;
ci = utfc_next(ci);
} }
col = cts.cts_vcol; idx = (int)(ci.ptr - line);
idx = (int)(cts.cts_ptr - line);
clear_chartabsize_arg(&cts);
// Handle all the special cases. The virtual_active() check // Handle all the special cases. The virtual_active() check
// is needed to ensure that a virtual position off the end of // is needed to ensure that a virtual position off the end of

View File

@ -1336,30 +1336,30 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
if (start_col > 0 && col_rows == 0) { if (start_col > 0 && col_rows == 0) {
char *prev_ptr = ptr; char *prev_ptr = ptr;
chartabsize_T cts; CharSize cs = { 0 };
int charsize = 0;
int head = 0;
init_chartabsize_arg(&cts, wp, lnum, wlv.vcol, line, ptr); CharsizeArg arg;
cts.cts_max_head_vcol = start_col; CSType cstype = init_charsize_arg(&arg, wp, lnum, line);
while (cts.cts_vcol < start_col && *cts.cts_ptr != NUL) { arg.max_head_vcol = start_col;
head = 0; int vcol = wlv.vcol;
charsize = win_lbr_chartabsize(&cts, &head); StrCharInfo ci = utf_ptr2StrCharInfo(ptr);
cts.cts_vcol += charsize; while (vcol < start_col && *ci.ptr != NUL) {
prev_ptr = cts.cts_ptr; cs = win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &arg);
MB_PTR_ADV(cts.cts_ptr); vcol += cs.width;
prev_ptr = ci.ptr;
ci = utfc_next(ci);
if (wp->w_p_list) { if (wp->w_p_list) {
in_multispace = *prev_ptr == ' ' && (*cts.cts_ptr == ' ' in_multispace = *prev_ptr == ' ' && (*ci.ptr == ' '
|| (prev_ptr > line && prev_ptr[-1] == ' ')); || (prev_ptr > line && prev_ptr[-1] == ' '));
if (!in_multispace) { if (!in_multispace) {
multispace_pos = 0; multispace_pos = 0;
} else if (cts.cts_ptr >= line + leadcol } else if (ci.ptr >= line + leadcol
&& wp->w_p_lcs_chars.multispace != NULL) { && wp->w_p_lcs_chars.multispace != NULL) {
multispace_pos++; multispace_pos++;
if (wp->w_p_lcs_chars.multispace[multispace_pos] == NUL) { if (wp->w_p_lcs_chars.multispace[multispace_pos] == NUL) {
multispace_pos = 0; multispace_pos = 0;
} }
} else if (cts.cts_ptr < line + leadcol } else if (ci.ptr < line + leadcol
&& wp->w_p_lcs_chars.leadmultispace != NULL) { && wp->w_p_lcs_chars.leadmultispace != NULL) {
multispace_pos++; multispace_pos++;
if (wp->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) { if (wp->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) {
@ -1368,9 +1368,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
} }
} }
} }
wlv.vcol = cts.cts_vcol; wlv.vcol = vcol;
ptr = cts.cts_ptr; ptr = ci.ptr;
clear_chartabsize_arg(&cts); int charsize = cs.width;
int head = cs.head;
// When: // When:
// - 'cuc' is set, or // - 'cuc' is set, or
@ -2081,13 +2082,12 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
&& vim_isbreak(mb_c) && !vim_isbreak((uint8_t)(*ptr))) { && vim_isbreak(mb_c) && !vim_isbreak((uint8_t)(*ptr))) {
int mb_off = utf_head_off(line, ptr - 1); int mb_off = utf_head_off(line, ptr - 1);
char *p = ptr - (mb_off + 1); char *p = ptr - (mb_off + 1);
chartabsize_T cts;
init_chartabsize_arg(&cts, wp, lnum, wlv.vcol, line, p); CharsizeArg arg;
// do not want virtual text to be counted here // lnum == 0, do not want virtual text to be counted here
cts.cts_has_virt_text = false; CSType cstype = init_charsize_arg(&arg, wp, 0, line);
wlv.n_extra = win_lbr_chartabsize(&cts, NULL) - 1; wlv.n_extra = win_charsize(cstype, wlv.vcol, p, utf_ptr2CharInfo(p).value,
clear_chartabsize_arg(&cts); &arg).width - 1;
if (on_last_col && mb_c != TAB) { if (on_last_col && mb_c != TAB) {
// Do not continue search/match highlighting over the // Do not continue search/match highlighting over the

View File

@ -1679,33 +1679,37 @@ void change_indent(int type, int amount, int round, int replaced, bool call_chan
} else { } else {
// Compute the screen column where the cursor should be. // Compute the screen column where the cursor should be.
vcol = get_indent() - vcol; vcol = get_indent() - vcol;
curwin->w_virtcol = (colnr_T)((vcol < 0) ? 0 : vcol); int const end_vcol = (colnr_T)((vcol < 0) ? 0 : vcol);
curwin->w_virtcol = end_vcol;
// Advance the cursor until we reach the right screen column. // Advance the cursor until we reach the right screen column.
int last_vcol = 0; new_cursor_col = 0;
char *ptr = get_cursor_line_ptr(); char *const line = get_cursor_line_ptr();
chartabsize_T cts; vcol = 0;
init_chartabsize_arg(&cts, curwin, 0, 0, ptr, ptr); if (*line != NUL) {
while (cts.cts_vcol <= (int)curwin->w_virtcol) { CharsizeArg arg;
last_vcol = cts.cts_vcol; CSType cstype = init_charsize_arg(&arg, curwin, 0, line);
if (cts.cts_vcol > 0) { StrCharInfo ci = utf_ptr2StrCharInfo(line);
MB_PTR_ADV(cts.cts_ptr); while (true) {
int next_vcol = vcol + win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &arg).width;
if (next_vcol > end_vcol) {
break;
}
vcol = next_vcol;
ci = utfc_next(ci);
if (*ci.ptr == NUL) {
break;
}
} }
if (*cts.cts_ptr == NUL) { new_cursor_col = (int)(ci.ptr - line);
break;
}
cts.cts_vcol += lbr_chartabsize(&cts);
} }
vcol = last_vcol;
new_cursor_col = (int)(cts.cts_ptr - cts.cts_line);
clear_chartabsize_arg(&cts);
// May need to insert spaces to be able to position the cursor on // May need to insert spaces to be able to position the cursor on
// the right screen column. // the right screen column.
if (vcol != (int)curwin->w_virtcol) { if (vcol != (int)curwin->w_virtcol) {
curwin->w_cursor.col = (colnr_T)new_cursor_col; curwin->w_cursor.col = (colnr_T)new_cursor_col;
size_t i = (size_t)(curwin->w_virtcol - vcol); size_t i = (size_t)(curwin->w_virtcol - vcol);
ptr = xmallocz(i); char *ptr = xmallocz(i);
memset(ptr, ' ', i); memset(ptr, ' ', i);
new_cursor_col += (int)i; new_cursor_col += (int)i;
ins_str(ptr); ins_str(ptr);
@ -4347,14 +4351,16 @@ static bool ins_tab(void)
getvcol(curwin, cursor, &want_vcol, NULL, NULL); getvcol(curwin, cursor, &want_vcol, NULL, NULL);
char *tab = "\t"; char *tab = "\t";
chartabsize_T cts; int32_t tab_v = (uint8_t)(*tab);
init_chartabsize_arg(&cts, curwin, 0, vcol, tab, tab);
CharsizeArg arg;
CSType cstype = init_charsize_arg(&arg, curwin, 0, tab);
// Use as many TABs as possible. Beware of 'breakindent', 'showbreak' // Use as many TABs as possible. Beware of 'breakindent', 'showbreak'
// and 'linebreak' adding extra virtual columns. // and 'linebreak' adding extra virtual columns.
while (ascii_iswhite(*ptr)) { while (ascii_iswhite(*ptr)) {
int i = lbr_chartabsize(&cts); int i = win_charsize(cstype, vcol, tab, tab_v, &arg).width;
if (cts.cts_vcol + i > want_vcol) { if (vcol + i > want_vcol) {
break; break;
} }
if (*ptr != TAB) { if (*ptr != TAB) {
@ -4369,23 +4375,18 @@ static bool ins_tab(void)
} }
fpos.col++; fpos.col++;
ptr++; ptr++;
cts.cts_vcol += i; vcol += i;
} }
vcol = cts.cts_vcol;
clear_chartabsize_arg(&cts);
if (change_col >= 0) { if (change_col >= 0) {
int repl_off = 0; int repl_off = 0;
// Skip over the spaces we need. // Skip over the spaces we need.
init_chartabsize_arg(&cts, curwin, 0, vcol, ptr, ptr); cstype = init_charsize_arg(&arg, curwin, 0, ptr);
while (cts.cts_vcol < want_vcol && *cts.cts_ptr == ' ') { while (vcol < want_vcol && *ptr == ' ') {
cts.cts_vcol += lbr_chartabsize(&cts); vcol += win_charsize(cstype, vcol, ptr, (uint8_t)(' '), &arg).width;
cts.cts_ptr++; ptr++;
repl_off++; repl_off++;
} }
ptr = cts.cts_ptr;
vcol = cts.cts_vcol;
clear_chartabsize_arg(&cts);
if (vcol > want_vcol) { if (vcol > want_vcol) {
// Must have a char with 'showbreak' just before it. // Must have a char with 'showbreak' just before it.
@ -4556,8 +4557,6 @@ static int ins_digraph(void)
// Returns the char to be inserted, or NUL if none found. // Returns the char to be inserted, or NUL if none found.
int ins_copychar(linenr_T lnum) int ins_copychar(linenr_T lnum)
{ {
char *ptr;
if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) { if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) {
vim_beep(BO_COPY); vim_beep(BO_COPY);
return NUL; return NUL;
@ -4565,24 +4564,22 @@ int ins_copychar(linenr_T lnum)
// try to advance to the cursor column // try to advance to the cursor column
validate_virtcol(); validate_virtcol();
int const end_vcol = curwin->w_virtcol;
char *line = ml_get(lnum); char *line = ml_get(lnum);
char *prev_ptr = line;
chartabsize_T cts; CharsizeArg arg;
init_chartabsize_arg(&cts, curwin, lnum, 0, line, line); CSType cstype = init_charsize_arg(&arg, curwin, lnum, line);
while (cts.cts_vcol < curwin->w_virtcol && *cts.cts_ptr != NUL) { StrCharInfo ci = utf_ptr2StrCharInfo(line);
prev_ptr = cts.cts_ptr; int vcol = 0;
cts.cts_vcol += lbr_chartabsize_adv(&cts); while (vcol < end_vcol && *ci.ptr != NUL) {
vcol += win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &arg).width;
if (vcol > end_vcol) {
break;
}
ci = utfc_next(ci);
} }
if (cts.cts_vcol > curwin->w_virtcol) { int c = ci.chr.value < 0 ? (uint8_t)(*ci.ptr) : ci.chr.value;
ptr = prev_ptr;
} else {
ptr = cts.cts_ptr;
}
clear_chartabsize_arg(&cts);
int c = utf_ptr2char(ptr);
if (c == NUL) { if (c == NUL) {
vim_beep(BO_COPY); vim_beep(BO_COPY);
} }

View File

@ -2502,20 +2502,22 @@ static int vgetorpeek(bool advance)
// we are expecting to truncate the trailing // we are expecting to truncate the trailing
// white-space, so find the last non-white // white-space, so find the last non-white
// character -- webb // character -- webb
if (did_ai if (did_ai && *skipwhite(get_cursor_line_ptr() + curwin->w_cursor.col) == NUL) {
&& *skipwhite(get_cursor_line_ptr() + curwin->w_cursor.col) == NUL) {
curwin->w_wcol = 0; curwin->w_wcol = 0;
ptr = get_cursor_line_ptr(); ptr = get_cursor_line_ptr();
chartabsize_T cts; char *endptr = ptr + curwin->w_cursor.col;
init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum, 0, ptr, ptr);
while (cts.cts_ptr < ptr + curwin->w_cursor.col) { CharsizeArg arg;
if (!ascii_iswhite(*cts.cts_ptr)) { CSType cstype = init_charsize_arg(&arg, curwin, curwin->w_cursor.lnum, ptr);
curwin->w_wcol = cts.cts_vcol; StrCharInfo ci = utf_ptr2StrCharInfo(ptr);
int vcol = 0;
while (ci.ptr < endptr) {
if (!ascii_iswhite(ci.chr.value)) {
curwin->w_wcol = vcol;
} }
cts.cts_vcol += lbr_chartabsize(&cts); vcol += win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &arg).width;
cts.cts_ptr += utfc_ptr2len(cts.cts_ptr); ci = utfc_next(ci);
} }
clear_chartabsize_arg(&cts);
curwin->w_wrow = curwin->w_cline_row curwin->w_wrow = curwin->w_cline_row
+ curwin->w_wcol / curwin->w_width_inner; + curwin->w_wcol / curwin->w_width_inner;

View File

@ -1246,18 +1246,19 @@ int get_lisp_indent(void)
curwin->w_cursor.col = pos->col; curwin->w_cursor.col = pos->col;
colnr_T col = pos->col; colnr_T col = pos->col;
char *that = get_cursor_line_ptr(); char *line = get_cursor_line_ptr();
char *line = that; CharsizeArg arg;
chartabsize_T cts; CSType cstype = init_charsize_arg(&arg, curwin, pos->lnum, line);
init_chartabsize_arg(&cts, curwin, pos->lnum, 0, line, line);
while (*cts.cts_ptr != NUL && col > 0) { StrCharInfo sci = utf_ptr2StrCharInfo(line);
cts.cts_vcol += lbr_chartabsize_adv(&cts); amount = 0;
while (*sci.ptr != NUL && col > 0) {
amount += win_charsize(cstype, amount, sci.ptr, sci.chr.value, &arg).width;
sci = utfc_next(sci);
col--; col--;
} }
amount = cts.cts_vcol; char *that = sci.ptr;
that = cts.cts_ptr;
clear_chartabsize_arg(&cts);
// Some keywords require "body" indenting rules (the // Some keywords require "body" indenting rules (the
// non-standard-lisp ones are Scheme special forms): // non-standard-lisp ones are Scheme special forms):
@ -1272,15 +1273,10 @@ int get_lisp_indent(void)
} }
colnr_T firsttry = amount; colnr_T firsttry = amount;
init_chartabsize_arg(&cts, curwin, (colnr_T)(that - line), while (ascii_iswhite(*that)) {
amount, line, that); amount += win_charsize(cstype, amount, that, (uint8_t)(*that), &arg).width;
while (ascii_iswhite(*cts.cts_ptr)) { that++;
cts.cts_vcol += lbr_chartabsize(&cts);
cts.cts_ptr++;
} }
that = cts.cts_ptr;
amount = cts.cts_vcol;
clear_chartabsize_arg(&cts);
if (*that && (*that != ';')) { if (*that && (*that != ';')) {
// Not a comment line. // Not a comment line.
@ -1292,37 +1288,38 @@ int get_lisp_indent(void)
parencount = 0; parencount = 0;
init_chartabsize_arg(&cts, curwin, CharInfo ci = utf_ptr2CharInfo(that);
(colnr_T)(that - line), amount, line, that); if (((ci.value != '"') && (ci.value != '\'') && (ci.value != '#')
if (((*that != '"') && (*that != '\'') && (*that != '#') && ((ci.value < '0') || (ci.value > '9')))) {
&& (((uint8_t)(*that) < '0') || ((uint8_t)(*that) > '9')))) {
int quotecount = 0; int quotecount = 0;
while (*cts.cts_ptr while (*that && (!ascii_iswhite(ci.value) || quotecount || parencount)) {
&& (!ascii_iswhite(*cts.cts_ptr) || quotecount || parencount)) { if (ci.value == '"') {
if (*cts.cts_ptr == '"') {
quotecount = !quotecount; quotecount = !quotecount;
} }
if (((*cts.cts_ptr == '(') || (*cts.cts_ptr == '[')) && !quotecount) { if (((ci.value == '(') || (ci.value == '[')) && !quotecount) {
parencount++; parencount++;
} }
if (((*cts.cts_ptr == ')') || (*cts.cts_ptr == ']')) && !quotecount) { if (((ci.value == ')') || (ci.value == ']')) && !quotecount) {
parencount--; parencount--;
} }
if ((*cts.cts_ptr == '\\') && (*(cts.cts_ptr + 1) != NUL)) { if ((ci.value == '\\') && (*(that + 1) != NUL)) {
cts.cts_vcol += lbr_chartabsize_adv(&cts); amount += win_charsize(cstype, amount, that, ci.value, &arg).width;
StrCharInfo next_sci = utfc_next((StrCharInfo){ that, ci });
that = next_sci.ptr;
ci = next_sci.chr;
} }
cts.cts_vcol += lbr_chartabsize_adv(&cts); amount += win_charsize(cstype, amount, that, ci.value, &arg).width;
StrCharInfo next_sci = utfc_next((StrCharInfo){ that, ci });
that = next_sci.ptr;
ci = next_sci.chr;
} }
} }
while (ascii_iswhite(*cts.cts_ptr)) { while (ascii_iswhite(*that)) {
cts.cts_vcol += lbr_chartabsize(&cts); amount += win_charsize(cstype, amount, that, (uint8_t)(*that), &arg).width;
cts.cts_ptr++; that++;
} }
that = cts.cts_ptr;
amount = cts.cts_vcol;
clear_chartabsize_arg(&cts);
if (!*that || (*that == ';')) { if (!*that || (*that == ';')) {
amount = firsttry; amount = firsttry;

View File

@ -111,10 +111,13 @@
#endif #endif
#if defined(__clang__) || defined(__GNUC__) #if defined(__clang__) || defined(__GNUC__)
# define EXPECT(cond, value) __builtin_expect((cond), (value))
# define UNREACHABLE __builtin_unreachable() # define UNREACHABLE __builtin_unreachable()
#elif defined(_MSVC_VER) #elif defined(_MSC_VER)
# define EXPECT(cond, value) (cond)
# define UNREACHABLE __assume(false) # define UNREACHABLE __assume(false)
#else #else
# define EXPECT(cond, value) (cond)
# define UNREACHABLE # define UNREACHABLE
#endif #endif

View File

@ -528,6 +528,74 @@ int utf_ptr2cells(const char *p)
return 1; return 1;
} }
/// Convert a UTF-8 byte sequence to a character number.
/// Doesn't handle ascii! only multibyte and illegal sequences.
///
/// @param[in] p String to convert.
/// @param[in] len Length of the character in bytes, 0 or 1 if illegal.
///
/// @return Unicode codepoint. A negative value When the sequence is illegal.
int32_t utf_ptr2CharInfo_impl(uint8_t const *p, uintptr_t const len)
FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
// uint8_t is a reminder for clang to use smaller cmp
#define CHECK \
do { \
if (EXPECT((uint8_t)(cur & 0xC0U) != 0x80U, false)) { \
return -1; \
} \
} while (0)
static uint32_t const corrections[] = {
(1U << 31), // invalid - set invalid bits (safe to add as first 2 bytes
(1U << 31), // won't affect highest bit in normal ret)
-(0x80U + (0xC0U << 6)), // multibyte - subtract added UTF8 bits (1..10xxx and 10xxx)
-(0x80U + (0x80U << 6) + (0xE0U << 12)),
-(0x80U + (0x80U << 6) + (0x80U << 12) + (0xF0U << 18)),
-(0x80U + (0x80U << 6) + (0x80U << 12) + (0x80U << 18) + (0xF8U << 24)),
-(0x80U + (0x80U << 6) + (0x80U << 12) + (0x80U << 18) + (0x80U << 24)), // + (0xFCU << 30)
};
// len is 0-6, but declared uintptr_t to avoid zeroing out upper bits
uint32_t const corr = corrections[len];
uint8_t cur;
// reading second byte unconditionally, safe for invalid
// as it cannot be the last byte, not safe for ascii
uint32_t code_point = ((uint32_t)p[0] << 6) + (cur = p[1]);
CHECK;
if ((uint32_t)len < 3) {
goto ret; // len == 0, 1, 2
}
code_point = (code_point << 6) + (cur = p[2]);
CHECK;
if ((uint32_t)len == 3) {
goto ret;
}
code_point = (code_point << 6) + (cur = p[3]);
CHECK;
if ((uint32_t)len == 4) {
goto ret;
}
code_point = (code_point << 6) + (cur = p[4]);
CHECK;
if ((uint32_t)len == 5) {
goto ret;
}
code_point = (code_point << 6) + (cur = p[5]);
CHECK;
// len == 6
ret:
return (int32_t)(code_point + corr);
#undef CHECK
}
/// Like utf_ptr2cells(), but limit string length to "size". /// Like utf_ptr2cells(), but limit string length to "size".
/// For an empty string or truncated character returns 1. /// For an empty string or truncated character returns 1.
int utf_ptr2cells_len(const char *p, int size) int utf_ptr2cells_len(const char *p, int size)
@ -597,45 +665,62 @@ size_t mb_string2cells_len(const char *str, size_t size)
/// ///
/// @return Unicode codepoint or byte value. /// @return Unicode codepoint or byte value.
int utf_ptr2char(const char *const p_in) int utf_ptr2char(const char *const p_in)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{ {
uint8_t *p = (uint8_t *)p_in; uint8_t *p = (uint8_t *)p_in;
if (p[0] < 0x80) { // Be quick for ASCII.
return p[0]; uint32_t const v0 = p[0];
if (EXPECT(v0 < 0x80U, true)) { // Be quick for ASCII.
return (int)v0;
} }
const uint8_t len = utf8len_tab_zero[p[0]]; const uint8_t len = utf8len_tab[v0];
if (len > 1 && (p[1] & 0xc0) == 0x80) { if (EXPECT(len < 2, false)) {
if (len == 2) { return (int)v0;
return ((p[0] & 0x1f) << 6) + (p[1] & 0x3f);
}
if ((p[2] & 0xc0) == 0x80) {
if (len == 3) {
return (((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6)
+ (p[2] & 0x3f));
}
if ((p[3] & 0xc0) == 0x80) {
if (len == 4) {
return (((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12)
+ ((p[2] & 0x3f) << 6) + (p[3] & 0x3f));
}
if ((p[4] & 0xc0) == 0x80) {
if (len == 5) {
return (((p[0] & 0x03) << 24) + ((p[1] & 0x3f) << 18)
+ ((p[2] & 0x3f) << 12) + ((p[3] & 0x3f) << 6)
+ (p[4] & 0x3f));
}
if ((p[5] & 0xc0) == 0x80 && len == 6) {
return (((p[0] & 0x01) << 30) + ((p[1] & 0x3f) << 24)
+ ((p[2] & 0x3f) << 18) + ((p[3] & 0x3f) << 12)
+ ((p[4] & 0x3f) << 6) + (p[5] & 0x3f));
}
}
}
}
} }
// Illegal value: just return the first byte.
return p[0]; #define CHECK(v) \
do { \
if (EXPECT((uint8_t)((v) & 0xC0U) != 0x80U, false)) { \
return (int)v0; \
} \
} while (0)
#define LEN_RETURN(len_v, result) \
do { \
if (len == (len_v)) { \
return (int)(result); \
} \
} while (0)
#define S(s) ((uint32_t)0x80U << (s))
uint32_t const v1 = p[1];
CHECK(v1);
LEN_RETURN(2, (v0 << 6) + v1 - ((0xC0U << 6) + S(0)));
uint32_t const v2 = p[2];
CHECK(v2);
LEN_RETURN(3, (v0 << 12) + (v1 << 6) + v2 - ((0xE0U << 12) + S(6) + S(0)));
uint32_t const v3 = p[3];
CHECK(v3);
LEN_RETURN(4, (v0 << 18) + (v1 << 12) + (v2 << 6) + v3
- ((0xF0U << 18) + S(12) + S(6) + S(0)));
uint32_t const v4 = p[4];
CHECK(v4);
LEN_RETURN(5, (v0 << 24) + (v1 << 18) + (v2 << 12) + (v3 << 6) + v4
- ((0xF8U << 24) + S(18) + S(12) + S(6) + S(0)));
uint32_t const v5 = p[5];
CHECK(v5);
// len == 6
return (int)((v0 << 30) + (v1 << 24) + (v2 << 18) + (v3 << 12) + (v4 << 6) + v5
// - (0xFCU << 30)
- (S(24) + S(18) + S(12) + S(6) + S(0)));
#undef S
#undef CHECK
#undef LEN_RETURN
} }
// Convert a UTF-8 byte sequence to a wide character. // Convert a UTF-8 byte sequence to a wide character.
@ -722,6 +807,16 @@ bool utf_composinglike(const char *p1, const char *p2)
return arabic_combine(utf_ptr2char(p1), c2); return arabic_combine(utf_ptr2char(p1), c2);
} }
/// Check if the next character is a composing character when it
/// comes after the first. For Arabic sometimes "ab" is replaced with "c", which
/// behaves like a composing character.
/// returns false for negative values
bool utf_char_composinglike(int32_t const first, int32_t const next)
FUNC_ATTR_PURE
{
return utf_iscomposing(next) || arabic_combine(first, next);
}
/// Get the screen char at the beginning of a string /// Get the screen char at the beginning of a string
/// ///
/// Caller is expected to check for things like unprintable chars etc /// Caller is expected to check for things like unprintable chars etc
@ -988,9 +1083,10 @@ int utf_char2bytes(const int c, char *const buf)
} }
} }
// Return true if "c" is a composing UTF-8 character. This means it will be /// Return true if "c" is a composing UTF-8 character.
// drawn on top of the preceding character. /// This means it will be drawn on top of the preceding character.
// Based on code from Markus Kuhn. /// Based on code from Markus Kuhn.
/// Returns false for negative values.
bool utf_iscomposing(int c) bool utf_iscomposing(int c)
{ {
return intable(combining, ARRAY_SIZE(combining), c); return intable(combining, ARRAY_SIZE(combining), c);

View File

@ -6,6 +6,7 @@
#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep #include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
#include "nvim/eval/typval_defs.h" // IWYU pragma: keep #include "nvim/eval/typval_defs.h" // IWYU pragma: keep
#include "nvim/macros_defs.h"
#include "nvim/mbyte_defs.h" // IWYU pragma: keep #include "nvim/mbyte_defs.h" // IWYU pragma: keep
#include "nvim/types_defs.h" // IWYU pragma: keep #include "nvim/types_defs.h" // IWYU pragma: keep
@ -13,6 +14,10 @@
# include "mbyte.h.generated.h" # include "mbyte.h.generated.h"
#endif #endif
enum {
kInvalidByteCells = 4,
};
// Return byte length of character that starts with byte "b". // Return byte length of character that starts with byte "b".
// Returns 1 for a single-byte character. // Returns 1 for a single-byte character.
// MB_BYTE2LEN_CHECK() can be used to count a special key as one byte. // MB_BYTE2LEN_CHECK() can be used to count a special key as one byte.
@ -44,3 +49,72 @@ extern const uint8_t utf8len_tab[256];
// multi-byte characters if needed. Only use with "p" > "s" ! // multi-byte characters if needed. Only use with "p" > "s" !
#define MB_PTR_BACK(s, p) \ #define MB_PTR_BACK(s, p) \
(p -= utf_head_off((char *)(s), (char *)(p) - 1) + 1) (p -= utf_head_off((char *)(s), (char *)(p) - 1) + 1)
static inline CharInfo utf_ptr2CharInfo(char const *p_in)
REAL_FATTR_NONNULL_ALL REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE;
/// Convert a UTF-8 byte sequence to a Unicode code point.
/// Handles ascii, multibyte sequiences and illegal sequences.
///
/// @param[in] p_in String to convert.
///
/// @return information abouth the character. When the sequence is illegal,
/// 'value' is negative, 'len' is 1.
static inline CharInfo utf_ptr2CharInfo(char const *const p_in)
{
uint8_t const *const p = (uint8_t const *)p_in;
uint8_t const first = *p;
if (first < 0x80) {
return (CharInfo){ .value = first, .len = 1 };
} else {
int len = utf8len_tab[first];
int32_t const code_point = utf_ptr2CharInfo_impl(p, (uintptr_t)len);
if (code_point < 0) {
len = 1;
}
return (CharInfo){ .value = code_point, .len = len };
}
}
static inline StrCharInfo utfc_next(StrCharInfo cur)
REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE;
/// Return information about the next character.
/// Composing and combining characters are
/// considered a part of the current character.
///
/// @param[in] cur Pointer to the current character. Must not point to NUL
/// @param[in] cur_char Decoded charater at 'cur'.
static inline StrCharInfo utfc_next(StrCharInfo cur)
{
int32_t prev_code = cur.chr.value;
uint8_t *next = (uint8_t *)(cur.ptr + cur.chr.len);
while (true) {
if (EXPECT(*next < 0x80U, true)) {
return (StrCharInfo){
.ptr = (char *)next,
.chr = (CharInfo){ .value = *next, .len = 1 },
};
}
uint8_t const next_len = utf8len_tab[*next];
int32_t const next_code = utf_ptr2CharInfo_impl(next, (uintptr_t)next_len);
if (!utf_char_composinglike(prev_code, next_code)) {
return (StrCharInfo){
.ptr = (char *)next,
.chr = (CharInfo){ .value = next_code, .len = (next_code < 0 ? 1 : next_len) },
};
}
prev_code = next_code;
next += next_len;
}
}
static inline StrCharInfo utf_ptr2StrCharInfo(char *ptr)
REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE;
static inline StrCharInfo utf_ptr2StrCharInfo(char *ptr)
{
return (StrCharInfo){ .ptr = ptr, .chr = utf_ptr2CharInfo(ptr) };
}

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h>
#include "nvim/iconv_defs.h" #include "nvim/iconv_defs.h"
@ -55,3 +56,13 @@ typedef struct {
bool vc_fail; ///< What to do with invalid characters: if true, fail, bool vc_fail; ///< What to do with invalid characters: if true, fail,
///< otherwise use '?'. ///< otherwise use '?'.
} vimconv_T; } vimconv_T;
typedef struct {
int32_t value; ///< code point
int len; ///< length in bytes
} CharInfo;
typedef struct {
char *ptr; ///< pointer to the first byte of the character
CharInfo chr; ///< the character
} StrCharInfo;

View File

@ -1755,22 +1755,23 @@ colnr_T vcol2col(win_T *wp, linenr_T lnum, colnr_T vcol, colnr_T *coladdp)
{ {
// try to advance to the specified column // try to advance to the specified column
char *line = ml_get_buf(wp->w_buffer, lnum); char *line = ml_get_buf(wp->w_buffer, lnum);
chartabsize_T cts; CharsizeArg arg;
init_chartabsize_arg(&cts, wp, lnum, 0, line, line); CSType cstype = init_charsize_arg(&arg, wp, lnum, line);
while (cts.cts_vcol < vcol && *cts.cts_ptr != NUL) { StrCharInfo ci = utf_ptr2StrCharInfo(line);
int size = win_lbr_chartabsize(&cts, NULL); int cur_vcol = 0;
if (cts.cts_vcol + size > vcol) { while (cur_vcol < vcol && *ci.ptr != NUL) {
int next_vcol = cur_vcol + win_charsize(cstype, cur_vcol, ci.ptr, ci.chr.value, &arg).width;
if (next_vcol > vcol) {
break; break;
} }
cts.cts_vcol += size; cur_vcol = next_vcol;
MB_PTR_ADV(cts.cts_ptr); ci = utfc_next(ci);
} }
clear_chartabsize_arg(&cts);
if (coladdp != NULL) { if (coladdp != NULL) {
*coladdp = vcol - cts.cts_vcol; *coladdp = vcol - cur_vcol;
} }
return (colnr_T)(cts.cts_ptr - line); return (colnr_T)(ci.ptr - line);
} }
/// Set UI mouse depending on current mode and 'mouse'. /// Set UI mouse depending on current mode and 'mouse'.

View File

@ -387,17 +387,18 @@ static void shift_block(oparg_T *oap, int amount)
} }
// TODO(vim): is passing bd.textstart for start of the line OK? // TODO(vim): is passing bd.textstart for start of the line OK?
chartabsize_T cts; CharsizeArg arg;
init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum, CSType cstype = init_charsize_arg(&arg, curwin, curwin->w_cursor.lnum, bd.textstart);
bd.start_vcol, bd.textstart, bd.textstart); StrCharInfo ci = utf_ptr2StrCharInfo(bd.textstart);
while (ascii_iswhite(*cts.cts_ptr)) { int vcol = bd.start_vcol;
incr = lbr_chartabsize_adv(&cts); while (ascii_iswhite(ci.chr.value)) {
incr = win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &arg).width;
ci = utfc_next(ci);
total += incr; total += incr;
cts.cts_vcol += incr; vcol += incr;
} }
bd.textstart = cts.cts_ptr; bd.textstart = ci.ptr;
bd.start_vcol = cts.cts_vcol; bd.start_vcol = vcol;
clear_chartabsize_arg(&cts);
int tabs = 0; int tabs = 0;
int spaces = 0; int spaces = 0;
@ -448,16 +449,13 @@ static void shift_block(oparg_T *oap, int amount)
// The character's column is in "bd.start_vcol". // The character's column is in "bd.start_vcol".
colnr_T non_white_col = bd.start_vcol; colnr_T non_white_col = bd.start_vcol;
chartabsize_T cts; CharsizeArg arg;
init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum, CSType cstype = init_charsize_arg(&arg, curwin, curwin->w_cursor.lnum, bd.textstart);
non_white_col, bd.textstart, non_white); while (ascii_iswhite(*non_white)) {
while (ascii_iswhite(*cts.cts_ptr)) { incr = win_charsize(cstype, non_white_col, non_white, (uint8_t)(*non_white), &arg).width;
incr = lbr_chartabsize_adv(&cts); non_white_col += incr;
cts.cts_vcol += incr; non_white++;
} }
non_white_col = cts.cts_vcol;
non_white = cts.cts_ptr;
clear_chartabsize_arg(&cts);
const colnr_T block_space_width = non_white_col - oap->start_vcol; const colnr_T block_space_width = non_white_col - oap->start_vcol;
// We will shift by "total" or "block_space_width", whichever is less. // We will shift by "total" or "block_space_width", whichever is less.
@ -478,19 +476,17 @@ static void shift_block(oparg_T *oap, int amount)
if (bd.startspaces) { if (bd.startspaces) {
verbatim_copy_width -= bd.start_char_vcols; verbatim_copy_width -= bd.start_char_vcols;
} }
init_chartabsize_arg(&cts, curwin, 0, verbatim_copy_width, cstype = init_charsize_arg(&arg, curwin, 0, bd.textstart);
bd.textstart, verbatim_copy_end); StrCharInfo ci = utf_ptr2StrCharInfo(verbatim_copy_end);
while (cts.cts_vcol < destination_col) { while (verbatim_copy_width < destination_col) {
incr = lbr_chartabsize(&cts); incr = win_charsize(cstype, verbatim_copy_width, ci.ptr, ci.chr.value, &arg).width;
if (cts.cts_vcol + incr > destination_col) { if (verbatim_copy_width + incr > destination_col) {
break; break;
} }
cts.cts_vcol += incr; verbatim_copy_width += incr;
MB_PTR_ADV(cts.cts_ptr); ci = utfc_next(ci);
} }
verbatim_copy_width = cts.cts_vcol; verbatim_copy_end = ci.ptr;
verbatim_copy_end = cts.cts_ptr;
clear_chartabsize_arg(&cts);
// If "destination_col" is different from the width of the initial // If "destination_col" is different from the width of the initial
// part of the line that will be copied, it means we encountered a tab // part of the line that will be copied, it means we encountered a tab
@ -3250,19 +3246,19 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags)
} }
// get the old line and advance to the position to insert at // get the old line and advance to the position to insert at
char *oldp = get_cursor_line_ptr(); char *oldp = get_cursor_line_ptr();
size_t oldlen = strlen(oldp);
chartabsize_T cts;
init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum, 0, oldp, oldp);
while (cts.cts_vcol < col && *cts.cts_ptr != NUL) { CharsizeArg arg;
// Count a tab for what it's worth (if list mode not on) CSType cstype = init_charsize_arg(&arg, curwin, curwin->w_cursor.lnum, oldp);
incr = lbr_chartabsize_adv(&cts); StrCharInfo ci = utf_ptr2StrCharInfo(oldp);
cts.cts_vcol += incr; vcol = 0;
while (vcol < col && *ci.ptr != NUL) {
incr = win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &arg).width;
vcol += incr;
ci = utfc_next(ci);
} }
vcol = cts.cts_vcol; size_t oldlen = (size_t)(ci.ptr - oldp) + strlen(ci.ptr);
char *ptr = cts.cts_ptr; char *ptr = ci.ptr;
bd.textcol = (colnr_T)(ptr - oldp); bd.textcol = (colnr_T)(ptr - oldp);
clear_chartabsize_arg(&cts);
shortline = (vcol < col) || (vcol == col && !*ptr); shortline = (vcol < col) || (vcol == col && !*ptr);
@ -3286,16 +3282,15 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags)
yanklen = (int)strlen(y_array[i]); yanklen = (int)strlen(y_array[i]);
if ((flags & PUT_BLOCK_INNER) == 0) { if ((flags & PUT_BLOCK_INNER) == 0) {
// calculate number of spaces required to fill right side of // calculate number of spaces required to fill right side of block
// block
spaces = y_width + 1; spaces = y_width + 1;
init_chartabsize_arg(&cts, curwin, 0, 0, y_array[i], y_array[i]);
for (int j = 0; j < yanklen; j++) { cstype = init_charsize_arg(&arg, curwin, 0, y_array[i]);
spaces -= lbr_chartabsize(&cts); ci = utf_ptr2StrCharInfo(y_array[i]);
cts.cts_ptr++; while (*ci.ptr != NUL) {
cts.cts_vcol = 0; spaces -= win_charsize(cstype, 0, ci.ptr, ci.chr.value, &arg).width;
ci = utfc_next(ci);
} }
clear_chartabsize_arg(&cts);
if (spaces < 0) { if (spaces < 0) {
spaces = 0; spaces = 0;
} }
@ -4228,25 +4223,25 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool
char *line = ml_get(lnum); char *line = ml_get(lnum);
char *prev_pstart = line; char *prev_pstart = line;
chartabsize_T cts; CharsizeArg arg;
init_chartabsize_arg(&cts, curwin, lnum, bdp->start_vcol, line, line); CSType cstype = init_charsize_arg(&arg, curwin, lnum, line);
while (cts.cts_vcol < oap->start_vcol && *cts.cts_ptr != NUL) { StrCharInfo ci = utf_ptr2StrCharInfo(line);
// Count a tab for what it's worth (if list mode not on) int vcol = bdp->start_vcol;
incr = lbr_chartabsize(&cts); while (vcol < oap->start_vcol && *ci.ptr != NUL) {
cts.cts_vcol += incr; incr = win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &arg).width;
if (ascii_iswhite(*cts.cts_ptr)) { vcol += incr;
if (ascii_iswhite(ci.chr.value)) {
bdp->pre_whitesp += incr; bdp->pre_whitesp += incr;
bdp->pre_whitesp_c++; bdp->pre_whitesp_c++;
} else { } else {
bdp->pre_whitesp = 0; bdp->pre_whitesp = 0;
bdp->pre_whitesp_c = 0; bdp->pre_whitesp_c = 0;
} }
prev_pstart = cts.cts_ptr; prev_pstart = ci.ptr;
MB_PTR_ADV(cts.cts_ptr); ci = utfc_next(ci);
} }
bdp->start_vcol = cts.cts_vcol; bdp->start_vcol = vcol;
char *pstart = cts.cts_ptr; char *pstart = ci.ptr;
clear_chartabsize_arg(&cts);
bdp->start_char_vcols = incr; bdp->start_char_vcols = incr;
if (bdp->start_vcol < oap->start_vcol) { // line too short if (bdp->start_vcol < oap->start_vcol) { // line too short
@ -4283,17 +4278,18 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool
} }
} }
} else { } else {
init_chartabsize_arg(&cts, curwin, lnum, bdp->end_vcol, line, pend); cstype = init_charsize_arg(&arg, curwin, lnum, line);
ci = utf_ptr2StrCharInfo(pend);
vcol = bdp->end_vcol;
char *prev_pend = pend; char *prev_pend = pend;
while (cts.cts_vcol <= oap->end_vcol && *cts.cts_ptr != NUL) { while (vcol <= oap->end_vcol && *ci.ptr != NUL) {
// Count a tab for what it's worth (if list mode not on) prev_pend = ci.ptr;
prev_pend = cts.cts_ptr; incr = win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &arg).width;
incr = lbr_chartabsize_adv(&cts); vcol += incr;
cts.cts_vcol += incr; ci = utfc_next(ci);
} }
bdp->end_vcol = cts.cts_vcol; bdp->end_vcol = vcol;
pend = cts.cts_ptr; pend = ci.ptr;
clear_chartabsize_arg(&cts);
if (bdp->end_vcol <= oap->end_vcol if (bdp->end_vcol <= oap->end_vcol
&& (!is_del && (!is_del

View File

@ -51,48 +51,21 @@ int win_chartabsize(win_T *wp, char *p, colnr_T col)
return ptr2cells(p); return ptr2cells(p);
} }
/// Return the number of characters the string 's' will take on the screen, /// Like linetabsize_str(), but "s" starts at virtual column "startvcol".
/// taking into account the size of a tab.
///
/// @param s
///
/// @return Number of characters the string will take on the screen.
int linetabsize_str(char *s)
{
return linetabsize_col(0, s);
}
/// Like linetabsize_str(), but "s" starts at column "startcol".
/// ///
/// @param startcol /// @param startcol
/// @param s /// @param s
/// ///
/// @return Number of characters the string will take on the screen. /// @return Number of characters the string will take on the screen.
int linetabsize_col(int startcol, char *s) int linetabsize_col(int startvcol, char *s)
{ {
chartabsize_T cts; CharsizeArg arg;
init_chartabsize_arg(&cts, curwin, 0, startcol, s, s); CSType const cstype = init_charsize_arg(&arg, curwin, 0, s);
while (*cts.cts_ptr != NUL) { if (cstype == kCharsizeFast) {
cts.cts_vcol += lbr_chartabsize_adv(&cts); return linesize_fast(&arg, startvcol, MAXCOL);
} else {
return linesize_regular(&arg, startvcol, MAXCOL);
} }
clear_chartabsize_arg(&cts);
return cts.cts_vcol;
}
/// Like linetabsize_str(), but for a given window instead of the current one.
///
/// @param wp
/// @param line
/// @param len
///
/// @return Number of characters the string will take on the screen.
int win_linetabsize(win_T *wp, linenr_T lnum, char *line, colnr_T len)
{
chartabsize_T cts;
init_chartabsize_arg(&cts, wp, lnum, 0, line, line);
win_linetabsize_cts(&cts, len);
clear_chartabsize_arg(&cts);
return cts.cts_vcol;
} }
/// Return the number of cells line "lnum" of window "wp" will take on the /// Return the number of cells line "lnum" of window "wp" will take on the
@ -102,127 +75,80 @@ int linetabsize(win_T *wp, linenr_T lnum)
return win_linetabsize(wp, lnum, ml_get_buf(wp->w_buffer, lnum), (colnr_T)MAXCOL); return win_linetabsize(wp, lnum, ml_get_buf(wp->w_buffer, lnum), (colnr_T)MAXCOL);
} }
void win_linetabsize_cts(chartabsize_T *cts, colnr_T len) /// Prepare the structure passed to charsize functions.
{
for (; *cts->cts_ptr != NUL && (len == MAXCOL || cts->cts_ptr < cts->cts_line + len);
MB_PTR_ADV(cts->cts_ptr)) {
cts->cts_vcol += win_lbr_chartabsize(cts, NULL);
}
// check for inline virtual text after the end of the line
if (len == MAXCOL && cts->cts_has_virt_text && *cts->cts_ptr == NUL) {
win_lbr_chartabsize(cts, NULL);
cts->cts_vcol += cts->cts_cur_text_width_left + cts->cts_cur_text_width_right;
}
}
/// Prepare the structure passed to chartabsize functions.
/// ///
/// "line" is the start of the line, "ptr" is the first relevant character. /// "line" is the start of the line.
/// When "lnum" is zero do not use inline virtual text. /// When "lnum" is zero do not use inline virtual text.
void init_chartabsize_arg(chartabsize_T *cts, win_T *wp, linenr_T lnum, colnr_T col, char *line, CSType init_charsize_arg(CharsizeArg *cts, win_T *wp, linenr_T lnum, char *line)
char *ptr)
{ {
cts->cts_win = wp; cts->win = wp;
cts->cts_vcol = col; cts->line = line;
cts->cts_line = line; cts->max_head_vcol = 0;
cts->cts_ptr = ptr; cts->cur_text_width_left = 0;
cts->cts_max_head_vcol = 0; cts->cur_text_width_right = 0;
cts->cts_cur_text_width_left = 0; cts->virt_row = -1;
cts->cts_cur_text_width_right = 0; cts->indent_width = INT_MIN;
cts->cts_has_virt_text = false; cts->use_tabstop = !wp->w_p_list || wp->w_p_lcs_chars.tab1;
cts->cts_row = lnum - 1;
if (cts->cts_row >= 0 && wp->w_buffer->b_virt_text_inline > 0) { if (lnum > 0 && wp->w_buffer->b_virt_text_inline > 0) {
marktree_itr_get(wp->w_buffer->b_marktree, cts->cts_row, 0, cts->cts_iter); marktree_itr_get(wp->w_buffer->b_marktree, lnum - 1, 0, cts->iter);
MTKey mark = marktree_itr_current(cts->cts_iter); MTKey mark = marktree_itr_current(cts->iter);
if (mark.pos.row == cts->cts_row) { if (mark.pos.row == lnum - 1) {
cts->cts_has_virt_text = true; cts->virt_row = lnum - 1;
} }
} }
if (cts->virt_row >= 0
|| (wp->w_p_wrap && (wp->w_p_lbr || wp->w_p_bri || *get_showbreak_value(wp) != NUL))) {
return kCharsizeRegular;
} else {
return kCharsizeFast;
}
} }
/// Free any allocated item in "cts". /// Get the number of characters taken up on the screen for the given cts and position.
void clear_chartabsize_arg(chartabsize_T *cts) /// "cts->cur_text_width_left" and "cts->cur_text_width_right" are set
{
}
/// like win_chartabsize(), but also check for line breaks on the screen
///
/// @param cts
///
/// @return The number of characters taken up on the screen.
int lbr_chartabsize(chartabsize_T *cts)
{
if (!curwin->w_p_lbr && *get_showbreak_value(curwin) == NUL
&& !curwin->w_p_bri && !cts->cts_has_virt_text) {
if (curwin->w_p_wrap) {
return win_nolbr_chartabsize(cts, NULL);
}
return win_chartabsize(curwin, cts->cts_ptr, cts->cts_vcol);
}
return win_lbr_chartabsize(cts, NULL);
}
/// Call lbr_chartabsize() and advance the pointer.
///
/// @param cts
///
/// @return The number of characters take up on the screen.
int lbr_chartabsize_adv(chartabsize_T *cts)
{
int retval = lbr_chartabsize(cts);
MB_PTR_ADV(cts->cts_ptr);
return retval;
}
/// Get the number of characters taken up on the screen indicated by "cts".
/// "cts->cts_cur_text_width_left" and "cts->cts_cur_text_width_right" are set
/// to the extra size for inline virtual text. /// to the extra size for inline virtual text.
/// This function is used very often, keep it fast!!!! /// This function is used very often, keep it fast!!!!
/// ///
/// If "headp" not NULL, set "*headp" to the size of 'showbreak'/'breakindent' /// When "cts->max_head_vcol" is positive, only count in "head" the size
/// included in the return value. /// of 'showbreak'/'breakindent' before "cts->max_head_vcol".
/// When "cts->cts_max_head_vcol" is positive, only count in "*headp" the size /// When "cts->max_head_vcol" is negative, only count in "head" the size
/// of 'showbreak'/'breakindent' before "cts->cts_max_head_vcol".
/// When "cts->cts_max_head_vcol" is negative, only count in "*headp" the size
/// of 'showbreak'/'breakindent' before where cursor should be placed. /// of 'showbreak'/'breakindent' before where cursor should be placed.
/// CharSize charsize_regular(CharsizeArg *cts, char *const cur, colnr_T const vcol,
/// Warning: "*headp" may not be set if it's 0, init to 0 before calling. int32_t const cur_char)
int win_lbr_chartabsize(chartabsize_T *cts, int *headp)
{ {
win_T *wp = cts->cts_win; cts->cur_text_width_left = 0;
char *line = cts->cts_line; // start of the line cts->cur_text_width_right = 0;
char *s = cts->cts_ptr;
colnr_T vcol = cts->cts_vcol; win_T *wp = cts->win;
buf_T *buf = wp->w_buffer;
char *line = cts->line;
bool const use_tabstop = cur_char == TAB && cts->use_tabstop;
int mb_added = 0; int mb_added = 0;
cts->cts_cur_text_width_left = 0;
cts->cts_cur_text_width_right = 0;
// No 'linebreak', 'showbreak' and 'breakindent': return quickly.
if (!wp->w_p_lbr && !wp->w_p_bri && *get_showbreak_value(wp) == NUL
&& !cts->cts_has_virt_text) {
if (wp->w_p_wrap) {
return win_nolbr_chartabsize(cts, headp);
}
return win_chartabsize(wp, s, vcol);
}
bool has_lcs_eol = wp->w_p_list && wp->w_p_lcs_chars.eol != NUL; bool has_lcs_eol = wp->w_p_list && wp->w_p_lcs_chars.eol != NUL;
// First get normal size, without 'linebreak' or inline virtual text // First get normal size, without 'linebreak' or inline virtual text
int size = win_chartabsize(wp, s, vcol); int size;
if (*s == NUL && !has_lcs_eol) { int is_doublewidth = false;
size = 0; // NUL is not displayed if (use_tabstop) {
size = tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array);
} else if (*cur == NUL && !has_lcs_eol) {
size = 0;
} else if (cur_char < 0) {
size = kInvalidByteCells;
} else {
size = char2cells(cur_char);
is_doublewidth = size == 2 && cur_char > 0x80;
} }
bool is_doublewidth = size == 2 && MB_BYTE2LEN((uint8_t)(*s)) > 1;
if (cts->cts_has_virt_text) { if (cts->virt_row >= 0) {
int tab_size = size; int tab_size = size;
int col = (int)(s - line); int col = (int)(cur - line);
while (true) { while (true) {
MTKey mark = marktree_itr_current(cts->cts_iter); MTKey mark = marktree_itr_current(cts->iter);
if (mark.pos.row != cts->cts_row || mark.pos.col > col) { if (mark.pos.row != cts->virt_row || mark.pos.col > col) {
break; break;
} else if (mark.pos.col == col) { } else if (mark.pos.col == col) {
if (!mt_end(mark) && mark.flags & (MT_FLAG_DECOR_VIRT_TEXT_INLINE)) { if (!mt_end(mark) && mark.flags & (MT_FLAG_DECOR_VIRT_TEXT_INLINE)) {
@ -231,15 +157,15 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp)
while (vt) { while (vt) {
if (!(vt->flags & kVTIsLines) && vt->pos == kVPosInline) { if (!(vt->flags & kVTIsLines) && vt->pos == kVPosInline) {
if (mt_right(mark)) { if (mt_right(mark)) {
cts->cts_cur_text_width_right += vt->width; cts->cur_text_width_right += vt->width;
} else { } else {
cts->cts_cur_text_width_left += vt->width; cts->cur_text_width_left += vt->width;
} }
size += vt->width; size += vt->width;
if (*s == TAB) { if (use_tabstop) {
// tab size changes because of the inserted text // tab size changes because of the inserted text
size -= tab_size; size -= tab_size;
tab_size = win_chartabsize(wp, s, vcol + size); tab_size = tabstop_padding(vcol + size, buf->b_p_ts, buf->b_p_vts_array);
size += tab_size; size += tab_size;
} }
} }
@ -247,7 +173,7 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp)
} }
} }
} }
marktree_itr_next(wp->w_buffer->b_marktree, cts->cts_iter); marktree_itr_next(wp->w_buffer->b_marktree, cts->iter);
} }
} }
@ -257,16 +183,17 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp)
mb_added = 1; mb_added = 1;
} }
char *const sbr = get_showbreak_value(wp);
// May have to add something for 'breakindent' and/or 'showbreak' // May have to add something for 'breakindent' and/or 'showbreak'
// string at the start of a screen line. // string at the start of a screen line.
int head = mb_added; int head = mb_added;
char *const sbr = get_showbreak_value(wp);
// When "size" is 0, no new screen line is started. // When "size" is 0, no new screen line is started.
if (size > 0 && wp->w_p_wrap && (*sbr != NUL || wp->w_p_bri)) { if (size > 0 && wp->w_p_wrap && (*sbr != NUL || wp->w_p_bri)) {
int col_off_prev = win_col_off(wp); int col_off_prev = win_col_off(wp);
int width2 = wp->w_width_inner - col_off_prev + win_col_off2(wp); int width2 = wp->w_width_inner - col_off_prev + win_col_off2(wp);
colnr_T wcol = vcol + col_off_prev; colnr_T wcol = vcol + col_off_prev;
colnr_T max_head_vcol = cts->cts_max_head_vcol; colnr_T max_head_vcol = cts->max_head_vcol;
int added = 0; int added = 0;
// cells taken by 'showbreak'/'breakindent' before current char // cells taken by 'showbreak'/'breakindent' before current char
@ -277,11 +204,16 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp)
if (wcol >= width2 && width2 > 0) { if (wcol >= width2 && width2 > 0) {
wcol %= width2; wcol %= width2;
} }
if (*sbr != NUL) { head_prev = cts->indent_width;
head_prev += vim_strsize(sbr); if (head_prev == INT_MIN) {
} head_prev = 0;
if (wp->w_p_bri) { if (*sbr != NUL) {
head_prev += get_breakindent_win(wp, line); head_prev += vim_strsize(sbr);
}
if (wp->w_p_bri) {
head_prev += get_breakindent_win(wp, line);
}
cts->indent_width = head_prev;
} }
if (wcol < head_prev) { if (wcol < head_prev) {
head_prev -= wcol; head_prev -= wcol;
@ -298,12 +230,16 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp)
if (wcol + size > wp->w_width) { if (wcol + size > wp->w_width) {
// cells taken by 'showbreak'/'breakindent' halfway current char // cells taken by 'showbreak'/'breakindent' halfway current char
int head_mid = 0; int head_mid = cts->indent_width;
if (*sbr != NUL) { if (head_mid == INT_MIN) {
head_mid += vim_strsize(sbr); head_mid = 0;
} if (*sbr != NUL) {
if (wp->w_p_bri) { head_mid += vim_strsize(sbr);
head_mid += get_breakindent_win(wp, line); }
if (wp->w_p_bri) {
head_mid += get_breakindent_win(wp, line);
}
cts->indent_width = head_mid;
} }
if (head_mid > 0 && wcol + size > wp->w_width_inner) { if (head_mid > 0 && wcol + size > wp->w_width_inner) {
// Calculate effective window width. // Calculate effective window width.
@ -323,7 +259,7 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp)
head += (max_head_vcol - (vcol + head_prev + prev_rem) head += (max_head_vcol - (vcol + head_prev + prev_rem)
+ width2 - 1) / width2 * head_mid; + width2 - 1) / width2 * head_mid;
} else if (max_head_vcol < 0) { } else if (max_head_vcol < 0) {
int off = virt_text_cursor_off(cts, *s == NUL); int off = virt_text_cursor_off(cts, *cur == NUL);
if (off >= prev_rem) { if (off >= prev_rem) {
if (size > off) { if (size > off) {
head += (1 + (off - prev_rem) / width) * head_mid; head += (1 + (off - prev_rem) / width) * head_mid;
@ -338,19 +274,16 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp)
size += added; size += added;
} }
if (headp != NULL) { char *s = cur;
*headp = head;
}
colnr_T vcol_start = 0; // start from where to consider linebreak colnr_T vcol_start = 0; // start from where to consider linebreak
// If 'linebreak' set check at a blank before a non-blank if the line // If 'linebreak' set check at a blank before a non-blank if the line
// needs a break here // needs a break here
if (wp->w_p_lbr && wp->w_p_wrap && wp->w_width_inner != 0) { if (wp->w_p_lbr && wp->w_p_wrap && wp->w_width_inner != 0) {
char *t = cts->cts_line; char *t = cts->line;
while (vim_isbreak((uint8_t)t[0])) { while (vim_isbreak((uint8_t)t[0])) {
t++; t++;
} }
vcol_start = (colnr_T)(t - cts->cts_line); vcol_start = (colnr_T)(t - cts->line);
} }
if (wp->w_p_lbr && vcol_start <= vcol if (wp->w_p_lbr && vcol_start <= vcol
&& vim_isbreak((uint8_t)s[0]) && vim_isbreak((uint8_t)s[0])
@ -388,39 +321,50 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp)
} }
} }
return size; return (CharSize){ .width = size, .head = head };
} }
/// Like win_lbr_chartabsize(), except that we know 'linebreak' is off and /// Like charsize_regular(), except it doesn't handle virtual text,
/// 'wrap' is on. This means we need to check for a double-byte character that /// linebreak, breakindent and showbreak. Handles normal characters, tabs and wrapping.
/// doesn't fit at the end of the screen line. /// This function is always inlined.
/// ///
/// @param cts /// @see charsize_regular
/// @param headp /// @see charsize_fast
/// static inline CharSize charsize_fast_impl(win_T *const wp, bool use_tabstop, colnr_T const vcol,
/// @return The number of characters take up on the screen. int32_t const cur_char)
static int win_nolbr_chartabsize(chartabsize_T *cts, int *headp) FUNC_ATTR_PURE FUNC_ATTR_ALWAYS_INLINE
{ {
win_T *wp = cts->cts_win; // A tab gets expanded, depending on the current column
char *s = cts->cts_ptr; if (cur_char == TAB && use_tabstop) {
colnr_T col = cts->cts_vcol; return (CharSize){
.width = tabstop_padding(vcol, wp->w_buffer->b_p_ts,
if ((*s == TAB) && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { wp->w_buffer->b_p_vts_array)
return tabstop_padding(col, };
wp->w_buffer->b_p_ts, } else {
wp->w_buffer->b_p_vts_array); int width;
} if (cur_char < 0) {
int n = ptr2cells(s); width = kInvalidByteCells;
} else {
// Add one cell for a double-width character in the last column of the width = char2cells(cur_char);
// window, displayed with a ">". }
if ((n == 2) && (MB_BYTE2LEN((uint8_t)(*s)) > 1) && in_win_border(wp, col)) {
if (headp != NULL) { // If a double-width char doesn't fit at the end of a line, it wraps to the next line,
*headp = 1; // and the last column displays a '>'.
if (width == 2 && cur_char >= 0x80 && wp->w_p_wrap && in_win_border(wp, vcol)) {
return (CharSize){ .width = 3, .head = 1 };
} else {
return (CharSize){ .width = width };
} }
return 3;
} }
return n; }
/// Like charsize_regular(), except it doesn't handle virtual text,
/// linebreak, breakindent and showbreak. Handles normal characters, tabs and wrapping.
/// Can be used if CSType is kCharsizeFast.
CharSize charsize_fast(CharsizeArg *cts, colnr_T const vcol, int32_t const cur_char)
FUNC_ATTR_PURE
{
return charsize_fast_impl(cts->win, cts->use_tabstop, vcol, cur_char);
} }
/// Check that virtual column "vcol" is in the rightmost column of window "wp". /// Check that virtual column "vcol" is in the rightmost column of window "wp".
@ -451,19 +395,63 @@ static bool in_win_border(win_T *wp, colnr_T vcol)
return (vcol - width1) % width2 == width2 - 1; return (vcol - width1) % width2 == width2 - 1;
} }
/// Calculate virtual column until the given 'len'.
///
/// @param arg Argument to charsize functions.
/// @param vcol Starting virtual column.
/// @param len First byte of the end character, or MAXCOL.
///
/// @return virtual column before the character at 'len',
/// or full size of the line if 'len' is MAXCOL.
int linesize_regular(CharsizeArg *const arg, int vcol, colnr_T const len)
{
char *const line = arg->line;
StrCharInfo ci = utf_ptr2StrCharInfo(line);
while (ci.ptr - line < len && *ci.ptr != NUL) {
vcol += charsize_regular(arg, ci.ptr, vcol, ci.chr.value).width;
ci = utfc_next(ci);
}
// Check for inline virtual text after the end of the line.
if (len == MAXCOL && arg->virt_row >= 0) {
(void)charsize_regular(arg, ci.ptr, vcol, ci.chr.value);
vcol += arg->cur_text_width_left + arg->cur_text_width_right;
}
return vcol;
}
/// Like win_linesize_regular, but can be used when CStype is kCharsizeFast.
///
/// @see win_linesize_regular
int linesize_fast(CharsizeArg const *const arg, int vcol, colnr_T const len)
{
win_T *const wp = arg->win;
bool const use_tabstop = arg->use_tabstop;
char *const line = arg->line;
StrCharInfo ci = utf_ptr2StrCharInfo(line);
while (ci.ptr - line < len && *ci.ptr != NUL) {
vcol += charsize_fast_impl(wp, use_tabstop, vcol, ci.chr.value).width;
ci = utfc_next(ci);
}
return vcol;
}
/// Get how many virtual columns inline virtual text should offset the cursor. /// Get how many virtual columns inline virtual text should offset the cursor.
/// ///
/// @param cts should contain information stored by win_lbr_chartabsize()
/// about widths of left and right gravity virtual text
/// @param on_NUL whether this is the end of the line /// @param on_NUL whether this is the end of the line
static int virt_text_cursor_off(chartabsize_T *cts, bool on_NUL) static int virt_text_cursor_off(CharsizeArg *cts, bool on_NUL)
{ {
int off = 0; int off = 0;
if (!on_NUL || !(State & MODE_NORMAL)) { if (!on_NUL || !(State & MODE_NORMAL)) {
off += cts->cts_cur_text_width_left; off += cts->cur_text_width_left;
} }
if (!on_NUL && (State & MODE_NORMAL)) { if (!on_NUL && (State & MODE_NORMAL)) {
off += cts->cts_cur_text_width_right; off += cts->cur_text_width_right;
} }
return off; return off;
} }
@ -482,115 +470,53 @@ static int virt_text_cursor_off(chartabsize_T *cts, bool on_NUL)
/// @param end /// @param end
void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end) void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end)
{ {
char *ptr; // points to current char char *const line = ml_get_buf(wp->w_buffer, pos->lnum); // start of the line
char *posptr; // points to char at pos->col int const end_col = pos->col;
int incr;
int head; CharsizeArg arg;
colnr_T *vts = wp->w_buffer->b_p_vts_array; bool on_NUL = false;
int ts = (int)wp->w_buffer->b_p_ts; CSType const cstype = init_charsize_arg(&arg, wp, pos->lnum, line);
arg.max_head_vcol = -1;
colnr_T vcol = 0; colnr_T vcol = 0;
char *line = ptr = ml_get_buf(wp->w_buffer, pos->lnum); // start of the line CharSize char_size;
StrCharInfo ci = utf_ptr2StrCharInfo(line);
if (pos->col == MAXCOL) { if (cstype == kCharsizeFast) {
// continue until the NUL bool const use_tabstop = arg.use_tabstop;
posptr = NULL;
} else {
// In a few cases the position can be beyond the end of the line.
for (colnr_T i = 0; i < pos->col; i++) {
if (ptr[i] == NUL) {
pos->col = i;
break;
}
}
posptr = ptr + pos->col;
posptr -= utf_head_off(line, posptr);
}
chartabsize_T cts;
bool on_NUL = false;
init_chartabsize_arg(&cts, wp, pos->lnum, 0, line, line);
cts.cts_max_head_vcol = -1;
// This function is used very often, do some speed optimizations.
// When 'list', 'linebreak', 'showbreak' and 'breakindent' are not set
// and there are no virtual text use a simple loop.
// Also use this when 'list' is set but tabs take their normal size.
if ((!wp->w_p_list || (wp->w_p_lcs_chars.tab1 != NUL))
&& !wp->w_p_lbr
&& *get_showbreak_value(wp) == NUL
&& !wp->w_p_bri
&& !cts.cts_has_virt_text) {
while (true) { while (true) {
head = 0; if (*ci.ptr == NUL) {
int c = (uint8_t)(*ptr); // if cursor is at NUL, it is treated like 1 cell char
char_size = (CharSize){ .width = 1 };
// make sure we don't go past the end of the line
if (c == NUL) {
// NUL at end of line only takes one column
incr = 1;
break; break;
} }
char_size = charsize_fast_impl(wp, use_tabstop, vcol, ci.chr.value);
// A tab gets expanded, depending on the current column StrCharInfo const next = utfc_next(ci);
if (c == TAB) { if (next.ptr - line > end_col) {
incr = tabstop_padding(vcol, ts, vts);
} else {
// For utf-8, if the byte is >= 0x80, need to look at
// further bytes to find the cell width.
if (c >= 0x80) {
incr = utf_ptr2cells(ptr);
} else {
incr = byte2cells(c);
}
// If a double-cell char doesn't fit at the end of a line
// it wraps to the next line, it's like this char is three
// cells wide.
if ((incr == 2)
&& wp->w_p_wrap
&& (MB_BYTE2LEN((uint8_t)(*ptr)) > 1)
&& in_win_border(wp, vcol)) {
incr++;
head = 1;
}
}
if ((posptr != NULL) && (ptr >= posptr)) {
// character at pos->col
break; break;
} }
ci = next;
vcol += incr; vcol += char_size.width;
MB_PTR_ADV(ptr);
} }
} else { } else {
while (true) { while (true) {
// A tab gets expanded, depending on the current column char_size = charsize_regular(&arg, ci.ptr, vcol, ci.chr.value);
// Other things also take up space. if (*ci.ptr == NUL) {
head = 0; // if cursor is at NUL, it is treated like 1 cell char unless there is virtual text
incr = win_lbr_chartabsize(&cts, &head); char_size.width = MAX(1, arg.cur_text_width_left + arg.cur_text_width_right);
// make sure we don't go past the end of the line
if (*cts.cts_ptr == NUL) {
// NUL at end of line only takes one column, unless there is virtual text
incr = MAX(1, cts.cts_cur_text_width_left + cts.cts_cur_text_width_right);
on_NUL = true; on_NUL = true;
break; break;
} }
StrCharInfo const next = utfc_next(ci);
if ((posptr != NULL) && (cts.cts_ptr >= posptr)) { if (next.ptr - line > end_col) {
// character at pos->col
break; break;
} }
ci = next;
cts.cts_vcol += incr; vcol += char_size.width;
MB_PTR_ADV(cts.cts_ptr);
} }
vcol = cts.cts_vcol;
ptr = cts.cts_ptr;
} }
clear_chartabsize_arg(&cts);
int head = char_size.head;
int incr = char_size.width;
if (start != NULL) { if (start != NULL) {
*start = vcol + head; *start = vcol + head;
@ -601,7 +527,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en
} }
if (cursor != NULL) { if (cursor != NULL) {
if ((*ptr == TAB) if (ci.chr.value == TAB
&& (State & MODE_NORMAL) && (State & MODE_NORMAL)
&& !wp->w_p_list && !wp->w_p_list
&& !virtual_active() && !virtual_active()
@ -609,7 +535,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en
// cursor at end // cursor at end
*cursor = vcol + incr - 1; *cursor = vcol + incr - 1;
} else { } else {
vcol += virt_text_cursor_off(&cts, on_NUL); vcol += virt_text_cursor_off(&arg, on_NUL);
// cursor at start // cursor at start
*cursor = vcol + head; *cursor = vcol + head;
} }
@ -798,14 +724,18 @@ int plines_win_nofill(win_T *wp, linenr_T lnum, bool limit_winheight)
int plines_win_nofold(win_T *wp, linenr_T lnum) int plines_win_nofold(win_T *wp, linenr_T lnum)
{ {
char *s = ml_get_buf(wp->w_buffer, lnum); char *s = ml_get_buf(wp->w_buffer, lnum);
chartabsize_T cts; CharsizeArg arg;
init_chartabsize_arg(&cts, wp, lnum, 0, s, s); CSType const cstype = init_charsize_arg(&arg, wp, lnum, s);
if (*s == NUL && !cts.cts_has_virt_text) { if (*s == NUL && arg.virt_row < 0) {
return 1; // be quick for an empty line return 1; // be quick for an empty line
} }
win_linetabsize_cts(&cts, (colnr_T)MAXCOL);
clear_chartabsize_arg(&cts); int64_t col;
int64_t col = cts.cts_vcol; if (cstype == kCharsizeFast) {
col = linesize_fast(&arg, 0, MAXCOL);
} else {
col = linesize_regular(&arg, 0, MAXCOL);
}
// If list mode is on, then the '$' at the end of the line may take up one // If list mode is on, then the '$' at the end of the line may take up one
// extra column. // extra column.
@ -844,26 +774,33 @@ int plines_win_col(win_T *wp, linenr_T lnum, long column)
char *line = ml_get_buf(wp->w_buffer, lnum); char *line = ml_get_buf(wp->w_buffer, lnum);
colnr_T col = 0; CharsizeArg cts;
chartabsize_T cts; CSType const cstype = init_charsize_arg(&cts, wp, lnum, line);
init_chartabsize_arg(&cts, wp, lnum, 0, line, line); colnr_T vcol = 0;
while (*cts.cts_ptr != NUL && --column >= 0) { StrCharInfo ci = utf_ptr2StrCharInfo(line);
cts.cts_vcol += win_lbr_chartabsize(&cts, NULL); if (cstype == kCharsizeFast) {
MB_PTR_ADV(cts.cts_ptr); bool const use_tabstop = cts.use_tabstop;
while (*ci.ptr != NUL && --column >= 0) {
vcol += charsize_fast_impl(wp, use_tabstop, vcol, ci.chr.value).width;
ci = utfc_next(ci);
}
} else {
while (*ci.ptr != NUL && --column >= 0) {
vcol += charsize_regular(&cts, ci.ptr, vcol, ci.chr.value).width;
ci = utfc_next(ci);
}
} }
// If *cts.cts_ptr is a TAB, and the TAB is not displayed as ^I, and we're not // If current char is a TAB, and the TAB is not displayed as ^I, and we're not
// in MODE_INSERT state, then col must be adjusted so that it represents the // in MODE_INSERT state, then col must be adjusted so that it represents the
// last screen position of the TAB. This only fixes an error when the TAB // last screen position of the TAB. This only fixes an error when the TAB
// wraps from one screen line to the next (when 'columns' is not a multiple // wraps from one screen line to the next (when 'columns' is not a multiple
// of 'ts') -- webb. // of 'ts') -- webb.
col = cts.cts_vcol; colnr_T col = vcol;
if (*cts.cts_ptr == TAB && (State & MODE_NORMAL) if (ci.chr.value == TAB && (State & MODE_NORMAL) && cts.use_tabstop) {
&& (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { col += win_charsize(cstype, col, ci.ptr, ci.chr.value, &cts).width - 1;
col += win_lbr_chartabsize(&cts, NULL) - 1;
} }
clear_chartabsize_arg(&cts);
// Add column offset for 'number', 'relativenumber', 'foldcolumn', etc. // Add column offset for 'number', 'relativenumber', 'foldcolumn', etc.
int width = wp->w_width_inner - win_col_off(wp); int width = wp->w_width_inner - win_col_off(wp);

View File

@ -4,25 +4,96 @@
#include <stdint.h> // IWYU pragma: keep #include <stdint.h> // IWYU pragma: keep
#include "nvim/marktree_defs.h" #include "nvim/marktree_defs.h"
#include "nvim/mbyte_defs.h"
#include "nvim/pos_defs.h" // IWYU pragma: keep #include "nvim/pos_defs.h" // IWYU pragma: keep
#include "nvim/types_defs.h" #include "nvim/types_defs.h"
/// Argument for lbr_chartabsize(). typedef bool CSType;
enum {
kCharsizeRegular,
kCharsizeFast,
};
/// Argument for char size functions.
typedef struct { typedef struct {
win_T *cts_win; win_T *win;
char *cts_line; ///< start of the line char *line; ///< start of the line
char *cts_ptr; ///< current position in line
int cts_row;
bool cts_has_virt_text; ///< true if if there is inline virtual text bool use_tabstop; ///< use tabstop for tab insted of counting it as ^I
int cts_cur_text_width_left; ///< width of virtual text left of cursor int indent_width; ///< width of showbreak and breakindent on wrapped lines
int cts_cur_text_width_right; ///< width of virtual text right of cursor /// INT_MIN if not yet calculated
MarkTreeIter cts_iter[1];
int cts_vcol; ///< virtual column at current position int virt_row; ///< line number, -1 if no virtual text
int cts_max_head_vcol; ///< see win_lbr_chartabsize() int cur_text_width_left; ///< width of virtual text left of cursor
} chartabsize_T; int cur_text_width_right; ///< width of virtual text right of cursor
int max_head_vcol; ///< see charsize_regular()
MarkTreeIter iter[1];
} CharsizeArg;
typedef struct {
int width;
int head; // size of breakindent etc. before the character (included in width)
} CharSize;
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "plines.h.generated.h" # include "plines.h.generated.h"
#endif #endif
static inline CharSize win_charsize(CSType cstype, int vcol, char *ptr, int32_t chr,
CharsizeArg *arg)
REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE;
/// Get the number of cells taken up on the screen by the given character at vcol.
/// "arg->cur_text_width_left" and "arg->cur_text_width_right" are set
/// to the extra size for inline virtual text.
///
/// When "arg->max_head_vcol" is positive, only count in "head" the size
/// of 'showbreak'/'breakindent' before "arg->max_head_vcol".
/// When "arg->max_head_vcol" is negative, only count in "head" the size
/// of 'showbreak'/'breakindent' before where cursor should be placed.
static inline CharSize win_charsize(CSType cstype, int vcol, char *ptr, int32_t chr,
CharsizeArg *arg)
{
if (cstype == kCharsizeFast) {
return charsize_fast(arg, vcol, chr);
} else {
return charsize_regular(arg, ptr, vcol, chr);
}
}
static inline int linetabsize_str(char *s)
REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE;
/// Return the number of characters the string 's' will take on the screen,
/// taking into account the size of a tab.
///
/// @param s
///
/// @return Number of characters the string will take on the screen.
static inline int linetabsize_str(char *s)
{
return linetabsize_col(0, s);
}
static inline int win_linetabsize(win_T *wp, linenr_T lnum, char *line, colnr_T len)
REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE;
/// Like linetabsize_str(), but for a given window instead of the current one.
///
/// @param wp
/// @param line
/// @param len
///
/// @return Number of characters the string will take on the screen.
static inline int win_linetabsize(win_T *wp, linenr_T lnum, char *line, colnr_T len)
{
CharsizeArg arg;
CSType const cstype = init_charsize_arg(&arg, wp, lnum, line);
if (cstype == kCharsizeFast) {
return linesize_fast(&arg, 0, len);
} else {
return linesize_regular(&arg, 0, len);
}
}

View File

@ -0,0 +1,359 @@
local helpers = require('test.functional.helpers')(after_each)
local api = helpers.api
local Screen = require('test.functional.ui.screen')
local function rand_utf8(count, seed)
math.randomseed(seed)
local symbols = { 'i', 'À', '', '𐀀' }
local s = ''
for _ = 1, count do
s = s .. symbols[math.random(1, #symbols)]
end
return s
end
local width, height = 100, 50
local benchmark_chars = {
{
name = 'ascii',
line = function(count, _)
return ('i'):rep(count)
end,
},
{
name = '2 byte utf-8',
line = function(count, _)
return ('À'):rep(count)
end,
},
{
name = 'random 1-4 byte utf-8',
line = function(count, i)
return rand_utf8(count, 123456 + i)
end,
},
}
local benchmark_lines = {
{
name = 'long line',
lines = function(line)
return { line(width * height - 1, 1) }
end,
},
{
name = 'multiple lines',
lines = function(line)
local lines = {}
for i = 1, height - 1 do
table.insert(lines, line(width, i))
end
table.insert(lines, line(width - 1, height))
return lines
end,
},
{
name = 'multiple wrapped lines',
lines = function(line)
local lines = {}
local count = math.floor(height / 2)
for i = 1, count - 1 do
table.insert(lines, line(width * 2, i))
end
table.insert(lines, line(width * 2 - 1, count))
return lines
end,
},
}
local N = 10000
local function benchmark(lines, expected_value)
local lnum = #lines
local results = helpers.exec_lua(
[==[
local N, lnum = ...
local values = {}
local stats = {} -- time in ns
for i = 1, N do
local tic = vim.uv.hrtime()
local result = vim.fn.screenpos(0, lnum, 999999)
local toc = vim.uv.hrtime()
table.insert(values, result)
table.insert(stats, toc - tic)
end
return { values, stats }
]==],
N,
lnum
)
for _, value in ipairs(results[1]) do
helpers.eq(expected_value, value)
end
local stats = results[2]
table.sort(stats)
local us = 1 / 1000
print(
string.format(
'min, 25%%, median, 75%%, max:\n\t%0.2fus,\t%0.2fus,\t%0.2fus,\t%0.2fus,\t%0.2fus',
stats[1] * us,
stats[1 + math.floor(#stats * 0.25)] * us,
stats[1 + math.floor(#stats * 0.5)] * us,
stats[1 + math.floor(#stats * 0.75)] * us,
stats[#stats] * us
)
)
end
local function benchmarks(benchmark_results)
describe('screenpos() perf', function()
before_each(helpers.clear)
-- no breakindent
for li, lines_type in ipairs(benchmark_lines) do
for ci, chars_type in ipairs(benchmark_chars) do
local name = 'for ' .. lines_type.name .. ', ' .. chars_type.name .. ', nobreakindent'
local lines = lines_type.lines(chars_type.line)
local result = benchmark_results[li][ci]
it(name, function()
local screen = Screen.new(width, height + 1)
screen:attach()
api.nvim_buf_set_lines(0, 0, 1, false, lines)
-- for smaller screen expect (last line always different, first line same as others)
helpers.feed('G$')
screen:expect(result.screen)
benchmark(lines, result.value)
end)
end
end
-- breakindent
for li, lines_type in ipairs(benchmark_lines) do
for ci, chars_type in ipairs(benchmark_chars) do
local name = 'for ' .. lines_type.name .. ', ' .. chars_type.name .. ', breakindent'
local lines = lines_type.lines(chars_type.line)
local result = benchmark_results[li][ci]
it(name, function()
local screen = Screen.new(width, height + 1)
screen:attach()
api.nvim_buf_set_lines(0, 0, 1, false, lines)
helpers.command('set breakindent')
-- for smaller screen expect (last line always different, first line same as others)
helpers.feed('G$')
screen:expect(result.screen)
benchmark(lines, result.value)
end)
end
end
end)
end
local ascii_results = {
screen = [=[
iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii|*49
iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii^i |
|
]=],
value = { col = 100, curscol = 100, endcol = 100, row = 50 },
}
local two_byte_results = {
screen = [=[
ÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀ|*49
ÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀ^À |
|
]=],
value = { col = 100, curscol = 100, endcol = 100, row = 50 },
}
benchmarks({
{
ascii_results,
two_byte_results,
{ -- random
screen = [=[
𐀀ii𐀀ႠÀ𐀀i𐀀𐀀iÀÀÀiÀ𐀀Ⴀ𐀀ႠiiႠ𐀀iii𐀀ÀႠÀႠႠÀiiÀႠÀiႠi𐀀ÀÀ𐀀𐀀Ⴀ𐀀𐀀Ⴀ𐀀iÀႠ𐀀i𐀀ÀÀႠiiÀ𐀀Ⴀ𐀀À𐀀ii𐀀ÀÀ𐀀ÀႠႠ𐀀𐀀𐀀𐀀ii𐀀ႠႠiႠႠ𐀀ႠÀ𐀀ÀÀiiႠ|
𐀀𐀀ÀÀ𐀀𐀀iႠÀ𐀀𐀀i𐀀𐀀𐀀𐀀ႠႠႠiiiiÀÀ𐀀ႠÀႠÀi𐀀i𐀀ႠႠÀiÀႠi𐀀𐀀À𐀀iÀ𐀀ÀÀÀ𐀀𐀀iÀႠ𐀀𐀀Ⴀ𐀀𐀀ÀiႠiiႠႠÀÀÀÀi𐀀ÀÀႠ𐀀Àiii𐀀iႠႠႠi𐀀ႠÀi𐀀iႠ𐀀ႠiႠ|
Ài𐀀ÀiႠÀiႠiႠ𐀀ÀiiiÀiiÀÀႠ𐀀À𐀀ÀႠÀÀÀႠ𐀀iႠႠ𐀀iႠÀ𐀀À𐀀ႠiÀÀÀÀiiÀiÀiÀႠiÀi𐀀iÀÀႠ𐀀𐀀ႠÀ𐀀iႠ𐀀i𐀀i𐀀ÀႠi𐀀iႠÀÀiiႠ𐀀𐀀ႠiÀiÀiiÀ|
iႠ𐀀iiႠÀႠ𐀀iÀÀÀႠÀiÀ𐀀𐀀𐀀ÀႠiႠiÀiႠiႠ𐀀𐀀ႠႠi𐀀ႠႠ𐀀𐀀Ⴀ𐀀ÀiiႠÀႠႠ𐀀iÀiÀÀ𐀀𐀀𐀀ႠႠ𐀀iii𐀀À𐀀Ⴀ𐀀iႠii𐀀i𐀀i𐀀ÀiÀi𐀀Ⴀ𐀀𐀀i𐀀iÀÀiႠ𐀀Ⴀ𐀀ÀÀÀÀ|
𐀀ÀiÀႠÀÀÀႠ𐀀À𐀀𐀀𐀀i𐀀Ài𐀀𐀀iႠ𐀀𐀀ÀႠ𐀀𐀀À𐀀𐀀ႠÀÀႠႠႠiႠÀÀii𐀀Ⴀii𐀀Ⴀi𐀀Ài𐀀Ⴀ𐀀𐀀𐀀𐀀iÀ𐀀ႠÀiÀ𐀀ႠႠi𐀀i𐀀ႠႠÀႠ𐀀ႠÀႠ𐀀ÀႠiiႠ𐀀𐀀Ⴀ𐀀Ⴀ𐀀𐀀À𐀀𐀀Ài|
iÀÀiÀÀiႠÀiiႠiÀiiiÀႠÀ𐀀Ài𐀀iiÀÀÀiႠႠiiႠ𐀀iÀ𐀀𐀀𐀀ႠÀÀ𐀀iiႠ𐀀Ⴀ𐀀ႠÀႠ𐀀Ài𐀀i𐀀ÀiÀႠႠ𐀀ÀႠiiႠÀ𐀀𐀀Ⴀii𐀀𐀀i𐀀𐀀ÀÀiÀ𐀀i𐀀𐀀i𐀀ÀiiiႠÀÀÀ|
ÀÀÀÀiiiÀႠÀႠႠi𐀀ႠÀ𐀀iႠÀ𐀀ႠÀiiÀÀÀÀႠ𐀀𐀀À𐀀iiÀ𐀀𐀀iႠႠ𐀀iiႠႠ𐀀ÀÀÀ𐀀Ⴀ𐀀À𐀀ÀႠi𐀀ႠÀႠÀႠႠiÀ𐀀iÀÀႠ𐀀ႠႠ𐀀𐀀iႠႠႠ𐀀𐀀À𐀀iÀÀi𐀀iႠႠ𐀀𐀀|
ÀÀiiႠÀ𐀀Ài𐀀iiÀ𐀀iÀႠÀÀi𐀀𐀀ႠႠ𐀀iiႠႠiÀ𐀀iႠÀÀiႠ𐀀ÀÀiiႠႠiÀÀ𐀀À𐀀iႠÀÀႠiႠ𐀀iႠ𐀀ÀiiႠÀÀႠÀÀiÀ𐀀𐀀ႠႠÀႠÀÀႠ𐀀À𐀀ႠႠi𐀀À𐀀𐀀ႠႠ𐀀iႠi𐀀Ⴀ|
ÀÀ𐀀𐀀ÀÀiႠႠႠÀiiiႠiiiÀi𐀀𐀀iႠႠiiÀiႠႠႠÀÀiii𐀀ÀiႠÀi𐀀i𐀀𐀀ÀiÀiÀÀiÀ𐀀iÀÀÀiÀႠႠÀiÀ𐀀À𐀀iႠÀႠ𐀀ii𐀀𐀀iႠiÀiႠ𐀀Ⴀ𐀀Ⴀ𐀀𐀀ႠiÀႠ|
Àii𐀀i𐀀iÀÀÀÀÀ𐀀ÀiÀ𐀀𐀀ÀႠiiiiiÀႠ𐀀ÀÀÀႠi𐀀iႠiႠiÀႠႠ𐀀𐀀ii𐀀𐀀iÀÀÀႠiiÀ𐀀iÀiÀ𐀀Ⴀ𐀀Ⴀ𐀀i𐀀iÀႠiÀ𐀀i𐀀ႠiႠÀ𐀀iႠÀႠiႠiÀÀÀÀ𐀀Ⴀ𐀀iႠႠÀ|
𐀀𐀀À𐀀iႠiÀi𐀀i𐀀𐀀𐀀ႠÀႠiÀႠႠ𐀀ÀiÀiႠi𐀀ÀႠi𐀀𐀀ÀÀi𐀀À𐀀ÀiiႠ𐀀iႠÀÀÀiii𐀀ႠiႠ𐀀𐀀𐀀ႠႠiႠ𐀀𐀀iiႠiiiii𐀀𐀀𐀀𐀀𐀀ÀÀÀÀi𐀀𐀀ႠႠ𐀀iÀ𐀀ÀiiÀii|
𐀀𐀀iႠi𐀀Ⴀ𐀀ႠႠiÀÀႠi𐀀ÀiႠႠႠ𐀀ÀႠiႠႠiႠႠÀ𐀀ႠiiiÀ𐀀𐀀𐀀ÀÀiႠi𐀀ႠÀ𐀀ÀႠÀi𐀀ႠiÀႠiiႠႠÀႠiÀÀႠÀÀÀÀ𐀀iÀႠႠiႠႠ𐀀ÀÀÀႠiÀÀiÀႠ𐀀iÀႠiiႠ𐀀|
iÀႠi𐀀ÀႠÀÀÀiÀÀႠ𐀀À𐀀𐀀𐀀ႠiiiႠiiႠ𐀀À𐀀iii𐀀À𐀀ႠႠi𐀀iiႠÀ𐀀𐀀i𐀀ႠÀ𐀀𐀀i𐀀ÀiႠÀÀiÀÀi𐀀𐀀Ⴀ𐀀À𐀀i𐀀iÀႠႠႠ𐀀ÀÀÀႠÀÀႠႠႠႠႠi𐀀𐀀iiႠÀi𐀀𐀀ႠႠ|
iiႠႠႠ𐀀ÀႠ𐀀Àiii𐀀ÀÀii𐀀À𐀀iiႠÀႠiiႠ𐀀Ⴀ𐀀ÀႠ𐀀iÀiႠiiÀÀiÀÀiႠiႠႠiႠ𐀀Ⴀi𐀀𐀀ÀÀÀÀiႠii𐀀ႠÀ𐀀Ⴀ𐀀i𐀀ႠiႠႠ𐀀i𐀀ႠiႠႠ𐀀ÀiႠ𐀀iÀi𐀀𐀀ÀÀ𐀀𐀀i|
iÀÀi𐀀ÀÀÀ𐀀ÀႠ𐀀ÀÀÀ𐀀𐀀𐀀iiႠÀÀႠ𐀀ႠÀ𐀀iiiÀÀiiႠ𐀀𐀀ႠiiႠiÀiႠႠÀ𐀀ႠiႠÀ𐀀i𐀀𐀀𐀀ႠÀ𐀀Ⴀ𐀀ÀÀÀႠႠ𐀀𐀀À𐀀iiႠႠႠႠႠÀ𐀀𐀀iႠႠiiÀiႠÀiÀႠiÀ𐀀À𐀀i|
À𐀀Ài𐀀i𐀀ႠÀÀÀiiiႠ𐀀ႠÀႠÀÀႠ𐀀ÀÀiÀiÀÀႠÀႠiႠiÀ𐀀iႠÀ𐀀𐀀𐀀ÀႠႠ𐀀ႠႠ𐀀ÀႠႠႠii𐀀ÀÀÀÀiÀi𐀀ႠႠÀiႠiႠÀႠ𐀀ÀiႠႠiÀႠi𐀀𐀀𐀀ÀÀiiÀÀ𐀀𐀀ÀႠ|
À𐀀ÀÀÀÀ𐀀ÀÀ𐀀i𐀀ႠÀႠiႠÀÀ𐀀ႠiÀÀiႠÀႠႠÀ𐀀iÀ𐀀i𐀀𐀀ႠÀÀÀႠႠiÀiiiiႠႠi𐀀ÀႠi𐀀i𐀀i𐀀𐀀𐀀ႠÀႠiiiÀi𐀀ÀႠi𐀀iiÀÀႠÀÀႠiiÀ𐀀À𐀀ÀÀ𐀀ႠÀႠÀ𐀀iႠ|
𐀀ÀiႠ𐀀iiÀ𐀀À𐀀𐀀À𐀀ÀÀiiiiiiႠႠiiÀi𐀀iÀ𐀀ÀÀiႠ𐀀ÀႠÀÀÀiÀႠႠ𐀀À𐀀𐀀iÀ𐀀𐀀ii𐀀ႠÀ𐀀ii𐀀𐀀iႠÀ𐀀ÀÀÀiÀii𐀀iႠ𐀀ႠႠ𐀀ÀႠiiiႠႠႠi𐀀ÀÀiÀ𐀀𐀀À|
iÀ𐀀ႠÀ𐀀iÀႠi𐀀ÀiiiÀÀ𐀀ÀႠ𐀀ႠႠႠÀႠ𐀀ႠÀiiÀi𐀀i𐀀𐀀iႠÀႠiႠiiÀÀ𐀀𐀀𐀀Ài𐀀𐀀iiႠiႠÀႠ𐀀iiႠÀi𐀀ႠႠႠi𐀀ÀÀiÀႠ𐀀𐀀ÀiႠÀiiÀÀႠ𐀀ႠiiÀႠႠÀiÀ|
i𐀀𐀀Ài𐀀𐀀ႠÀiÀÀႠႠi𐀀𐀀ÀÀႠÀÀႠႠႠႠ𐀀ÀiႠiiႠႠÀiiiႠႠÀ𐀀Ⴀii𐀀i𐀀ႠÀ𐀀ÀÀႠႠiÀiÀÀiႠÀiiiiÀႠiiÀႠႠႠÀÀi𐀀À𐀀ႠႠ𐀀Ⴀi𐀀ႠႠ𐀀𐀀𐀀iႠÀÀႠ𐀀ႠႠ|
iÀÀႠႠႠiiÀii𐀀𐀀ႠÀiii𐀀ႠÀ𐀀ႠiiÀ𐀀iႠ𐀀ÀႠ𐀀Ⴀ𐀀𐀀Ⴀi𐀀ÀiႠÀႠ𐀀ÀÀi𐀀ႠiÀÀÀÀႠÀႠiႠiႠႠ𐀀𐀀ÀႠÀႠ𐀀𐀀ÀÀ𐀀ႠႠ𐀀𐀀i𐀀Ⴀii𐀀iii𐀀Ⴀi𐀀Ⴀ𐀀Ⴀi𐀀𐀀ÀႠi|
À𐀀𐀀ÀÀiiiÀiiÀႠi𐀀iiiႠiiႠÀÀ𐀀ႠÀ𐀀ႠႠႠ𐀀i𐀀ÀÀiႠiiiႠiiÀÀiႠ𐀀À𐀀i𐀀Ⴀ𐀀ႠÀiႠႠ𐀀ÀႠႠ𐀀𐀀ÀiႠÀÀႠiiႠi𐀀i𐀀ႠÀÀiႠ𐀀Ⴀ𐀀iႠႠ𐀀ÀÀÀiÀÀii|
À𐀀𐀀iႠÀ𐀀ÀÀÀႠႠÀႠ𐀀ÀiႠii𐀀𐀀ÀႠ𐀀iÀÀiiÀiÀÀ𐀀iႠÀiÀ𐀀𐀀À𐀀Ⴀ𐀀𐀀iiႠႠ𐀀ႠÀႠÀiÀiÀ𐀀iႠႠiÀi𐀀Ⴀ𐀀iiiÀႠ𐀀ÀႠiÀiiiႠiiÀႠÀiႠiÀÀiႠÀi|
𐀀𐀀ÀÀi𐀀À𐀀𐀀ÀႠႠႠ𐀀À𐀀𐀀À𐀀ÀiႠႠ𐀀𐀀ႠÀiႠiႠÀ𐀀ÀiႠiiii𐀀iႠiႠÀ𐀀ÀႠ𐀀𐀀iÀႠiÀi𐀀𐀀ႠÀiႠiÀႠႠ𐀀iႠ𐀀ÀႠÀii𐀀ႠÀႠii𐀀Ⴀ𐀀ÀÀi𐀀ÀÀiÀiÀႠ𐀀ÀႠႠ|
𐀀iiii𐀀iႠÀÀႠiÀႠ𐀀ႠiÀiႠi𐀀iႠႠႠÀႠႠÀႠ𐀀iÀႠႠii𐀀iii𐀀À𐀀ႠႠႠÀiÀÀ𐀀iiiiႠႠႠi𐀀𐀀iiႠ𐀀ႠiႠ𐀀i𐀀𐀀ÀÀႠႠ𐀀iႠiႠÀÀÀႠႠÀ𐀀iÀႠႠႠႠÀ𐀀iÀ|
ÀÀÀ𐀀ÀÀiႠÀiiÀÀ𐀀Ⴀ𐀀Àii𐀀ႠႠ𐀀iÀÀ𐀀𐀀Ⴀ𐀀iႠ𐀀iႠÀႠႠ𐀀ÀÀ𐀀𐀀À𐀀ÀႠiႠÀiÀ𐀀iÀi𐀀ႠiႠ𐀀i𐀀iiႠႠiႠÀÀ𐀀Ⴀ𐀀ႠႠ𐀀𐀀𐀀ႠႠiႠႠ𐀀ႠႠႠ𐀀ÀiႠႠi𐀀iÀÀÀ|
ÀÀiiÀÀÀႠÀ𐀀𐀀iÀÀÀiÀi𐀀iႠiႠ𐀀iÀÀÀ𐀀𐀀𐀀iÀiÀÀiÀÀi𐀀i𐀀𐀀ႠÀ𐀀ii𐀀𐀀Ⴀ𐀀À𐀀iÀÀႠ𐀀iÀÀÀႠÀÀ𐀀𐀀iႠ𐀀ႠiÀႠi𐀀𐀀𐀀iႠ𐀀ႠႠႠi𐀀𐀀ÀÀÀ𐀀ÀႠÀiii|
iiႠi𐀀ÀÀiႠ𐀀𐀀ႠႠÀ𐀀𐀀iÀႠiႠÀႠÀiÀiႠÀ𐀀𐀀ႠiÀႠႠႠႠ𐀀iiႠÀÀ𐀀ÀiÀ𐀀Ⴀ𐀀ÀiႠႠ𐀀À𐀀ႠiiႠiႠ𐀀iႠ𐀀ÀÀ𐀀ÀÀiႠÀi𐀀ÀႠii𐀀𐀀𐀀ÀÀ𐀀iႠ𐀀iႠÀႠႠiii𐀀|
iÀiႠÀi𐀀À𐀀𐀀iiÀ𐀀𐀀𐀀Ài𐀀𐀀ႠÀÀ𐀀ii𐀀𐀀i𐀀ii𐀀Ⴀ𐀀𐀀𐀀ႠÀႠ𐀀ÀႠ𐀀iÀÀÀႠÀÀ𐀀𐀀iႠႠiÀ𐀀ÀႠ𐀀iiÀÀiႠႠႠႠ𐀀Ⴀ𐀀Ⴀ𐀀À𐀀iႠႠiႠ𐀀iiii𐀀Ⴀi𐀀ÀiႠ𐀀ÀÀii|
Àii𐀀ÀႠႠ𐀀𐀀i𐀀iiႠ𐀀i𐀀Ⴀ𐀀À𐀀𐀀ႠႠiiÀiiÀi𐀀ii𐀀𐀀iiiÀiiႠiÀ𐀀𐀀ÀÀÀ𐀀ÀႠ𐀀Ài𐀀À𐀀ÀiiÀÀ𐀀Ⴀ𐀀ႠႠiiÀÀႠ𐀀𐀀i𐀀𐀀ႠႠ𐀀𐀀𐀀𐀀ႠiႠ𐀀ႠႠÀ𐀀ႠÀÀÀÀÀ|
iႠႠႠ𐀀ÀႠ𐀀𐀀ÀiÀ𐀀À𐀀iiÀ𐀀𐀀iႠiiႠႠÀiႠÀႠ𐀀ႠÀÀ𐀀iiÀ𐀀𐀀ႠÀiÀ𐀀iႠiႠÀႠiiiႠiiႠႠႠiiÀ𐀀iÀ𐀀iႠႠ𐀀Ⴀi𐀀ႠႠÀiႠ𐀀i𐀀𐀀𐀀iiႠiÀ𐀀ÀႠႠÀÀÀ𐀀i𐀀|
ÀÀÀii𐀀ႠiႠႠiႠ𐀀ႠiႠ𐀀ÀÀi𐀀𐀀ÀÀႠႠiÀÀiÀႠÀ𐀀ႠÀႠ𐀀𐀀𐀀iႠiႠiႠ𐀀À𐀀𐀀ÀႠ𐀀𐀀iiÀႠ𐀀i𐀀𐀀𐀀iiႠÀ𐀀𐀀ÀiႠi𐀀ႠiÀ𐀀iÀႠÀiÀႠႠႠ𐀀ÀႠ𐀀Ⴀ𐀀À𐀀ႠÀiii|
𐀀ÀÀ𐀀𐀀iႠႠ𐀀ႠiႠii𐀀𐀀ႠႠÀÀႠÀႠiÀႠႠ𐀀ÀႠႠÀ𐀀ii𐀀𐀀𐀀ii𐀀𐀀Ⴀii𐀀𐀀ÀÀÀiႠÀiiiiiႠiÀႠႠÀ𐀀𐀀ႠÀႠÀiiiႠ𐀀ÀÀႠÀi𐀀ႠiÀiÀi𐀀ÀႠiႠiiႠ𐀀iÀÀÀ|
iiÀii𐀀ÀÀi𐀀𐀀ႠÀÀႠႠ𐀀ii𐀀ÀႠiႠႠÀ𐀀𐀀ႠႠiႠႠii𐀀iÀiiiႠ𐀀iiႠÀÀÀÀ𐀀ႠÀi𐀀iႠi𐀀ii𐀀Ⴀ𐀀ႠÀiii𐀀𐀀ÀÀÀiiႠÀ𐀀Ⴀ𐀀ႠÀႠႠ𐀀𐀀𐀀𐀀𐀀À𐀀ÀႠႠi𐀀ႠႠ|
À𐀀iiႠႠႠÀÀ𐀀iÀiÀ𐀀ႠႠÀiÀႠÀ𐀀ÀÀÀiႠ𐀀𐀀ႠÀ𐀀ÀႠÀÀ𐀀𐀀𐀀𐀀𐀀ÀÀ𐀀𐀀iÀႠႠiႠiÀiiiႠiÀÀiႠÀ𐀀𐀀Ài𐀀iႠÀ𐀀ႠÀÀÀ𐀀𐀀𐀀ÀႠiiႠ𐀀ÀႠÀÀ𐀀iÀႠÀႠ𐀀À𐀀|
𐀀𐀀iiႠ𐀀ÀÀ𐀀iÀႠiႠÀÀႠÀ𐀀i𐀀𐀀Ⴀ𐀀ႠႠÀႠႠ𐀀ႠႠ𐀀𐀀ÀႠႠiÀÀÀÀÀiႠÀ𐀀ႠÀÀ𐀀iÀi𐀀iႠ𐀀Ⴀ𐀀Ⴀii𐀀iႠႠႠႠႠႠi𐀀iÀÀ𐀀ႠÀiÀႠiÀ𐀀𐀀ii𐀀𐀀𐀀À|
iႠi𐀀ÀႠi𐀀Ⴀ𐀀Àiii𐀀Ⴀii𐀀Ⴀii𐀀𐀀Ⴀ𐀀ႠÀÀii𐀀ႠႠ𐀀i𐀀𐀀ႠiiႠÀÀiႠÀiႠႠÀႠÀÀiÀi𐀀iႠႠ𐀀ႠÀ𐀀iႠÀÀ𐀀i𐀀𐀀ÀiႠႠÀiÀiiiႠႠႠ𐀀À𐀀ÀÀiÀÀႠÀႠ𐀀ÀႠ|
𐀀𐀀𐀀ÀÀ𐀀iiÀi𐀀𐀀iiÀÀ𐀀𐀀𐀀iႠ𐀀À𐀀iႠႠႠÀႠiÀÀiÀႠiiiÀiÀÀႠႠႠÀÀႠႠiÀiႠႠႠႠÀiÀႠiÀ𐀀À𐀀À𐀀𐀀iiiႠ𐀀𐀀𐀀ÀႠ𐀀ÀiÀÀiႠÀÀႠႠÀႠiiႠi𐀀i𐀀|
iÀiiႠiiiiႠÀ𐀀ÀÀÀiÀi𐀀iiiႠ𐀀𐀀ႠÀiႠÀႠiႠÀiႠÀႠiÀႠÀႠÀÀÀÀiÀႠi𐀀ႠiႠi𐀀Ⴀ𐀀À𐀀i𐀀𐀀ႠiiÀႠ𐀀ႠÀႠႠႠii𐀀𐀀iiiiii𐀀À𐀀iÀiiÀႠÀႠiႠi𐀀|
À𐀀i𐀀ႠÀiႠ𐀀ႠÀႠ𐀀𐀀ႠႠiႠiiiႠÀႠÀႠႠÀ𐀀𐀀Ⴀ𐀀𐀀i𐀀ႠÀ𐀀iႠႠiႠiႠ𐀀Ⴀiii𐀀𐀀À𐀀Ⴀ𐀀ႠÀÀႠÀ𐀀iÀႠÀiႠÀÀ𐀀Ⴀii𐀀ႠiiiiႠÀ𐀀Ài𐀀𐀀ႠiႠÀÀ𐀀𐀀ႠiႠႠÀÀ|
𐀀À𐀀𐀀iႠႠ𐀀iÀÀiÀ𐀀ႠÀ𐀀𐀀𐀀𐀀iႠ𐀀À𐀀ႠႠ𐀀Ⴀ𐀀𐀀iႠiႠႠ𐀀ႠႠÀÀႠႠÀÀႠ𐀀𐀀ႠÀÀii𐀀𐀀𐀀ÀÀႠ𐀀i𐀀Ⴀ𐀀iiiÀÀÀႠiÀiÀ𐀀ii𐀀𐀀iႠႠႠii𐀀iiႠႠi𐀀ÀÀ𐀀i|
𐀀ÀÀ𐀀𐀀À𐀀À𐀀𐀀ii𐀀Ⴀ𐀀𐀀ႠႠ𐀀À𐀀𐀀𐀀ႠiႠႠႠ𐀀ႠÀi𐀀𐀀Ⴀ𐀀Ài𐀀ႠÀÀi𐀀À𐀀iႠiႠႠ𐀀iiÀiႠႠÀ𐀀À𐀀iiႠႠႠႠ𐀀ÀÀႠႠႠiÀႠ𐀀i𐀀i𐀀iiÀ𐀀i𐀀ႠiÀÀÀiÀ|
ii𐀀i𐀀ႠiÀiiÀÀÀ𐀀Àii𐀀ႠÀႠi𐀀ႠႠiႠႠi𐀀i𐀀𐀀iႠႠ𐀀𐀀iႠ𐀀iႠႠ𐀀ÀiiႠiႠiii𐀀ÀÀÀi𐀀ႠiÀႠႠႠÀႠႠႠႠႠႠÀiiÀႠi𐀀ÀÀiÀႠ𐀀ÀiႠႠÀ𐀀𐀀iiÀ𐀀𐀀À|
iႠႠiႠiiႠÀÀႠ𐀀iÀÀiÀ𐀀iiႠÀ𐀀i𐀀ႠႠ𐀀iႠႠ𐀀À𐀀𐀀iiႠႠႠ𐀀ႠiႠi𐀀iႠ𐀀ႠႠÀiႠ𐀀𐀀Ⴀ𐀀Ⴀi𐀀iႠႠÀ𐀀À𐀀ÀႠႠ𐀀ÀႠႠi𐀀Ⴀi𐀀iÀႠÀ𐀀À𐀀ႠÀ𐀀ႠÀÀi𐀀Ⴀ𐀀iiÀ|
𐀀iÀႠႠiiiiiiႠi𐀀i𐀀ႠÀ𐀀i𐀀𐀀ႠႠÀႠi𐀀ÀÀÀÀႠ𐀀ႠႠ𐀀i𐀀iiÀ𐀀Ài𐀀𐀀i𐀀i𐀀𐀀ÀႠႠႠii𐀀ÀiiÀiႠiႠ𐀀iiႠႠႠႠ𐀀i𐀀ii𐀀iiÀÀ𐀀𐀀ÀႠ𐀀ÀႠ𐀀iÀ𐀀𐀀|
iႠÀiႠii𐀀𐀀ÀiႠႠiiÀ𐀀ÀÀ𐀀𐀀ႠÀႠ𐀀iႠiiႠiiÀi𐀀ႠႠႠiÀi𐀀𐀀ÀႠÀÀႠi𐀀iÀႠÀႠÀ𐀀𐀀À𐀀𐀀À𐀀ႠiÀÀi𐀀iÀÀ𐀀ÀႠႠႠi𐀀iႠႠi𐀀iiႠႠႠÀiÀ𐀀𐀀Ⴀ𐀀ÀÀ𐀀À|
ÀiႠÀÀႠÀÀÀႠႠÀႠii𐀀i𐀀i𐀀iiႠiÀiÀÀÀႠႠiႠiiÀÀÀႠÀႠÀÀÀႠii𐀀Ⴀ𐀀Ⴀi𐀀ÀႠႠiÀÀႠi𐀀Ⴀ𐀀𐀀ÀႠႠ𐀀iႠႠ𐀀iÀiÀÀႠÀÀ𐀀i𐀀𐀀ÀႠiÀႠႠ𐀀𐀀ÀႠႠiႠÀi|
ÀiႠÀiiiÀႠ𐀀𐀀iႠ𐀀𐀀iÀÀÀႠÀႠiÀiÀi𐀀Ⴀ𐀀À𐀀iiႠ𐀀ÀiÀႠႠ𐀀iiiႠ𐀀Ài𐀀𐀀𐀀𐀀𐀀𐀀𐀀ÀႠÀ𐀀ÀiÀ𐀀ÀÀ𐀀iႠႠ𐀀Ⴀ𐀀i𐀀𐀀iii𐀀𐀀𐀀𐀀ႠႠi𐀀ii𐀀𐀀ႠႠႠ𐀀ÀiႠÀႠ|
À𐀀À𐀀𐀀𐀀À𐀀ÀiÀႠiiႠႠÀႠႠiႠÀÀ𐀀𐀀i𐀀𐀀𐀀ႠiÀႠÀÀ𐀀𐀀𐀀À𐀀ႠႠiÀiÀi𐀀ႠiÀiiႠÀ𐀀ÀiiiႠႠiႠ𐀀ႠiÀ𐀀ÀႠÀÀi𐀀ႠiႠiiႠiÀiiႠÀႠiiÀi𐀀Ⴀ𐀀𐀀iႠi|
ÀÀ𐀀iÀÀÀ𐀀Ⴀ𐀀𐀀ÀႠႠ𐀀Ⴀ𐀀Ⴀ𐀀ႠÀ𐀀i𐀀ÀÀiÀÀ𐀀À𐀀𐀀𐀀iÀiႠiiÀႠÀiႠii𐀀𐀀iÀii𐀀Ⴀ𐀀ႠÀႠiႠiႠ𐀀ÀႠÀ𐀀i𐀀iႠႠ𐀀ႠႠႠÀÀÀii𐀀Ⴀ𐀀𐀀i𐀀i𐀀𐀀iႠi𐀀À𐀀𐀀^ |
|
]=],
value = { col = 100, curscol = 100, endcol = 100, row = 50 },
},
},
{
ascii_results,
two_byte_results,
{ -- random
screen = [=[
𐀀ii𐀀ႠÀ𐀀i𐀀𐀀iÀÀÀiÀ𐀀Ⴀ𐀀ႠiiႠ𐀀iii𐀀ÀႠÀႠႠÀiiÀႠÀiႠi𐀀ÀÀ𐀀𐀀Ⴀ𐀀𐀀Ⴀ𐀀iÀႠ𐀀i𐀀ÀÀႠiiÀ𐀀Ⴀ𐀀À𐀀ii𐀀ÀÀ𐀀ÀႠႠ𐀀𐀀𐀀𐀀ii𐀀ႠႠiႠႠ𐀀ႠÀ𐀀ÀÀiiႠ|
iiႠ𐀀iÀÀiႠႠÀi𐀀ႠႠÀ𐀀𐀀ÀiiiiÀiႠ𐀀iႠÀiႠiႠႠÀiÀ𐀀ႠiiႠႠÀ𐀀Àii𐀀ႠÀႠiႠÀႠÀႠii𐀀Ài𐀀ႠႠ𐀀ÀÀÀi𐀀ÀÀÀ𐀀iႠ𐀀iႠÀ𐀀iႠi𐀀ÀiÀ𐀀ႠႠiÀ𐀀𐀀Ⴀi|
iÀiiiႠÀÀ𐀀ႠႠႠi𐀀À𐀀𐀀iiiÀÀiiÀႠÀ𐀀À𐀀ႠႠ𐀀𐀀ႠႠႠi𐀀iiÀႠ𐀀ႠႠႠÀiႠiႠiÀÀÀi𐀀iႠ𐀀ÀÀiႠ𐀀iÀÀi𐀀i𐀀𐀀ÀiÀႠ𐀀𐀀iႠ𐀀ÀÀiÀÀႠ𐀀𐀀ႠႠ𐀀𐀀𐀀𐀀Ⴀ𐀀𐀀|
ÀiႠiÀ𐀀i𐀀ႠႠiႠႠÀ𐀀ÀÀÀÀ𐀀𐀀ÀႠႠ𐀀ႠÀÀiႠ𐀀i𐀀Ⴀ𐀀ÀႠi𐀀ႠÀႠÀ𐀀ႠႠ𐀀i𐀀iႠÀi𐀀i𐀀𐀀À𐀀iÀiႠႠႠ𐀀ÀiÀႠÀ𐀀ÀÀÀi𐀀𐀀𐀀ႠÀi𐀀𐀀À𐀀À𐀀𐀀iiႠiÀi𐀀i𐀀Ⴀ|
𐀀i𐀀𐀀ÀiÀႠႠႠႠႠÀÀႠႠÀႠ𐀀ii𐀀ÀႠiႠiii𐀀i𐀀i𐀀𐀀𐀀À𐀀ii𐀀iÀiiiÀÀႠiiiႠiiႠÀ𐀀À𐀀𐀀ÀႠ𐀀iÀÀiiÀiÀ𐀀iႠi𐀀𐀀À𐀀ÀiiႠ𐀀iÀ𐀀𐀀iႠႠÀÀႠႠiiÀ|
𐀀ÀiႠႠÀ𐀀𐀀𐀀i𐀀i𐀀i𐀀ႠÀ𐀀ÀiiÀႠ𐀀ÀÀÀi𐀀ႠÀiÀႠi𐀀ႠÀiiÀÀÀiiiÀiႠႠiÀ𐀀ႠႠ𐀀iÀႠÀႠႠiÀÀႠÀႠÀÀii𐀀Ⴀi𐀀iiÀÀÀiႠ𐀀i𐀀𐀀i𐀀iiÀ𐀀𐀀𐀀ႠÀiႠ𐀀|
i𐀀ÀႠiႠi𐀀ႠiႠ𐀀Ⴀi𐀀ႠÀ𐀀𐀀𐀀ႠÀiiiii𐀀Ⴀ𐀀iiiÀiiÀ𐀀𐀀𐀀À𐀀𐀀Ⴀ𐀀ႠÀ𐀀ႠႠႠiÀÀÀÀii𐀀i𐀀ÀiiႠÀiÀ𐀀iႠႠiÀႠii𐀀i𐀀Ⴀ𐀀𐀀iႠႠÀ𐀀ႠiiiႠႠÀÀ𐀀iÀႠ|
𐀀𐀀𐀀À𐀀À𐀀ÀÀ𐀀𐀀iႠႠÀÀiÀႠ𐀀ÀiÀႠi𐀀ႠÀ𐀀𐀀𐀀𐀀Ⴀ𐀀iႠÀ𐀀iÀ𐀀iÀ𐀀iÀÀႠi𐀀iÀႠi𐀀ႠiiႠÀ𐀀À𐀀ႠႠÀÀi𐀀ႠႠ𐀀iiႠÀiႠ𐀀𐀀𐀀𐀀ႠႠႠÀႠiÀႠiÀÀ𐀀À|
ÀÀÀ𐀀i𐀀iႠÀÀÀႠ𐀀𐀀ÀႠÀÀiii𐀀𐀀iiÀiiႠÀÀႠiÀiÀÀ𐀀i𐀀i𐀀ႠiႠႠiႠÀiiÀႠ𐀀ႠႠÀiÀႠ𐀀𐀀iÀ𐀀Ⴀ𐀀iÀ𐀀ႠÀÀႠÀÀÀ𐀀𐀀i𐀀𐀀À𐀀𐀀ii𐀀À𐀀𐀀ႠÀ𐀀ႠႠႠ𐀀𐀀|
ÀÀÀÀiႠiႠႠႠiႠ𐀀ႠÀÀÀ𐀀ÀÀiႠÀ𐀀ÀiႠÀႠÀႠႠÀÀႠiÀႠႠiiႠÀ𐀀ႠႠÀiႠ𐀀iÀႠ𐀀Ⴀ𐀀Ⴀ𐀀iႠÀႠi𐀀𐀀Ⴀ𐀀iÀ𐀀ÀႠ𐀀ÀÀႠ𐀀Ⴀi𐀀iႠÀ𐀀𐀀𐀀𐀀i𐀀i𐀀𐀀𐀀ÀႠiÀÀ𐀀i|
𐀀𐀀iiÀ𐀀ÀႠ𐀀𐀀𐀀𐀀𐀀Àiii𐀀𐀀𐀀Ⴀ𐀀𐀀i𐀀ÀÀ𐀀iiÀiiiiÀ𐀀iႠiႠiÀႠÀႠÀiႠႠႠႠႠႠႠႠႠÀiiႠiÀႠÀ𐀀iiႠÀႠiႠႠÀiႠ𐀀iႠ𐀀iiႠÀ𐀀𐀀Àii𐀀i𐀀ÀႠÀÀiÀÀ|
𐀀𐀀𐀀i𐀀iÀ𐀀𐀀iÀ𐀀Ài𐀀𐀀ႠႠ𐀀ႠÀi𐀀𐀀ÀÀiiiႠiႠ𐀀iႠÀႠÀ𐀀ႠÀ𐀀ႠiiiiÀiႠÀiiiႠႠÀ𐀀Ⴀ𐀀𐀀𐀀ÀႠiÀႠiiiiႠiႠ𐀀Ⴀ𐀀ÀႠÀii𐀀i𐀀Ⴀ𐀀À𐀀iႠႠ𐀀iႠiiii𐀀|
iႠÀÀႠÀÀ𐀀𐀀𐀀iiiÀ𐀀À𐀀iÀÀi𐀀À𐀀ÀႠÀiÀii𐀀ႠႠii𐀀ႠႠ𐀀𐀀ÀႠ𐀀ÀÀ𐀀ÀÀÀÀi𐀀ÀႠ𐀀À𐀀ÀiiႠ𐀀ÀÀႠiႠ𐀀Ⴀ𐀀ÀÀ𐀀ႠႠ𐀀ႠႠÀ𐀀ႠႠႠ𐀀iiÀႠÀႠiႠi𐀀ii𐀀Ài|
𐀀À𐀀i𐀀iÀ𐀀Ⴀ𐀀𐀀iÀiÀ𐀀iႠi𐀀ႠÀiႠႠiÀ𐀀iႠiÀႠi𐀀𐀀iႠiႠႠ𐀀ÀÀi𐀀iÀႠႠ𐀀ႠÀ𐀀𐀀𐀀ႠႠiÀÀiႠႠႠ𐀀𐀀ႠႠႠႠÀiႠ𐀀Ài𐀀iÀႠii𐀀ÀႠii𐀀Ⴀ𐀀ÀႠÀ𐀀Ⴀi|
𐀀𐀀𐀀ÀÀ𐀀𐀀iÀÀႠiÀiiႠÀiႠ𐀀𐀀ÀÀiÀႠ𐀀ÀÀႠ𐀀À𐀀À𐀀iႠႠÀiႠÀiiiႠÀiÀÀÀ𐀀iႠႠႠÀÀiႠႠႠ𐀀ii𐀀ii𐀀iႠႠii𐀀iႠÀi𐀀𐀀𐀀ii𐀀ÀႠႠiႠÀ𐀀ႠႠ|
𐀀iႠ𐀀ႠႠႠÀÀ𐀀iiiÀÀႠii𐀀i𐀀ÀÀႠ𐀀ႠႠiႠႠႠiႠ𐀀i𐀀À𐀀ႠႠႠi𐀀Ài𐀀ႠÀ𐀀ÀႠiiiiႠiiႠႠi𐀀𐀀ÀႠ𐀀ႠiÀÀ𐀀iiiႠÀiႠi𐀀À𐀀𐀀i𐀀𐀀iiႠ𐀀À𐀀iiႠႠÀ𐀀iႠ|
ÀÀ𐀀iÀႠ𐀀iႠႠÀÀ𐀀ÀiႠ𐀀iÀႠႠႠ𐀀𐀀À𐀀ii𐀀𐀀À𐀀𐀀ÀႠႠႠÀႠiiႠ𐀀𐀀ii𐀀ႠႠÀႠiႠi𐀀ႠႠ𐀀i𐀀𐀀𐀀𐀀Ⴀ𐀀iÀiႠႠ𐀀À𐀀ÀႠႠႠÀÀ𐀀𐀀iÀႠi𐀀𐀀ÀiႠႠÀÀiÀÀ|
𐀀ÀiÀ𐀀ႠႠ𐀀i𐀀ႠႠ𐀀𐀀𐀀𐀀𐀀𐀀iႠÀႠႠiiÀႠ𐀀𐀀i𐀀ႠႠ𐀀ႠႠႠ𐀀𐀀iÀiÀႠ𐀀À𐀀À𐀀𐀀ႠiiiႠiiiႠiႠ𐀀ÀiiiႠ𐀀À𐀀ÀႠႠÀ𐀀À𐀀𐀀𐀀ႠiႠi𐀀ႠÀÀႠiiႠႠႠႠ𐀀ÀႠ𐀀𐀀|
𐀀iÀÀႠ𐀀ႠႠÀႠÀ𐀀ႠႠÀiiÀÀÀႠ𐀀i𐀀Ⴀ𐀀ႠÀ𐀀𐀀iႠႠႠႠiiႠႠႠÀiႠÀiiႠÀ𐀀Ⴀ𐀀Ⴀi𐀀𐀀ႠÀ𐀀ÀႠႠ𐀀ÀႠÀႠ𐀀Ⴀ𐀀iႠi𐀀ÀႠႠii𐀀ÀႠÀ𐀀Ⴀ𐀀Ài𐀀À𐀀ÀÀႠiႠႠii𐀀|
ÀÀii𐀀Ài𐀀Ⴀi𐀀i𐀀i𐀀Ⴀ𐀀ÀÀÀiÀÀi𐀀ÀÀÀ𐀀i𐀀iÀ𐀀𐀀ႠiÀi𐀀𐀀𐀀ႠÀiÀ𐀀𐀀iÀÀႠႠ𐀀ႠႠႠႠÀÀÀÀiiÀ𐀀iiႠႠႠi𐀀ႠÀi𐀀ÀႠႠÀ𐀀ႠiiႠ𐀀𐀀i𐀀Ⴀi𐀀𐀀À|
ÀiiÀi𐀀ႠÀÀiÀႠi𐀀ÀႠႠ𐀀ႠiÀiiႠiiႠႠ𐀀ii𐀀i𐀀𐀀𐀀𐀀i𐀀ႠႠÀႠÀÀiÀÀÀႠÀႠႠÀÀႠ𐀀ÀÀ𐀀ÀÀႠÀ𐀀𐀀ÀiႠ𐀀ႠႠ𐀀iÀ𐀀Ⴀ𐀀iÀႠႠ𐀀iÀiiii𐀀ii𐀀𐀀ႠႠႠ𐀀Ⴀ|
𐀀ÀiÀႠÀiiÀÀiiႠႠႠi𐀀ÀÀiႠ𐀀iႠႠÀ𐀀𐀀𐀀ÀႠႠÀ𐀀ႠÀÀႠ𐀀ÀÀ𐀀𐀀Ⴀ𐀀ÀÀ𐀀ÀႠ𐀀ႠÀiÀ𐀀iႠ𐀀ႠႠ𐀀𐀀À𐀀iii𐀀iiႠÀႠiႠÀႠ𐀀Ⴀ𐀀i𐀀𐀀ÀႠႠi𐀀𐀀ႠႠ𐀀𐀀𐀀𐀀À𐀀Ⴀ𐀀|
𐀀iiÀႠiႠiÀႠ𐀀i𐀀iii𐀀Ⴀ𐀀i𐀀iÀÀi𐀀Ⴀii𐀀ÀiÀiiiÀႠÀ𐀀ÀÀႠ𐀀Ⴀ𐀀iiÀi𐀀i𐀀𐀀i𐀀ႠiiiÀႠႠႠiiÀ𐀀À𐀀𐀀iÀ𐀀iႠÀႠÀÀi𐀀ႠiÀႠÀ𐀀𐀀iÀÀ𐀀i𐀀𐀀ÀÀႠ𐀀|
ÀiÀႠႠႠႠii𐀀ÀÀ𐀀𐀀𐀀Ⴀi𐀀À𐀀ÀႠiiÀi𐀀Ⴀii𐀀iÀÀ𐀀ႠiႠ𐀀ႠiiiႠÀÀiÀÀÀÀ𐀀ႠႠii𐀀À𐀀ÀiÀi𐀀ÀÀi𐀀iႠiÀi𐀀ÀiÀi𐀀ÀiÀႠ𐀀i𐀀Ⴀi𐀀𐀀𐀀ႠႠ𐀀ႠÀႠÀႠi|
i𐀀ႠiÀႠႠÀÀ𐀀𐀀ii𐀀ÀÀ𐀀iÀiÀႠÀiiii𐀀ÀiÀႠi𐀀i𐀀𐀀i𐀀𐀀iႠ𐀀iÀi𐀀ÀÀÀÀiႠiÀႠÀÀႠiiÀÀႠႠi𐀀iႠiiႠi𐀀Ⴀ𐀀𐀀ÀႠႠÀႠiႠႠÀ𐀀iiÀႠႠႠ𐀀𐀀Ⴀi𐀀Ⴀi|
ii𐀀iÀÀÀÀÀÀiÀ𐀀À𐀀iiႠiႠႠi𐀀À𐀀ÀႠÀ𐀀ႠႠ𐀀𐀀𐀀iႠႠiiႠÀÀႠÀiiႠÀႠႠÀ𐀀𐀀Ⴀ𐀀ÀÀÀÀႠ𐀀𐀀𐀀ႠႠÀႠ𐀀ÀiႠiÀႠiÀÀ𐀀ii𐀀iiiÀႠÀႠႠ𐀀ႠÀiÀÀ𐀀ႠႠႠÀ|
𐀀𐀀À𐀀𐀀iÀႠ𐀀ႠiႠÀÀ𐀀iÀÀ𐀀À𐀀iÀÀႠႠÀiii𐀀À𐀀ÀႠÀႠႠÀႠႠi𐀀ÀÀÀi𐀀À𐀀ႠiÀi𐀀i𐀀i𐀀ÀiÀÀiÀÀ𐀀𐀀À𐀀ႠÀ𐀀ႠÀႠ𐀀ႠiÀ𐀀𐀀ÀiÀÀ𐀀𐀀𐀀À𐀀Ⴀi𐀀i𐀀i𐀀Ài|
𐀀𐀀iႠ𐀀i𐀀ÀႠႠÀ𐀀iÀ𐀀ÀiႠႠi𐀀iiႠÀ𐀀ÀiiÀႠ𐀀Ⴀ𐀀ÀÀiÀiႠi𐀀À𐀀𐀀iÀiÀiႠi𐀀ႠႠႠi𐀀À𐀀ÀႠႠ𐀀Ⴀ𐀀ÀÀႠiÀiÀ𐀀Ⴀ𐀀ÀႠiႠႠÀÀÀi𐀀i𐀀Ⴀi𐀀À𐀀ii𐀀ႠÀ𐀀Ⴀ|
iႠ𐀀iÀႠႠ𐀀i𐀀À𐀀iÀÀ𐀀𐀀ÀႠႠÀႠÀ𐀀iiiÀ𐀀i𐀀iÀ𐀀ႠႠ𐀀iÀႠ𐀀ႠÀi𐀀iiii𐀀iႠႠ𐀀ÀiÀ𐀀Àii𐀀Ⴀ𐀀𐀀ႠiÀii𐀀𐀀Ⴀ𐀀𐀀ႠiႠ𐀀iႠiႠi𐀀iiiႠႠႠi𐀀iiÀi𐀀Ⴀ|
i𐀀i𐀀ÀÀÀ𐀀ÀiÀႠiÀiiႠÀÀÀiÀiiii𐀀i𐀀ÀÀiiiႠÀiÀႠÀiႠ𐀀iiႠiႠႠiÀi𐀀ႠႠ𐀀ÀႠiႠ𐀀ႠÀiiႠÀ𐀀ÀႠႠ𐀀ႠiÀi𐀀À𐀀𐀀iiÀ𐀀𐀀ÀiႠႠiႠ𐀀ÀႠiÀÀႠ𐀀i|
Ài𐀀𐀀𐀀iÀi𐀀Ài𐀀À𐀀ႠႠ𐀀ႠÀiiÀႠ𐀀i𐀀i𐀀𐀀ႠiႠÀ𐀀𐀀Ⴀ𐀀iÀ𐀀ÀÀႠiႠႠiÀ𐀀iÀ𐀀ႠiÀÀÀÀႠiiÀ𐀀𐀀ÀႠႠiÀ𐀀iiÀ𐀀À𐀀iÀiÀÀ𐀀iÀiÀÀiiÀ𐀀ÀႠႠÀiiÀÀႠ|
𐀀ÀiႠႠÀ𐀀ÀiiÀ𐀀iÀႠႠႠႠiÀÀi𐀀iÀi𐀀iiiႠ𐀀iႠ𐀀𐀀𐀀𐀀ÀÀÀႠi𐀀iႠi𐀀ႠÀႠႠ𐀀𐀀À𐀀iiÀႠ𐀀𐀀ႠႠ𐀀𐀀ÀiႠÀÀÀႠ𐀀𐀀ÀiႠ𐀀𐀀iÀÀiÀ𐀀ႠÀi𐀀𐀀ႠႠႠႠ𐀀Ⴀi|
iÀi𐀀ႠႠÀ𐀀𐀀i𐀀Àii𐀀ÀiÀÀiÀiÀÀ𐀀ÀÀ𐀀À𐀀ႠႠႠÀÀÀႠii𐀀ႠÀÀႠႠi𐀀ႠႠiႠႠ𐀀Ⴀ𐀀ÀiÀiiii𐀀ÀiႠÀiiiiႠႠiiႠÀÀÀႠÀႠ𐀀𐀀𐀀iiႠႠ𐀀ႠÀႠ𐀀iႠႠ𐀀𐀀Ⴀ|
À𐀀À𐀀iÀ𐀀ÀႠÀႠႠiiႠii𐀀ႠÀÀÀ𐀀iႠiiiiiÀ𐀀ÀÀiÀÀႠ𐀀𐀀iiႠi𐀀𐀀Ài𐀀ႠÀÀiႠႠႠႠ𐀀iႠiႠႠႠႠႠÀÀႠiiÀiႠ𐀀iÀiiႠiiii𐀀ii𐀀À𐀀ႠÀ𐀀Ⴀ𐀀ÀႠ|
ÀÀiiiiႠiiႠiÀi𐀀𐀀ႠÀÀ𐀀Ài𐀀Ài𐀀ÀiႠÀ𐀀ႠႠႠႠ𐀀ႠiÀ𐀀iႠႠÀႠi𐀀Ài𐀀ႠiiႠ𐀀Ⴀii𐀀ÀÀÀႠ𐀀ÀÀÀႠÀiÀႠiႠiႠi𐀀𐀀À𐀀𐀀𐀀ÀႠiႠႠႠႠÀiiÀႠ𐀀À𐀀𐀀À|
iÀ𐀀ÀႠႠÀiÀi𐀀Ài𐀀ÀiႠႠÀ𐀀iႠÀ𐀀i𐀀ÀiiÀÀÀႠÀÀႠ𐀀À𐀀À𐀀ÀÀÀÀႠi𐀀iႠÀ𐀀𐀀ÀႠiÀiႠႠiÀ𐀀ႠႠÀ𐀀𐀀Ⴀ𐀀ÀÀ𐀀ႠႠÀÀiÀi𐀀𐀀𐀀À𐀀ÀႠ𐀀iႠႠ𐀀𐀀𐀀i𐀀ႠÀ𐀀Ⴀ|
i𐀀ÀÀ𐀀ႠÀiÀi𐀀i𐀀ÀႠ𐀀Ⴀ𐀀ÀႠ𐀀ႠÀႠႠႠ𐀀𐀀ÀiiiiႠႠi𐀀ႠÀႠÀ𐀀Ⴀ𐀀i𐀀À𐀀𐀀𐀀Ⴀ𐀀ÀiÀÀႠႠiiႠiÀiႠႠÀiÀÀႠႠÀÀႠÀ𐀀ႠiႠ𐀀𐀀i𐀀i𐀀𐀀ÀႠÀႠႠႠÀÀiiÀ𐀀|
iiÀႠႠiÀႠ𐀀ÀiႠႠÀႠiÀႠႠÀÀi𐀀ÀÀiÀ𐀀𐀀i𐀀i𐀀iiÀÀiႠ𐀀Ⴀ𐀀𐀀𐀀ÀiiႠ𐀀Ài𐀀iiiiÀiႠႠii𐀀Ⴀi𐀀iႠႠ𐀀ÀÀႠ𐀀iÀႠႠႠiÀ𐀀𐀀iÀႠiႠÀ𐀀ÀႠÀiႠ𐀀À|
𐀀iႠႠiiii𐀀𐀀i𐀀Àiiii𐀀À𐀀Ⴀi𐀀iႠ𐀀ႠiÀiÀႠi𐀀𐀀ÀiÀiiÀÀÀ𐀀𐀀i𐀀À𐀀ÀႠÀiiÀႠ𐀀ႠႠ𐀀𐀀Ⴀ𐀀ÀÀiÀ𐀀iႠ𐀀𐀀iÀÀႠi𐀀iႠiÀ𐀀ႠႠ𐀀ÀÀႠiÀ𐀀ÀႠႠÀႠ|
ii𐀀𐀀ႠÀiႠႠÀÀ𐀀ÀÀÀÀÀÀÀႠiႠႠÀÀi𐀀ÀiႠÀ𐀀𐀀i𐀀ႠÀii𐀀Ⴀ𐀀𐀀À𐀀𐀀ÀiÀ𐀀i𐀀𐀀ႠÀiÀÀႠiiႠႠiႠÀiÀႠÀi𐀀iÀ𐀀À𐀀𐀀ႠႠi𐀀ႠÀiÀÀÀႠÀiÀÀႠiႠ𐀀iÀ|
𐀀ÀiÀႠႠႠÀÀႠÀႠ𐀀iiiiÀiÀÀႠ𐀀Ⴀiii𐀀𐀀iiႠiÀ𐀀𐀀i𐀀ÀiiÀႠ𐀀𐀀Ⴀ𐀀Ⴀ𐀀Ⴀii𐀀ႠiႠÀiႠႠÀÀÀႠÀ𐀀Ⴀ𐀀𐀀𐀀À𐀀Ⴀ𐀀Ⴀ𐀀ႠÀ𐀀ႠႠiႠ𐀀𐀀ÀiiiÀ𐀀ÀiÀiႠÀ𐀀À|
i𐀀ႠiႠi𐀀ii𐀀𐀀iiiႠႠÀÀiiii𐀀ÀiႠႠÀi𐀀ÀÀÀÀiÀiiႠ𐀀ÀႠiႠႠiÀႠ𐀀ÀႠႠ𐀀ÀÀÀ𐀀ႠÀ𐀀À𐀀iႠi𐀀iÀÀi𐀀iÀÀiႠႠ𐀀ႠiÀÀiÀ𐀀iႠ𐀀ႠÀႠÀii𐀀𐀀ႠႠi𐀀|
iႠ𐀀À𐀀𐀀Ài𐀀ÀႠ𐀀Ⴀ𐀀𐀀ႠÀ𐀀ႠႠႠiÀ𐀀ÀiႠႠႠÀႠÀႠiÀႠi𐀀ÀÀÀႠÀiÀႠÀÀÀiii𐀀𐀀ÀiiႠÀi𐀀iÀ𐀀À𐀀ÀiiÀÀÀiÀiÀÀi𐀀iiiiÀ𐀀ÀႠႠiiႠi𐀀iiႠ𐀀À𐀀𐀀|
ÀÀiႠ𐀀iႠiÀÀႠႠi𐀀ႠÀÀiႠ𐀀ႠႠÀÀÀii𐀀𐀀iiႠ𐀀iႠ𐀀iႠႠ𐀀Ài𐀀iiÀÀႠႠ𐀀Ⴀ𐀀𐀀𐀀i𐀀ÀÀi𐀀𐀀ႠiÀi𐀀iÀiiႠႠÀႠႠiႠÀiႠႠႠÀÀÀ𐀀ႠÀ𐀀ႠÀႠႠiÀÀႠ𐀀|
Àii𐀀i𐀀iႠÀÀႠႠÀii𐀀ႠႠiÀiÀiႠÀiႠ𐀀Ⴀi𐀀𐀀𐀀À𐀀𐀀𐀀𐀀𐀀iႠ𐀀ႠႠi𐀀i𐀀iႠ𐀀ႠႠÀႠ𐀀iႠႠႠiႠႠ𐀀ႠႠႠႠ𐀀ႠÀÀႠႠႠႠÀ𐀀ႠÀႠÀiiÀiÀiÀႠi𐀀𐀀𐀀𐀀À𐀀𐀀𐀀À|
iÀiႠi𐀀𐀀ÀiႠiႠÀ𐀀iÀii𐀀ႠÀ𐀀𐀀ႠÀiÀÀ𐀀ႠÀÀႠ𐀀iÀiႠ𐀀𐀀Ⴀ𐀀ႠႠႠÀ𐀀iÀႠiႠÀÀ𐀀ႠÀႠႠႠႠÀ𐀀𐀀À𐀀Ⴀ𐀀À𐀀iႠi𐀀i𐀀Ⴀi𐀀Ⴀ𐀀𐀀iiiႠiႠ𐀀À𐀀ႠÀ𐀀𐀀iÀÀi|
i𐀀ÀÀiÀi𐀀iႠÀ𐀀𐀀ႠiႠÀႠÀ𐀀iÀÀÀႠ𐀀𐀀iÀÀiႠ𐀀ii𐀀i𐀀𐀀iiiႠႠႠÀÀiႠÀ𐀀i𐀀ÀiႠÀÀႠႠi𐀀ႠÀ𐀀À𐀀iiႠႠ𐀀𐀀iÀႠÀi𐀀À𐀀𐀀iÀ𐀀ii𐀀𐀀À𐀀iÀ𐀀𐀀iÀi𐀀|
iႠÀiiiႠ𐀀ႠiÀႠႠႠႠ𐀀À𐀀ႠႠ𐀀ႠႠi𐀀𐀀𐀀ႠÀÀ𐀀𐀀Ⴀ𐀀iÀ𐀀ႠiႠÀ𐀀ႠႠiÀi𐀀ÀiÀႠႠÀ𐀀𐀀iiÀႠ𐀀ႠႠi𐀀𐀀ÀႠÀiႠႠiÀÀ𐀀iÀÀÀ𐀀𐀀ÀႠii𐀀ÀiiÀႠÀႠÀi𐀀𐀀iႠ|
iÀ𐀀ႠÀi𐀀iႠႠii𐀀ႠÀ𐀀Ài𐀀𐀀iႠ𐀀iÀi𐀀À𐀀iÀÀiÀ𐀀ÀÀiiiÀႠႠi𐀀ႠiiiႠi𐀀iÀÀ𐀀𐀀ႠႠႠÀiiႠÀႠÀႠiႠi𐀀ႠÀÀ𐀀Ⴀ𐀀i𐀀ႠÀÀ𐀀iÀ𐀀Ⴀ𐀀iÀ𐀀Ⴀ𐀀ႠÀႠÀÀ𐀀|
À𐀀𐀀ÀiÀiႠiႠႠi𐀀𐀀𐀀ÀႠႠi𐀀À𐀀i𐀀𐀀𐀀𐀀iiÀÀÀÀ𐀀Ⴀ𐀀ii𐀀i𐀀iÀi𐀀ႠႠ𐀀iÀ𐀀𐀀𐀀ႠႠႠ𐀀𐀀𐀀𐀀i𐀀𐀀ႠiႠ𐀀i𐀀iႠi𐀀i𐀀ÀႠ𐀀iႠႠႠ𐀀À𐀀𐀀iiႠi𐀀ÀÀÀiii^𐀀 |
|
]=],
value = { col = 100, curscol = 100, endcol = 100, row = 50 },
},
},
{
ascii_results,
two_byte_results,
{ -- random
screen = [=[
𐀀ii𐀀ႠÀ𐀀i𐀀𐀀iÀÀÀiÀ𐀀Ⴀ𐀀ႠiiႠ𐀀iii𐀀ÀႠÀႠႠÀiiÀႠÀiႠi𐀀ÀÀ𐀀𐀀Ⴀ𐀀𐀀Ⴀ𐀀iÀႠ𐀀i𐀀ÀÀႠiiÀ𐀀Ⴀ𐀀À𐀀ii𐀀ÀÀ𐀀ÀႠႠ𐀀𐀀𐀀𐀀ii𐀀ႠႠiႠႠ𐀀ႠÀ𐀀ÀÀiiႠ|
𐀀𐀀ÀÀ𐀀𐀀iႠÀ𐀀𐀀i𐀀𐀀𐀀𐀀ႠႠႠiiiiÀÀ𐀀ႠÀႠÀi𐀀i𐀀ႠႠÀiÀႠi𐀀𐀀À𐀀iÀ𐀀ÀÀÀ𐀀𐀀iÀႠ𐀀𐀀Ⴀ𐀀𐀀ÀiႠiiႠႠÀÀÀÀi𐀀ÀÀႠ𐀀Àiii𐀀iႠႠႠi𐀀ႠÀi𐀀iႠ𐀀ႠiႠ|
iiႠ𐀀iÀÀiႠႠÀi𐀀ႠႠÀ𐀀𐀀ÀiiiiÀiႠ𐀀iႠÀiႠiႠႠÀiÀ𐀀ႠiiႠႠÀ𐀀Àii𐀀ႠÀႠiႠÀႠÀႠii𐀀Ài𐀀ႠႠ𐀀ÀÀÀi𐀀ÀÀÀ𐀀iႠ𐀀iႠÀ𐀀iႠi𐀀ÀiÀ𐀀ႠႠiÀ𐀀𐀀Ⴀi|
À𐀀𐀀iÀiÀÀÀÀႠႠႠ𐀀iÀÀiႠ𐀀À𐀀ႠÀiiႠ𐀀iiႠႠ𐀀iÀiႠႠÀႠÀ𐀀Ài𐀀iႠ𐀀𐀀iiႠÀႠiÀÀÀiÀiiÀ𐀀i𐀀ÀÀႠ𐀀𐀀𐀀i𐀀𐀀ႠႠi𐀀À𐀀iႠi𐀀ႠႠiiiÀႠ𐀀ႠÀiÀiႠႠ|
iÀiiiႠÀÀ𐀀ႠႠႠi𐀀À𐀀𐀀iiiÀÀiiÀႠÀ𐀀À𐀀ႠႠ𐀀𐀀ႠႠႠi𐀀iiÀႠ𐀀ႠႠႠÀiႠiႠiÀÀÀi𐀀iႠ𐀀ÀÀiႠ𐀀iÀÀi𐀀i𐀀𐀀ÀiÀႠ𐀀𐀀iႠ𐀀ÀÀiÀÀႠ𐀀𐀀ႠႠ𐀀𐀀𐀀𐀀Ⴀ𐀀𐀀|
𐀀ÀÀÀ𐀀ÀÀiÀÀÀႠiiႠiiÀႠÀiႠÀiÀiႠႠ𐀀ÀÀÀႠiiÀႠ𐀀iÀi𐀀ႠႠ𐀀𐀀ÀÀ𐀀ÀiÀÀႠi𐀀iÀႠ𐀀À𐀀ႠႠÀ𐀀Ⴀiii𐀀ႠiiႠiÀႠႠiႠÀႠ𐀀ႠÀÀႠ𐀀À𐀀ÀiÀÀႠႠÀÀ|
ÀiႠiÀ𐀀i𐀀ႠႠiႠႠÀ𐀀ÀÀÀÀ𐀀𐀀ÀႠႠ𐀀ႠÀÀiႠ𐀀i𐀀Ⴀ𐀀ÀႠi𐀀ႠÀႠÀ𐀀ႠႠ𐀀i𐀀iႠÀi𐀀i𐀀𐀀À𐀀iÀiႠႠႠ𐀀ÀiÀႠÀ𐀀ÀÀÀi𐀀𐀀𐀀ႠÀi𐀀𐀀À𐀀À𐀀𐀀iiႠiÀi𐀀i𐀀Ⴀ|
𐀀𐀀i𐀀ÀႠႠ𐀀𐀀𐀀iႠႠ𐀀À𐀀ÀႠiÀ𐀀𐀀ႠÀi𐀀𐀀iiiႠ𐀀𐀀iႠÀÀ𐀀ႠiiÀႠႠÀ𐀀𐀀ႠÀႠႠÀiႠႠÀႠÀႠiႠႠ𐀀𐀀𐀀iႠႠႠiႠႠii𐀀ÀႠi𐀀ÀÀႠႠi𐀀À𐀀Ⴀ𐀀ÀÀ𐀀Ⴀ𐀀iႠiiႠႠ|
𐀀i𐀀𐀀ÀiÀႠႠႠႠႠÀÀႠႠÀႠ𐀀ii𐀀ÀႠiႠiii𐀀i𐀀i𐀀𐀀𐀀À𐀀ii𐀀iÀiiiÀÀႠiiiႠiiႠÀ𐀀À𐀀𐀀ÀႠ𐀀iÀÀiiÀiÀ𐀀iႠi𐀀𐀀À𐀀ÀiiႠ𐀀iÀ𐀀𐀀iႠႠÀÀႠႠiiÀ|
i𐀀𐀀𐀀ÀÀi𐀀ႠႠႠႠႠÀiiÀ𐀀𐀀ii𐀀Ⴀ𐀀Ài𐀀iႠiÀÀႠÀ𐀀ÀႠiႠÀi𐀀𐀀iiႠ𐀀i𐀀ႠÀiႠii𐀀𐀀À𐀀𐀀ႠႠÀႠiÀiႠÀÀi𐀀i𐀀ႠÀiႠႠႠ𐀀𐀀ÀiႠႠႠÀÀi𐀀ÀႠႠÀiႠ𐀀ႠÀ|
𐀀ÀiႠႠÀ𐀀𐀀𐀀i𐀀i𐀀i𐀀ႠÀ𐀀ÀiiÀႠ𐀀ÀÀÀi𐀀ႠÀiÀႠi𐀀ႠÀiiÀÀÀiiiÀiႠႠiÀ𐀀ႠႠ𐀀iÀႠÀႠႠiÀÀႠÀႠÀÀii𐀀Ⴀi𐀀iiÀÀÀiႠ𐀀i𐀀𐀀i𐀀iiÀ𐀀𐀀𐀀ႠÀiႠ𐀀|
À𐀀𐀀ÀiႠႠiÀ𐀀i𐀀ÀႠÀႠiiÀiÀÀiႠ𐀀𐀀𐀀Ⴀ𐀀ÀႠi𐀀𐀀iႠႠႠiiႠÀi𐀀𐀀𐀀iႠÀÀÀႠi𐀀À𐀀iiiႠÀႠiÀ𐀀iႠ𐀀ii𐀀𐀀𐀀ÀႠႠÀÀႠႠႠႠiÀi𐀀Àiii𐀀ii𐀀𐀀À|
i𐀀ÀႠiႠi𐀀ႠiႠ𐀀Ⴀi𐀀ႠÀ𐀀𐀀𐀀ႠÀiiiii𐀀Ⴀ𐀀iiiÀiiÀ𐀀𐀀𐀀À𐀀𐀀Ⴀ𐀀ႠÀ𐀀ႠႠႠiÀÀÀÀii𐀀i𐀀ÀiiႠÀiÀ𐀀iႠႠiÀႠii𐀀i𐀀Ⴀ𐀀𐀀iႠႠÀ𐀀ႠiiiႠႠÀÀ𐀀iÀႠ|
𐀀iii𐀀ÀႠiႠÀ𐀀𐀀i𐀀ÀႠ𐀀𐀀ႠႠÀiႠ𐀀𐀀iႠ𐀀ႠiiႠiiႠÀ𐀀𐀀ႠiÀÀႠÀiÀႠ𐀀ÀႠ𐀀ႠÀi𐀀Ⴀi𐀀𐀀𐀀𐀀𐀀À𐀀𐀀𐀀i𐀀iÀ𐀀À𐀀ÀÀÀ𐀀ႠႠ𐀀iiÀ𐀀ÀÀÀႠÀ𐀀ႠÀႠÀiÀiiÀႠ|
𐀀𐀀𐀀À𐀀À𐀀ÀÀ𐀀𐀀iႠႠÀÀiÀႠ𐀀ÀiÀႠi𐀀ႠÀ𐀀𐀀𐀀𐀀Ⴀ𐀀iႠÀ𐀀iÀ𐀀iÀ𐀀iÀÀႠi𐀀iÀႠi𐀀ႠiiႠÀ𐀀À𐀀ႠႠÀÀi𐀀ႠႠ𐀀iiႠÀiႠ𐀀𐀀𐀀𐀀ႠႠႠÀႠiÀႠiÀÀ𐀀À|
À𐀀iiiÀÀ𐀀ÀႠiႠ𐀀ႠႠႠ𐀀iÀ𐀀ႠiႠ𐀀i𐀀ÀÀiႠ𐀀ÀiiႠႠiÀÀ𐀀ÀiႠiÀ𐀀i𐀀ÀiÀ𐀀ÀႠiÀiႠႠi𐀀iႠÀiÀÀႠႠiÀiႠÀႠi𐀀𐀀ႠiÀႠii𐀀ႠiiႠi𐀀Ⴀi𐀀ÀiÀÀ𐀀|
ÀÀÀ𐀀i𐀀iႠÀÀÀႠ𐀀𐀀ÀႠÀÀiii𐀀𐀀iiÀiiႠÀÀႠiÀiÀÀ𐀀i𐀀i𐀀ႠiႠႠiႠÀiiÀႠ𐀀ႠႠÀiÀႠ𐀀𐀀iÀ𐀀Ⴀ𐀀iÀ𐀀ႠÀÀႠÀÀÀ𐀀𐀀i𐀀𐀀À𐀀𐀀ii𐀀À𐀀𐀀ႠÀ𐀀ႠႠႠ𐀀𐀀|
𐀀i𐀀𐀀i𐀀𐀀Ⴀ𐀀iÀ𐀀ÀiႠiiÀÀiÀÀÀiÀiiႠႠ𐀀iႠiÀi𐀀Ⴀ𐀀ႠÀ𐀀Ⴀ𐀀𐀀𐀀ႠÀႠ𐀀ႠiiႠiiiႠÀÀiÀ𐀀ÀÀ𐀀ÀႠÀ𐀀iiÀÀiÀiÀÀÀႠႠÀÀii𐀀ႠÀÀiႠiÀÀ𐀀iiiÀ|
ÀÀÀÀiႠiႠႠႠiႠ𐀀ႠÀÀÀ𐀀ÀÀiႠÀ𐀀ÀiႠÀႠÀႠႠÀÀႠiÀႠႠiiႠÀ𐀀ႠႠÀiႠ𐀀iÀႠ𐀀Ⴀ𐀀Ⴀ𐀀iႠÀႠi𐀀𐀀Ⴀ𐀀iÀ𐀀ÀႠ𐀀ÀÀႠ𐀀Ⴀi𐀀iႠÀ𐀀𐀀𐀀𐀀i𐀀i𐀀𐀀𐀀ÀႠiÀÀ𐀀i|
i𐀀iÀ𐀀i𐀀iႠႠႠÀÀiiiႠi𐀀iÀÀ𐀀iႠÀÀ𐀀ii𐀀i𐀀𐀀ÀÀÀÀiÀiiÀiiiÀiÀi𐀀𐀀ႠႠÀႠႠÀiÀÀႠiႠႠiÀႠÀiႠႠ𐀀ႠႠႠÀႠÀႠ𐀀iiႠႠႠ𐀀iÀ𐀀iႠ𐀀iÀiÀiÀi|
𐀀𐀀iiÀ𐀀ÀႠ𐀀𐀀𐀀𐀀𐀀Àiii𐀀𐀀𐀀Ⴀ𐀀𐀀i𐀀ÀÀ𐀀iiÀiiiiÀ𐀀iႠiႠiÀႠÀႠÀiႠႠႠႠႠႠႠႠႠÀiiႠiÀႠÀ𐀀iiႠÀႠiႠႠÀiႠ𐀀iႠ𐀀iiႠÀ𐀀𐀀Àii𐀀i𐀀ÀႠÀÀiÀÀ|
𐀀iÀiÀÀÀÀႠi𐀀ႠႠi𐀀ႠႠႠ𐀀ႠႠ𐀀iÀÀÀiÀi𐀀Ài𐀀𐀀𐀀ႠÀiႠiႠ𐀀𐀀Àiii𐀀ÀÀiႠÀiႠ𐀀𐀀i𐀀ÀÀiiÀiႠႠi𐀀À𐀀ÀႠÀႠiiiiii𐀀Ⴀ𐀀ÀႠ𐀀ႠႠႠ𐀀𐀀iႠႠႠႠiÀ|
𐀀𐀀𐀀i𐀀iÀ𐀀𐀀iÀ𐀀Ài𐀀𐀀ႠႠ𐀀ႠÀi𐀀𐀀ÀÀiiiႠiႠ𐀀iႠÀႠÀ𐀀ႠÀ𐀀ႠiiiiÀiႠÀiiiႠႠÀ𐀀Ⴀ𐀀𐀀𐀀ÀႠiÀႠiiiiႠiႠ𐀀Ⴀ𐀀ÀႠÀii𐀀i𐀀Ⴀ𐀀À𐀀iႠႠ𐀀iႠiiii𐀀|
ÀÀÀÀ𐀀À𐀀À𐀀iႠ𐀀𐀀𐀀À𐀀ႠႠ𐀀ÀiÀiÀi𐀀Ⴀ𐀀iiႠiiÀႠÀႠ𐀀ႠÀi𐀀𐀀iÀiÀႠi𐀀À𐀀𐀀ÀႠ𐀀𐀀iႠÀ𐀀Ⴀ𐀀ÀiÀÀႠÀiÀ𐀀ႠႠႠiiÀi𐀀ÀႠiÀÀÀiႠႠiÀÀiÀႠÀႠÀ|
iႠÀÀႠÀÀ𐀀𐀀𐀀iiiÀ𐀀À𐀀iÀÀi𐀀À𐀀ÀႠÀiÀii𐀀ႠႠii𐀀ႠႠ𐀀𐀀ÀႠ𐀀ÀÀ𐀀ÀÀÀÀi𐀀ÀႠ𐀀À𐀀ÀiiႠ𐀀ÀÀႠiႠ𐀀Ⴀ𐀀ÀÀ𐀀ႠႠ𐀀ႠႠÀ𐀀ႠႠႠ𐀀iiÀႠÀႠiႠi𐀀ii𐀀Ài|
ÀiÀႠ𐀀À𐀀𐀀ii𐀀𐀀ႠႠiiiiႠiÀiႠiÀiÀ𐀀ÀiႠÀiiÀÀÀ𐀀𐀀Ⴀ𐀀ႠႠ𐀀iÀiႠ𐀀ii𐀀ႠÀ𐀀ႠiႠÀiiႠÀႠ𐀀Ài𐀀Àii𐀀iiÀiႠÀiႠႠÀiÀ𐀀i𐀀ႠÀ𐀀iÀÀႠii𐀀iÀ𐀀|
𐀀À𐀀i𐀀iÀ𐀀Ⴀ𐀀𐀀iÀiÀ𐀀iႠi𐀀ႠÀiႠႠiÀ𐀀iႠiÀႠi𐀀𐀀iႠiႠႠ𐀀ÀÀi𐀀iÀႠႠ𐀀ႠÀ𐀀𐀀𐀀ႠႠiÀÀiႠႠႠ𐀀𐀀ႠႠႠႠÀiႠ𐀀Ài𐀀iÀႠii𐀀ÀႠii𐀀Ⴀ𐀀ÀႠÀ𐀀Ⴀi|
À𐀀iႠႠiႠi𐀀iႠii𐀀Ⴀi𐀀ii𐀀iႠÀ𐀀ႠÀÀi𐀀iÀ𐀀iÀ𐀀ႠÀႠiႠÀႠi𐀀Ⴀ𐀀ÀႠႠiႠ𐀀iႠiÀ𐀀À𐀀ÀႠi𐀀𐀀iÀi𐀀À𐀀Ⴀ𐀀ႠႠi𐀀𐀀𐀀iႠÀ𐀀ÀႠÀ𐀀i𐀀i𐀀iÀႠÀÀÀ𐀀iႠ𐀀|
𐀀𐀀𐀀ÀÀ𐀀𐀀iÀÀႠiÀiiႠÀiႠ𐀀𐀀ÀÀiÀႠ𐀀ÀÀႠ𐀀À𐀀À𐀀iႠႠÀiႠÀiiiႠÀiÀÀÀ𐀀iႠႠႠÀÀiႠႠႠ𐀀ii𐀀ii𐀀iႠႠii𐀀iႠÀi𐀀𐀀𐀀ii𐀀ÀႠႠiႠÀ𐀀ႠႠ|
𐀀iiႠi𐀀ÀႠ𐀀ÀiႠÀÀiÀÀÀÀႠ𐀀ÀiiႠi𐀀iႠi𐀀ÀႠÀ𐀀𐀀𐀀iÀiÀ𐀀ႠiÀ𐀀ႠႠÀÀi𐀀Ⴀ𐀀i𐀀ႠÀႠiႠiႠiii𐀀ÀႠႠ𐀀𐀀ÀႠ𐀀𐀀ii𐀀ÀiiÀÀႠiÀ𐀀ÀÀÀiÀႠÀ𐀀Ài𐀀Ⴀ|
𐀀iႠ𐀀ႠႠႠÀÀ𐀀iiiÀÀႠii𐀀i𐀀ÀÀႠ𐀀ႠႠiႠႠႠiႠ𐀀i𐀀À𐀀ႠႠႠi𐀀Ài𐀀ႠÀ𐀀ÀႠiiiiႠiiႠႠi𐀀𐀀ÀႠ𐀀ႠiÀÀ𐀀iiiႠÀiႠi𐀀À𐀀𐀀i𐀀𐀀iiႠ𐀀À𐀀iiႠႠÀ𐀀iႠ|
À𐀀Àii𐀀iiႠႠ𐀀iႠ𐀀iiႠii𐀀𐀀𐀀iiႠiÀ𐀀ႠႠÀÀÀiႠႠ𐀀À𐀀ႠႠႠ𐀀ÀiႠ𐀀ÀiႠ𐀀i𐀀ÀÀ𐀀Ⴀ𐀀Ⴀ𐀀iÀ𐀀iiiÀႠiÀႠiÀႠႠÀႠii𐀀ႠႠႠÀii𐀀i𐀀iiႠiÀ𐀀𐀀|
ÀÀ𐀀iÀႠ𐀀iႠႠÀÀ𐀀ÀiႠ𐀀iÀႠႠႠ𐀀𐀀À𐀀ii𐀀𐀀À𐀀𐀀ÀႠႠႠÀႠiiႠ𐀀𐀀ii𐀀ႠႠÀႠiႠi𐀀ႠႠ𐀀i𐀀𐀀𐀀𐀀Ⴀ𐀀iÀiႠႠ𐀀À𐀀ÀႠႠႠÀÀ𐀀𐀀iÀႠi𐀀𐀀ÀiႠႠÀÀiÀÀ|
ÀiႠii𐀀ÀÀႠi𐀀ÀႠi𐀀À𐀀ii𐀀iႠi𐀀ÀႠ𐀀À𐀀ÀÀႠႠ𐀀i𐀀ႠiiႠ𐀀ÀÀ𐀀À𐀀i𐀀𐀀i𐀀Ⴀ𐀀Ⴀi𐀀𐀀𐀀i𐀀Ài𐀀𐀀ÀႠႠi𐀀ÀiÀႠÀiiiÀÀႠႠi𐀀ÀiÀiႠÀÀiÀ𐀀𐀀ÀiiiiÀ|
𐀀ÀiÀ𐀀ႠႠ𐀀i𐀀ႠႠ𐀀𐀀𐀀𐀀𐀀𐀀iႠÀႠႠiiÀႠ𐀀𐀀i𐀀ႠႠ𐀀ႠႠႠ𐀀𐀀iÀiÀႠ𐀀À𐀀À𐀀𐀀ႠiiiႠiiiႠiႠ𐀀ÀiiiႠ𐀀À𐀀ÀႠႠÀ𐀀À𐀀𐀀𐀀ႠiႠi𐀀ႠÀÀႠiiႠႠႠႠ𐀀ÀႠ𐀀𐀀|
𐀀À𐀀iႠ𐀀ႠiiႠi𐀀ÀႠႠ𐀀iÀiÀÀiႠi𐀀iiÀ𐀀ႠႠႠiÀႠ𐀀ႠႠႠÀ𐀀ႠiႠ𐀀𐀀ÀႠÀÀ𐀀ÀႠႠiႠ𐀀𐀀iiÀÀ𐀀À𐀀iႠiÀ𐀀iÀ𐀀𐀀iiiiii𐀀ÀiႠ𐀀𐀀i𐀀Ài𐀀À𐀀𐀀i𐀀Ⴀ|
𐀀iÀÀႠ𐀀ႠႠÀႠÀ𐀀ႠႠÀiiÀÀÀႠ𐀀i𐀀Ⴀ𐀀ႠÀ𐀀𐀀iႠႠႠႠiiႠႠႠÀiႠÀiiႠÀ𐀀Ⴀ𐀀Ⴀi𐀀𐀀ႠÀ𐀀ÀႠႠ𐀀ÀႠÀႠ𐀀Ⴀ𐀀iႠi𐀀ÀႠႠii𐀀ÀႠÀ𐀀Ⴀ𐀀Ài𐀀À𐀀ÀÀႠiႠႠii𐀀|
ÀiႠ𐀀Ⴀ𐀀Ⴀ𐀀ႠiiႠÀ𐀀ÀiiÀi𐀀iÀÀiiiiÀႠÀႠÀႠiiႠ𐀀𐀀ႠÀÀ𐀀𐀀À𐀀ÀႠႠ𐀀𐀀iii𐀀iiÀ𐀀ႠiႠႠÀ𐀀ÀÀii𐀀ႠÀႠi𐀀𐀀Ⴀi𐀀ÀႠႠ𐀀ÀÀÀ𐀀iÀႠႠ𐀀ÀÀi𐀀i|
ÀÀii𐀀Ài𐀀Ⴀi𐀀i𐀀i𐀀Ⴀ𐀀ÀÀÀiÀÀi𐀀ÀÀÀ𐀀i𐀀iÀ𐀀𐀀ႠiÀi𐀀𐀀𐀀ႠÀiÀ𐀀𐀀iÀÀႠႠ𐀀ႠႠႠႠÀÀÀÀiiÀ𐀀iiႠႠႠi𐀀ႠÀi𐀀ÀႠႠÀ𐀀ႠiiႠ𐀀𐀀i𐀀Ⴀi𐀀𐀀À|
𐀀À𐀀𐀀𐀀iÀÀiႠiiÀႠÀiÀႠÀႠႠ𐀀iႠႠÀiႠႠႠႠ𐀀iႠÀÀႠႠiႠ𐀀ÀiႠႠi𐀀𐀀ႠiÀ𐀀ÀiÀi𐀀i𐀀Ⴀi𐀀ႠÀiiÀႠÀi𐀀Ài𐀀ÀÀÀi𐀀𐀀ÀႠi𐀀ႠiႠÀiÀiႠ𐀀ii𐀀𐀀𐀀À|
ÀiiÀi𐀀ႠÀÀiÀႠi𐀀ÀႠႠ𐀀ႠiÀiiႠiiႠႠ𐀀ii𐀀i𐀀𐀀𐀀𐀀i𐀀ႠႠÀႠÀÀiÀÀÀႠÀႠႠÀÀႠ𐀀ÀÀ𐀀ÀÀႠÀ𐀀𐀀ÀiႠ𐀀ႠႠ𐀀iÀ𐀀Ⴀ𐀀iÀႠႠ𐀀iÀiiii𐀀ii𐀀𐀀ႠႠႠ𐀀Ⴀ|
À𐀀ÀÀiႠႠ𐀀iႠႠႠÀႠ𐀀ႠႠ𐀀iႠ𐀀𐀀𐀀𐀀ႠiÀ𐀀ႠႠÀ𐀀Ⴀ𐀀ÀÀii𐀀Ⴀ𐀀ÀÀ𐀀ÀÀႠ𐀀ႠÀiÀ𐀀𐀀À𐀀À𐀀𐀀iႠi𐀀ÀႠႠႠႠ𐀀ႠႠÀÀ𐀀𐀀𐀀ÀÀiiႠÀÀ𐀀ႠႠႠiÀႠÀႠႠiÀႠ𐀀|
𐀀ÀiÀႠÀiiÀÀiiႠႠႠi𐀀ÀÀiႠ𐀀iႠႠÀ𐀀𐀀𐀀ÀႠႠÀ𐀀ႠÀÀႠ𐀀ÀÀ𐀀𐀀Ⴀ𐀀ÀÀ𐀀ÀႠ𐀀ႠÀiÀ𐀀iႠ𐀀ႠႠ𐀀𐀀À𐀀iii𐀀iiႠÀႠiႠÀႠ𐀀Ⴀ𐀀i𐀀𐀀ÀႠႠi𐀀𐀀ႠႠ𐀀𐀀𐀀𐀀À𐀀Ⴀ𐀀|
Àiiiii𐀀iႠÀÀÀiiႠiii𐀀𐀀ÀiÀႠÀÀiႠႠ𐀀iiÀÀႠ𐀀ႠiÀႠ𐀀𐀀ii𐀀iႠÀ𐀀iiႠ𐀀Ⴀ𐀀𐀀i𐀀Ⴀ𐀀i𐀀𐀀𐀀ÀႠiႠiႠi𐀀iiiÀii𐀀𐀀Àii𐀀À𐀀Ⴀ𐀀𐀀Ⴀ𐀀i𐀀ႠÀႠii𐀀Ⴀ|
𐀀iiÀႠiႠiÀႠ𐀀i𐀀iii𐀀Ⴀ𐀀i𐀀iÀÀi𐀀Ⴀii𐀀ÀiÀiiiÀႠÀ𐀀ÀÀႠ𐀀Ⴀ𐀀iiÀi𐀀i𐀀𐀀i𐀀ႠiiiÀႠႠႠiiÀ𐀀À𐀀𐀀iÀ𐀀iႠÀႠÀÀi𐀀ႠiÀႠÀ𐀀𐀀iÀÀ𐀀i𐀀𐀀ÀÀႠ𐀀|
𐀀iႠႠÀ𐀀ÀႠÀ𐀀iႠ𐀀Àii𐀀i𐀀𐀀ÀÀi𐀀𐀀𐀀iiႠÀ𐀀ii𐀀Ⴀ𐀀𐀀iႠi𐀀iÀ𐀀À𐀀ႠႠ𐀀À𐀀i𐀀𐀀iႠiiÀÀႠႠႠiÀÀiÀÀ𐀀𐀀𐀀À𐀀𐀀𐀀ႠÀႠ𐀀iÀ𐀀𐀀Ⴀ𐀀ႠႠii𐀀𐀀ÀÀႠÀi𐀀𐀀i|
ÀiÀႠႠႠႠii𐀀ÀÀ𐀀𐀀𐀀Ⴀi𐀀À𐀀ÀႠiiÀi𐀀Ⴀii𐀀iÀÀ𐀀ႠiႠ𐀀ႠiiiႠÀÀiÀÀÀÀ𐀀ႠႠii𐀀À𐀀ÀiÀi𐀀ÀÀi𐀀iႠiÀi𐀀ÀiÀi𐀀ÀiÀႠ𐀀i𐀀Ⴀi𐀀𐀀𐀀ႠႠ𐀀ႠÀႠÀႠi|
À𐀀𐀀i𐀀Ài𐀀𐀀ႠiႠÀႠiiႠiႠÀ𐀀𐀀ÀiÀ𐀀𐀀ÀÀႠႠႠ𐀀ႠiÀႠႠÀ𐀀Ⴀi𐀀𐀀ÀiÀ𐀀À𐀀iႠi𐀀𐀀ÀÀ𐀀iႠiႠႠ𐀀ÀÀ𐀀𐀀ÀiÀÀ𐀀ÀÀ𐀀i𐀀ÀÀ𐀀𐀀ÀÀႠii𐀀Ⴀ𐀀Ⴀ𐀀iiÀÀÀi𐀀À|
i𐀀ႠiÀႠႠÀÀ𐀀𐀀ii𐀀ÀÀ𐀀iÀiÀႠÀiiii𐀀ÀiÀႠi𐀀i𐀀𐀀i𐀀𐀀iႠ𐀀iÀi𐀀ÀÀÀÀiႠiÀႠÀÀႠiiÀÀႠႠi𐀀iႠiiႠi𐀀Ⴀ𐀀𐀀ÀႠႠÀႠiႠႠÀ𐀀iiÀႠႠႠ𐀀𐀀Ⴀi𐀀Ⴀi|
À𐀀À𐀀iႠÀ𐀀ႠiႠii𐀀ÀႠÀÀ𐀀i𐀀Ⴀi𐀀ႠiÀ𐀀ႠÀÀÀiÀÀÀႠ𐀀𐀀ႠiiႠ𐀀ÀႠiiÀiiႠႠi𐀀ÀiiႠ𐀀iÀႠÀi𐀀À𐀀ÀiÀÀÀi𐀀ÀÀႠႠiÀiႠ𐀀ii𐀀ႠÀiႠÀႠႠi^i |
|
]=],
value = { col = 100, curscol = 100, endcol = 100, row = 50 },
},
},
})