Files
mop3/kernel/Flanterm/src/flanterm.c
kamkow1 f07e920270
All checks were successful
Build documentation / build-and-deploy (push) Successful in 55s
Minimal device system, implement terminal device and libterminal
2026-02-12 15:49:04 +01:00

2502 lines
68 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* SPDX-License-Identifier: BSD-2-Clause */
/* Copyright (C) 2022-2026 Mintsuki and contributors.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef __cplusplus
#error "Please do not compile Flanterm as C++ code! Flanterm should be compiled as C99 or newer."
#endif
#ifndef __STDC_VERSION__
#error "Flanterm must be compiled as C99 or newer."
#endif
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifndef FLANTERM_IN_FLANTERM
#define FLANTERM_IN_FLANTERM
#endif
#include "flanterm.h"
// Tries to implement this standard for terminfo
// https://man7.org/linux/man-pages/man4/console_codes.4.html
static const uint32_t col256[] = {
0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f, 0x005f87,
0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, 0x008787, 0x0087af, 0x0087d7, 0x0087ff,
0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff, 0x00d700, 0x00d75f, 0x00d787,
0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f, 0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff,
0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af, 0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, 0x5f5f87,
0x5f5faf, 0x5f5fd7, 0x5f5fff, 0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff,
0x5faf00, 0x5faf5f, 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787,
0x5fd7af, 0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff,
0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f, 0x875f87,
0x875faf, 0x875fd7, 0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af, 0x8787d7, 0x8787ff,
0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff, 0x87d700, 0x87d75f, 0x87d787,
0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f, 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff,
0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af, 0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87,
0xaf5faf, 0xaf5fd7, 0xaf5fff, 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff,
0xafaf00, 0xafaf5f, 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, 0xafd787,
0xafd7af, 0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff,
0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f, 0xd75f87,
0xd75faf, 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af, 0xd787d7, 0xd787ff,
0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff, 0xd7d700, 0xd7d75f, 0xd7d787,
0xd7d7af, 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f, 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff,
0xff0000, 0xff005f, 0xff0087, 0xff00af, 0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87,
0xff5faf, 0xff5fd7, 0xff5fff, 0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff,
0xffaf00, 0xffaf5f, 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, 0xffd700, 0xffd75f, 0xffd787,
0xffd7af, 0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff,
0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e, 0x585858,
0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e, 0xa8a8a8, 0xb2b2b2,
0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee};
#define CHARSET_DEFAULT 0
#define CHARSET_DEC_SPECIAL 1
void flanterm_context_reinit (struct flanterm_context* ctx) {
ctx->tab_size = 8;
ctx->autoflush = true;
ctx->cursor_enabled = true;
ctx->scroll_enabled = true;
ctx->wrap_enabled = true;
ctx->origin_mode = false;
ctx->control_sequence = false;
ctx->escape = false;
ctx->osc = false;
ctx->osc_escape = false;
ctx->rrr = false;
ctx->discard_next = false;
ctx->bold = false;
ctx->bg_bold = false;
ctx->reverse_video = false;
ctx->dec_private = false;
ctx->insert_mode = false;
ctx->csi_unhandled = false;
ctx->unicode_remaining = 0;
ctx->g_select = 0;
ctx->charsets[0] = CHARSET_DEFAULT;
ctx->charsets[1] = CHARSET_DEC_SPECIAL;
ctx->current_charset = 0;
ctx->escape_offset = 0;
ctx->esc_values_i = 0;
ctx->saved_cursor_x = 0;
ctx->saved_cursor_y = 0;
ctx->current_primary = (size_t)-1;
ctx->current_bg = (size_t)-1;
ctx->saved_state_current_primary = (size_t)-1;
ctx->saved_state_current_bg = (size_t)-1;
ctx->last_printed_char = ' ';
ctx->last_was_graphic = false;
ctx->scroll_top_margin = 0;
ctx->scroll_bottom_margin = ctx->rows;
}
static void flanterm_putchar (struct flanterm_context* ctx, uint8_t c);
void flanterm_write (struct flanterm_context* ctx, const char* buf, size_t count) {
for (size_t i = 0; i < count; i++) {
flanterm_putchar (ctx, buf[i]);
}
if (ctx->autoflush) {
ctx->double_buffer_flush (ctx);
}
}
static void sgr (struct flanterm_context* ctx) {
size_t i = 0;
if (!ctx->esc_values_i)
goto def;
for (; i < ctx->esc_values_i; i++) {
size_t offset;
if (ctx->esc_values[i] == 0) {
def:
if (ctx->reverse_video) {
ctx->reverse_video = false;
ctx->swap_palette (ctx);
}
ctx->bold = false;
ctx->bg_bold = false;
ctx->current_primary = (size_t)-1;
ctx->current_bg = (size_t)-1;
ctx->set_text_bg_default (ctx);
ctx->set_text_fg_default (ctx);
continue;
}
else if (ctx->esc_values[i] == 1) {
ctx->bold = true;
if (ctx->current_primary == (size_t)-2) {
// RGB/256-color; bold does not alter the colour
} else if (ctx->current_primary != (size_t)-1) {
if (!ctx->reverse_video) {
ctx->set_text_fg_bright (ctx, ctx->current_primary);
} else {
ctx->set_text_bg_bright (ctx, ctx->current_primary);
}
} else {
if (!ctx->reverse_video) {
ctx->set_text_fg_default_bright (ctx);
} else {
ctx->set_text_bg_default_bright (ctx);
}
}
continue;
}
else if (ctx->esc_values[i] == 2 || ctx->esc_values[i] == 3 || ctx->esc_values[i] == 4 ||
ctx->esc_values[i] == 8) {
continue;
}
else if (ctx->esc_values[i] == 5) {
ctx->bg_bold = true;
if (ctx->current_bg == (size_t)-2) {
// RGB/256-color; bold does not alter the colour
} else if (ctx->current_bg != (size_t)-1) {
if (!ctx->reverse_video) {
ctx->set_text_bg_bright (ctx, ctx->current_bg);
} else {
ctx->set_text_fg_bright (ctx, ctx->current_bg);
}
} else {
if (!ctx->reverse_video) {
ctx->set_text_bg_default_bright (ctx);
} else {
ctx->set_text_fg_default_bright (ctx);
}
}
continue;
}
else if (ctx->esc_values[i] == 22) {
ctx->bold = false;
if (ctx->current_primary == (size_t)-2) {
// RGB/256-color; unbold does not alter the colour
} else if (ctx->current_primary != (size_t)-1) {
if (!ctx->reverse_video) {
ctx->set_text_fg (ctx, ctx->current_primary);
} else {
ctx->set_text_bg (ctx, ctx->current_primary);
}
} else {
if (!ctx->reverse_video) {
ctx->set_text_fg_default (ctx);
} else {
ctx->set_text_bg_default (ctx);
}
}
continue;
}
else if (ctx->esc_values[i] == 23 || ctx->esc_values[i] == 24 || ctx->esc_values[i] == 28) {
continue;
}
else if (ctx->esc_values[i] == 25) {
ctx->bg_bold = false;
if (ctx->current_bg == (size_t)-2) {
// RGB/256-color; unbold does not alter the colour
} else if (ctx->current_bg != (size_t)-1) {
if (!ctx->reverse_video) {
ctx->set_text_bg (ctx, ctx->current_bg);
} else {
ctx->set_text_fg (ctx, ctx->current_bg);
}
} else {
if (!ctx->reverse_video) {
ctx->set_text_bg_default (ctx);
} else {
ctx->set_text_fg_default (ctx);
}
}
continue;
}
else if (ctx->esc_values[i] >= 30 && ctx->esc_values[i] <= 37) {
offset = 30;
ctx->current_primary = ctx->esc_values[i] - offset;
if (ctx->reverse_video) {
goto set_bg;
}
set_fg:
if ((ctx->bold && !ctx->reverse_video) || (ctx->bg_bold && ctx->reverse_video)) {
ctx->set_text_fg_bright (ctx, ctx->esc_values[i] - offset);
} else {
ctx->set_text_fg (ctx, ctx->esc_values[i] - offset);
}
continue;
}
else if (ctx->esc_values[i] >= 40 && ctx->esc_values[i] <= 47) {
offset = 40;
ctx->current_bg = ctx->esc_values[i] - offset;
if (ctx->reverse_video) {
goto set_fg;
}
set_bg:
if ((ctx->bold && ctx->reverse_video) || (ctx->bg_bold && !ctx->reverse_video)) {
ctx->set_text_bg_bright (ctx, ctx->esc_values[i] - offset);
} else {
ctx->set_text_bg (ctx, ctx->esc_values[i] - offset);
}
continue;
}
else if (ctx->esc_values[i] >= 90 && ctx->esc_values[i] <= 97) {
offset = 90;
ctx->current_primary = ctx->esc_values[i] - offset;
if (ctx->reverse_video) {
goto set_bg_bright;
}
set_fg_bright:
ctx->set_text_fg_bright (ctx, ctx->esc_values[i] - offset);
continue;
}
else if (ctx->esc_values[i] >= 100 && ctx->esc_values[i] <= 107) {
offset = 100;
ctx->current_bg = ctx->esc_values[i] - offset;
if (ctx->reverse_video) {
goto set_fg_bright;
}
set_bg_bright:
ctx->set_text_bg_bright (ctx, ctx->esc_values[i] - offset);
continue;
}
else if (ctx->esc_values[i] == 39) {
ctx->current_primary = (size_t)-1;
if (ctx->reverse_video) {
ctx->swap_palette (ctx);
}
if (!ctx->bold) {
ctx->set_text_fg_default (ctx);
} else {
ctx->set_text_fg_default_bright (ctx);
}
if (ctx->reverse_video) {
ctx->swap_palette (ctx);
}
continue;
}
else if (ctx->esc_values[i] == 49) {
ctx->current_bg = (size_t)-1;
if (ctx->reverse_video) {
ctx->swap_palette (ctx);
}
if (!ctx->bg_bold) {
ctx->set_text_bg_default (ctx);
} else {
ctx->set_text_bg_default_bright (ctx);
}
if (ctx->reverse_video) {
ctx->swap_palette (ctx);
}
continue;
}
else if (ctx->esc_values[i] == 7) {
if (!ctx->reverse_video) {
ctx->reverse_video = true;
ctx->swap_palette (ctx);
}
continue;
}
else if (ctx->esc_values[i] == 27) {
if (ctx->reverse_video) {
ctx->reverse_video = false;
ctx->swap_palette (ctx);
}
continue;
}
// 256/RGB
else if (ctx->esc_values[i] == 38 || ctx->esc_values[i] == 48) {
bool fg = ctx->esc_values[i] == 38;
if (ctx->reverse_video) {
fg = !fg;
}
i++;
if (i >= ctx->esc_values_i) {
break;
}
switch (ctx->esc_values[i]) {
case 2: { // RGB
if (i + 3 >= ctx->esc_values_i) {
goto out;
}
uint32_t rgb_value = 0;
rgb_value |= (ctx->esc_values[i + 1] & 0xff) << 16;
rgb_value |= (ctx->esc_values[i + 2] & 0xff) << 8;
rgb_value |= (ctx->esc_values[i + 3] & 0xff);
i += 3;
if (fg) {
ctx->current_primary = (size_t)-2;
} else {
ctx->current_bg = (size_t)-2;
}
(fg ? ctx->set_text_fg_rgb : ctx->set_text_bg_rgb) (ctx, rgb_value);
break;
}
case 5: { // 256 colors
if (i + 1 >= ctx->esc_values_i) {
goto out;
}
uint32_t col = ctx->esc_values[i + 1];
i++;
if (col < 8) {
if (fg) {
ctx->current_primary = col;
} else {
ctx->current_bg = col;
}
(fg ? ctx->set_text_fg : ctx->set_text_bg) (ctx, col);
} else if (col < 16) {
if (fg) {
ctx->current_primary = col - 8;
} else {
ctx->current_bg = col - 8;
}
(fg ? ctx->set_text_fg_bright : ctx->set_text_bg_bright) (ctx, col - 8);
} else if (col < 256) {
if (fg) {
ctx->current_primary = (size_t)-2;
} else {
ctx->current_bg = (size_t)-2;
}
uint32_t rgb_value = col256[col - 16];
(fg ? ctx->set_text_fg_rgb : ctx->set_text_bg_rgb) (ctx, rgb_value);
}
break;
}
default:
continue;
}
}
}
out:;
}
static void dec_private_parse (struct flanterm_context* ctx, uint8_t c) {
ctx->dec_private = false;
if (ctx->esc_values_i == 0) {
return;
}
bool set;
switch (c) {
case 'h':
set = true;
break;
case 'l':
set = false;
break;
default:
return;
}
for (size_t i = 0; i < ctx->esc_values_i; i++) {
switch (ctx->esc_values[i]) {
case 6:
ctx->origin_mode = set;
ctx->set_cursor_pos (ctx, 0, set ? ctx->scroll_top_margin : 0);
break;
case 7:
ctx->wrap_enabled = set;
break;
case 25:
ctx->cursor_enabled = set;
break;
case 1049:
if (set) {
ctx->clear (ctx, true);
} else {
if (ctx->reverse_video) {
ctx->reverse_video = false;
ctx->swap_palette (ctx);
}
ctx->bold = false;
ctx->bg_bold = false;
ctx->current_primary = (size_t)-1;
ctx->current_bg = (size_t)-1;
ctx->set_text_bg_default (ctx);
ctx->set_text_fg_default (ctx);
ctx->clear (ctx, true);
}
break;
}
}
if (ctx->callback != NULL) {
ctx->callback (ctx, FLANTERM_CB_DEC, ctx->esc_values_i, (uintptr_t)ctx->esc_values, c);
}
}
static void linux_private_parse (struct flanterm_context* ctx) {
if (ctx->esc_values_i == 0) {
return;
}
if (ctx->callback != NULL) {
ctx->callback (ctx, FLANTERM_CB_LINUX, ctx->esc_values_i, (uintptr_t)ctx->esc_values, 0);
}
}
static void mode_toggle (struct flanterm_context* ctx, uint8_t c) {
if (ctx->esc_values_i == 0) {
return;
}
bool set;
switch (c) {
case 'h':
set = true;
break;
case 'l':
set = false;
break;
default:
return;
}
switch (ctx->esc_values[0]) {
case 4:
ctx->insert_mode = set;
return;
}
if (ctx->callback != NULL) {
ctx->callback (ctx, FLANTERM_CB_MODE, ctx->esc_values_i, (uintptr_t)ctx->esc_values, c);
}
}
static void osc_finalize (struct flanterm_context* ctx) {
if (ctx->callback != NULL) {
// Parse the leading OSC number and skip past the semicolon.
uint64_t osc_num = 0;
size_t i = 0;
while (i < ctx->osc_buf_i && ctx->osc_buf[i] >= '0' && ctx->osc_buf[i] <= '9') {
osc_num = osc_num * 10 + (ctx->osc_buf[i] - '0');
i++;
}
if (i < ctx->osc_buf_i && ctx->osc_buf[i] == ';') {
i++;
}
ctx->callback (ctx, FLANTERM_CB_OSC, osc_num, ctx->osc_buf_i - i, (uintptr_t)&ctx->osc_buf[i]);
}
}
static bool osc_parse (struct flanterm_context* ctx, uint8_t c) {
// ESC \ terminates an OSC sequence cleanly
// but if ESC is followed by non-\, report failure from osc_parse and
// try parsing the character as another escape code
if (ctx->osc_escape) {
if (c == '\\') {
osc_finalize (ctx);
ctx->osc = false;
ctx->osc_escape = false;
ctx->escape = false;
return true;
} else {
ctx->osc_escape = false;
ctx->osc = false;
// escape stays true here
return false;
}
}
switch (c) {
case 0x1b:
ctx->osc_escape = true;
break;
// BEL is the other terminator
case '\a':
osc_finalize (ctx);
ctx->osc_escape = false;
ctx->osc = false;
ctx->escape = false;
break;
default:
if (ctx->osc_buf_i < sizeof (ctx->osc_buf)) {
ctx->osc_buf[ctx->osc_buf_i++] = c;
}
break;
}
return true;
}
static void control_sequence_parse (struct flanterm_context* ctx, uint8_t c) {
if (ctx->escape_offset == 2) {
switch (c) {
case '[':
ctx->discard_next = true;
goto cleanup;
case '?':
ctx->dec_private = true;
return;
}
}
// C0 control characters are executed immediately within CSI sequences
// (except ESC which is handled later, and CAN/SUB which are handled by the caller)
if (c < 0x20 && c != 0x1b) {
size_t x, y;
switch (c) {
case '\a':
if (ctx->callback != NULL) {
ctx->callback (ctx, FLANTERM_CB_BELL, 0, 0, 0);
}
break;
case '\b':
ctx->get_cursor_pos (ctx, &x, &y);
if (x > 0) {
ctx->set_cursor_pos (ctx, x - 1, y);
}
break;
case '\t':
ctx->get_cursor_pos (ctx, &x, &y);
x = (x / ctx->tab_size + 1) * ctx->tab_size;
if (x >= ctx->cols) {
x = ctx->cols - 1;
}
ctx->set_cursor_pos (ctx, x, y);
break;
case 0x0b:
case 0x0c:
case '\n':
ctx->get_cursor_pos (ctx, &x, &y);
if (y == ctx->scroll_bottom_margin - 1) {
ctx->scroll (ctx);
ctx->set_cursor_pos (ctx, x, y);
} else {
ctx->set_cursor_pos (ctx, x, y + 1);
}
break;
case '\r':
ctx->get_cursor_pos (ctx, &x, &y);
ctx->set_cursor_pos (ctx, 0, y);
break;
case 14:
ctx->current_charset = 1;
break;
case 15:
ctx->current_charset = 0;
break;
default:
break;
}
return;
}
if (c >= '0' && c <= '9') {
if (ctx->esc_values_i == FLANTERM_MAX_ESC_VALUES) {
return;
}
ctx->rrr = true;
if (ctx->esc_values[ctx->esc_values_i] > UINT32_MAX / 10) {
return;
}
ctx->esc_values[ctx->esc_values_i] *= 10;
ctx->esc_values[ctx->esc_values_i] += c - '0';
return;
}
if (ctx->rrr == true) {
ctx->esc_values_i++;
ctx->rrr = false;
if (c == ';')
return;
} else if (c == ';') {
if (ctx->esc_values_i == FLANTERM_MAX_ESC_VALUES) {
return;
}
ctx->esc_values[ctx->esc_values_i] = 0;
ctx->esc_values_i++;
return;
}
size_t esc_default;
switch (c) {
case 'J':
case 'K':
case 'q':
case 'm':
case 'c':
case ']':
esc_default = 0;
break;
default:
esc_default = 1;
break;
}
for (size_t i = ctx->esc_values_i; i < FLANTERM_MAX_ESC_VALUES; i++) {
ctx->esc_values[i] = esc_default;
}
if (esc_default != 0) {
for (size_t i = 0; i < ctx->esc_values_i; i++) {
if (ctx->esc_values[i] == 0) {
ctx->esc_values[i] = esc_default;
}
}
}
if (ctx->dec_private == true) {
dec_private_parse (ctx, c);
goto cleanup;
}
// CSI sequences are terminated by a byte in [0x40,0x7E]
// so skip all bytes until the terminator byte
if (ctx->csi_unhandled) {
if (c >= 0x40 && c <= 0x7E) {
ctx->csi_unhandled = false;
goto cleanup;
}
return;
}
bool r = ctx->scroll_enabled;
ctx->scroll_enabled = false;
size_t x, y;
ctx->get_cursor_pos (ctx, &x, &y);
switch (c) {
// ESC aborts the current CSI and starts a new escape sequence
case 0x1B:
ctx->scroll_enabled = r;
ctx->control_sequence = false;
ctx->escape_offset = 0;
return;
case 'F':
x = 0;
// FALLTHRU
case 'A': {
if (ctx->esc_values[0] > y)
ctx->esc_values[0] = y;
size_t dest_y = y - ctx->esc_values[0];
size_t min_y = ctx->origin_mode ? ctx->scroll_top_margin : 0;
if (dest_y < min_y) {
dest_y = min_y;
}
ctx->set_cursor_pos (ctx, x, dest_y);
break;
}
case 'E':
x = 0;
// FALLTHRU
case 'e':
case 'B': {
if (y + ctx->esc_values[0] > ctx->rows - 1)
ctx->esc_values[0] = (ctx->rows - 1) - y;
size_t dest_y = y + ctx->esc_values[0];
size_t max_y = ctx->origin_mode ? ctx->scroll_bottom_margin : ctx->rows;
if (dest_y >= max_y) {
dest_y = max_y - 1;
}
ctx->set_cursor_pos (ctx, x, dest_y);
break;
}
case 'a':
case 'C':
if (x + ctx->esc_values[0] > ctx->cols - 1)
ctx->esc_values[0] = (ctx->cols - 1) - x;
ctx->set_cursor_pos (ctx, x + ctx->esc_values[0], y);
break;
case 'D':
if (ctx->esc_values[0] > x)
ctx->esc_values[0] = x;
ctx->set_cursor_pos (ctx, x - ctx->esc_values[0], y);
break;
case 'c':
if (ctx->callback != NULL) {
ctx->callback (ctx, FLANTERM_CB_PRIVATE_ID, 0, 0, 0);
}
break;
case 'd': {
if (ctx->esc_values[0] != 0) {
ctx->esc_values[0]--;
}
size_t max_row = ctx->rows;
size_t row_offset = 0;
if (ctx->origin_mode) {
max_row = ctx->scroll_bottom_margin - ctx->scroll_top_margin;
row_offset = ctx->scroll_top_margin;
}
if (ctx->esc_values[0] >= max_row)
ctx->esc_values[0] = max_row - 1;
ctx->set_cursor_pos (ctx, x, ctx->esc_values[0] + row_offset);
break;
}
case 'G':
case '`':
if (ctx->esc_values[0] != 0) {
ctx->esc_values[0]--;
}
if (ctx->esc_values[0] >= ctx->cols)
ctx->esc_values[0] = ctx->cols - 1;
ctx->set_cursor_pos (ctx, ctx->esc_values[0], y);
break;
case 'H':
case 'f': {
if (ctx->esc_values[0] != 0) {
ctx->esc_values[0]--;
}
if (ctx->esc_values[1] != 0) {
ctx->esc_values[1]--;
}
size_t max_row = ctx->rows;
size_t row_offset = 0;
if (ctx->origin_mode) {
max_row = ctx->scroll_bottom_margin - ctx->scroll_top_margin;
row_offset = ctx->scroll_top_margin;
}
if (ctx->esc_values[1] >= ctx->cols) {
ctx->esc_values[1] = ctx->cols - 1;
}
if (ctx->esc_values[0] >= max_row) {
ctx->esc_values[0] = max_row - 1;
}
ctx->set_cursor_pos (ctx, ctx->esc_values[1], ctx->esc_values[0] + row_offset);
break;
}
case 'M': {
if (y < ctx->scroll_top_margin || y >= ctx->scroll_bottom_margin) {
break;
}
size_t old_scroll_top_margin = ctx->scroll_top_margin;
ctx->scroll_top_margin = y;
size_t max_count = ctx->scroll_bottom_margin - y;
size_t count = ctx->esc_values[0] > max_count ? max_count : ctx->esc_values[0];
for (size_t i = 0; i < count; i++) {
ctx->scroll (ctx);
}
ctx->scroll_top_margin = old_scroll_top_margin;
break;
}
case 'L': {
if (y < ctx->scroll_top_margin || y >= ctx->scroll_bottom_margin) {
break;
}
size_t old_scroll_top_margin = ctx->scroll_top_margin;
ctx->scroll_top_margin = y;
size_t max_count = ctx->scroll_bottom_margin - y;
size_t count = ctx->esc_values[0] > max_count ? max_count : ctx->esc_values[0];
for (size_t i = 0; i < count; i++) {
ctx->revscroll (ctx);
}
ctx->scroll_top_margin = old_scroll_top_margin;
break;
}
case 'n':
switch (ctx->esc_values[0]) {
case 5:
if (ctx->callback != NULL) {
ctx->callback (ctx, FLANTERM_CB_STATUS_REPORT, 0, 0, 0);
}
break;
case 6:
if (ctx->callback != NULL) {
size_t report_y =
ctx->origin_mode && y >= ctx->scroll_top_margin ? y - ctx->scroll_top_margin : y;
ctx->callback (ctx, FLANTERM_CB_POS_REPORT, x + 1, report_y + 1, 0);
}
break;
}
break;
case 'q':
if (ctx->callback != NULL) {
ctx->callback (ctx, FLANTERM_CB_KBD_LEDS, ctx->esc_values[0], 0, 0);
}
break;
case 'J':
switch (ctx->esc_values[0]) {
case 0: {
// Erase from cursor to end: clear rest of current line,
// then clear full lines below, using explicit cursor
// positioning to avoid scroll region wrapping limits.
ctx->set_cursor_pos (ctx, x, y);
for (size_t xc = x; xc < ctx->cols; xc++) {
ctx->raw_putchar (ctx, ' ');
}
for (size_t yc = y + 1; yc < ctx->rows; yc++) {
ctx->set_cursor_pos (ctx, 0, yc);
for (size_t xc = 0; xc < ctx->cols; xc++) {
ctx->raw_putchar (ctx, ' ');
}
}
ctx->set_cursor_pos (ctx, x, y);
break;
}
case 1: {
// Erase from start to cursor: clear full lines above,
// then clear current line up to and including cursor.
for (size_t yc = 0; yc < y; yc++) {
ctx->set_cursor_pos (ctx, 0, yc);
for (size_t xc = 0; xc < ctx->cols; xc++) {
ctx->raw_putchar (ctx, ' ');
}
}
ctx->set_cursor_pos (ctx, 0, y);
for (size_t xc = 0; xc <= x; xc++) {
ctx->raw_putchar (ctx, ' ');
}
ctx->set_cursor_pos (ctx, x, y);
break;
}
case 2:
case 3:
ctx->clear (ctx, false);
break;
}
break;
case '@': {
size_t n = ctx->esc_values[0];
if (n > ctx->cols - x) {
n = ctx->cols - x;
}
for (size_t i = ctx->cols - 1; i >= x + n; i--) {
ctx->move_character (ctx, i, y, i - n, y);
}
ctx->set_cursor_pos (ctx, x, y);
for (size_t i = 0; i < n; i++) {
ctx->raw_putchar (ctx, ' ');
}
ctx->set_cursor_pos (ctx, x, y);
break;
}
case 'P':
if (ctx->esc_values[0] > ctx->cols - x)
ctx->esc_values[0] = ctx->cols - x;
for (size_t i = x + ctx->esc_values[0]; i < ctx->cols; i++)
ctx->move_character (ctx, i - ctx->esc_values[0], y, i, y);
ctx->set_cursor_pos (ctx, ctx->cols - ctx->esc_values[0], y);
// FALLTHRU
case 'X': {
size_t cx, cy;
ctx->get_cursor_pos (ctx, &cx, &cy);
ctx->set_cursor_pos (ctx, cx, cy);
size_t remaining = ctx->cols - cx;
size_t count = ctx->esc_values[0] > remaining ? remaining : ctx->esc_values[0];
for (size_t i = 0; i < count; i++)
ctx->raw_putchar (ctx, ' ');
ctx->set_cursor_pos (ctx, x, y);
break;
}
case 'm':
sgr (ctx);
break;
case 's':
ctx->get_cursor_pos (ctx, &ctx->saved_cursor_x, &ctx->saved_cursor_y);
break;
case 'u':
ctx->set_cursor_pos (ctx, ctx->saved_cursor_x, ctx->saved_cursor_y);
break;
case 'K':
switch (ctx->esc_values[0]) {
case 0: {
ctx->set_cursor_pos (ctx, x, y);
for (size_t i = x; i < ctx->cols; i++)
ctx->raw_putchar (ctx, ' ');
ctx->set_cursor_pos (ctx, x, y);
break;
}
case 1: {
ctx->set_cursor_pos (ctx, 0, y);
for (size_t i = 0; i <= x; i++)
ctx->raw_putchar (ctx, ' ');
ctx->set_cursor_pos (ctx, x, y);
break;
}
case 2: {
ctx->set_cursor_pos (ctx, 0, y);
for (size_t i = 0; i < ctx->cols; i++)
ctx->raw_putchar (ctx, ' ');
ctx->set_cursor_pos (ctx, x, y);
break;
}
}
break;
case 'r':
ctx->scroll_top_margin = 0;
ctx->scroll_bottom_margin = ctx->rows;
if (ctx->esc_values_i > 0) {
ctx->scroll_top_margin = ctx->esc_values[0] - 1;
}
if (ctx->esc_values_i > 1) {
ctx->scroll_bottom_margin = ctx->esc_values[1];
}
if (ctx->scroll_top_margin >= ctx->rows || ctx->scroll_bottom_margin > ctx->rows ||
ctx->scroll_top_margin >= (ctx->scroll_bottom_margin - 1)) {
ctx->scroll_top_margin = 0;
ctx->scroll_bottom_margin = ctx->rows;
}
ctx->set_cursor_pos (ctx, 0, ctx->origin_mode ? ctx->scroll_top_margin : 0);
break;
case 'l':
case 'h':
mode_toggle (ctx, c);
break;
case 'S': {
size_t region = ctx->scroll_bottom_margin - ctx->scroll_top_margin;
size_t count = ctx->esc_values[0] > region ? region : ctx->esc_values[0];
for (size_t i = 0; i < count; i++) {
ctx->scroll (ctx);
}
break;
}
case 'T': {
size_t region = ctx->scroll_bottom_margin - ctx->scroll_top_margin;
size_t count = ctx->esc_values[0] > region ? region : ctx->esc_values[0];
for (size_t i = 0; i < count; i++) {
ctx->revscroll (ctx);
}
break;
}
case 'b': {
if (!ctx->last_was_graphic) {
break;
}
ctx->scroll_enabled = r;
size_t count = ctx->esc_values[0] > 65535 ? 65535 : ctx->esc_values[0];
for (size_t i = 0; i < count; i++) {
if (ctx->insert_mode == true) {
size_t ix, iy;
ctx->get_cursor_pos (ctx, &ix, &iy);
for (size_t j = ctx->cols - 1; j > ix; j--) {
ctx->move_character (ctx, j, iy, j - 1, iy);
}
}
ctx->raw_putchar (ctx, ctx->last_printed_char);
}
break;
}
case ']':
linux_private_parse (ctx);
break;
default:
if (c >= 0x40 && c <= 0x7E) {
break;
}
ctx->scroll_enabled = r;
ctx->csi_unhandled = true;
return;
}
ctx->scroll_enabled = r;
cleanup:
ctx->control_sequence = false;
ctx->escape = false;
}
static void restore_state (struct flanterm_context* ctx) {
ctx->bold = ctx->saved_state_bold;
ctx->bg_bold = ctx->saved_state_bg_bold;
ctx->reverse_video = ctx->saved_state_reverse_video;
ctx->origin_mode = ctx->saved_state_origin_mode;
ctx->wrap_enabled = ctx->saved_state_wrap_enabled;
ctx->current_charset = ctx->saved_state_current_charset;
ctx->charsets[0] = ctx->saved_state_charsets[0];
ctx->charsets[1] = ctx->saved_state_charsets[1];
ctx->current_primary = ctx->saved_state_current_primary;
ctx->current_bg = ctx->saved_state_current_bg;
ctx->restore_state (ctx);
}
static void save_state (struct flanterm_context* ctx) {
ctx->save_state (ctx);
ctx->saved_state_bold = ctx->bold;
ctx->saved_state_bg_bold = ctx->bg_bold;
ctx->saved_state_reverse_video = ctx->reverse_video;
ctx->saved_state_origin_mode = ctx->origin_mode;
ctx->saved_state_wrap_enabled = ctx->wrap_enabled;
ctx->saved_state_current_charset = ctx->current_charset;
ctx->saved_state_charsets[0] = ctx->charsets[0];
ctx->saved_state_charsets[1] = ctx->charsets[1];
ctx->saved_state_current_primary = ctx->current_primary;
ctx->saved_state_current_bg = ctx->current_bg;
}
static void escape_parse (struct flanterm_context* ctx, uint8_t c) {
ctx->escape_offset++;
if (ctx->osc == true) {
// ESC \ is one of the two possible terminators of OSC sequences,
// so osc_parse consumes ESC.
// If it is then followed by \ it cleans correctly,
// otherwise it returns false, and it tries parsing it as another escape sequence
if (osc_parse (ctx, c)) {
return;
}
// OSC aborted by ESC + non-backslash; reset offset for new sequence
ctx->escape_offset = 1;
}
if (ctx->control_sequence == true) {
control_sequence_parse (ctx, c);
return;
}
size_t x, y;
ctx->get_cursor_pos (ctx, &x, &y);
switch (c) {
case 0x1b:
ctx->escape_offset = 0;
return;
case ']':
ctx->osc_escape = false;
ctx->osc = true;
ctx->osc_buf_i = 0;
return;
case '[':
for (size_t i = 0; i < FLANTERM_MAX_ESC_VALUES; i++)
ctx->esc_values[i] = 0;
ctx->esc_values_i = 0;
ctx->rrr = false;
ctx->csi_unhandled = false;
ctx->control_sequence = true;
return;
case '7':
save_state (ctx);
break;
case '8':
restore_state (ctx);
break;
case 'c':
if (ctx->reverse_video) {
ctx->swap_palette (ctx);
}
flanterm_context_reinit (ctx);
ctx->set_text_bg_default (ctx);
ctx->set_text_fg_default (ctx);
ctx->clear (ctx, true);
break;
case 'D':
if (y == ctx->scroll_bottom_margin - 1) {
ctx->scroll (ctx);
ctx->set_cursor_pos (ctx, x, y);
} else if (y < ctx->rows - 1) {
ctx->set_cursor_pos (ctx, x, y + 1);
}
break;
case 'E':
if (y == ctx->scroll_bottom_margin - 1) {
ctx->scroll (ctx);
ctx->set_cursor_pos (ctx, 0, y);
} else if (y < ctx->rows - 1) {
ctx->set_cursor_pos (ctx, 0, y + 1);
} else {
ctx->set_cursor_pos (ctx, 0, y);
}
break;
case 'M':
// "Reverse linefeed"
if (y == ctx->scroll_top_margin) {
ctx->revscroll (ctx);
ctx->set_cursor_pos (ctx, x, y);
} else if (y > 0) {
ctx->set_cursor_pos (ctx, x, y - 1);
}
break;
case 'Z':
if (ctx->callback != NULL) {
ctx->callback (ctx, FLANTERM_CB_PRIVATE_ID, 0, 0, 0);
}
break;
case '(':
case ')':
ctx->g_select = c - '\'';
break;
}
ctx->escape = false;
}
static bool dec_special_print (struct flanterm_context* ctx, uint8_t c) {
#define FLANTERM_DEC_SPCL_PRN(C) \
ctx->last_printed_char = (C); \
ctx->last_was_graphic = true; \
ctx->raw_putchar (ctx, (C)); \
return true;
switch (c) {
case '`':
FLANTERM_DEC_SPCL_PRN (0x04)
case '0':
FLANTERM_DEC_SPCL_PRN (0xdb)
case '-':
FLANTERM_DEC_SPCL_PRN (0x18)
case ',':
FLANTERM_DEC_SPCL_PRN (0x1b)
case '.':
FLANTERM_DEC_SPCL_PRN (0x19)
case 'a':
FLANTERM_DEC_SPCL_PRN (0xb1)
case 'f':
FLANTERM_DEC_SPCL_PRN (0xf8)
case 'g':
FLANTERM_DEC_SPCL_PRN (0xf1)
case 'h':
FLANTERM_DEC_SPCL_PRN (0xb0)
case 'j':
FLANTERM_DEC_SPCL_PRN (0xd9)
case 'k':
FLANTERM_DEC_SPCL_PRN (0xbf)
case 'l':
FLANTERM_DEC_SPCL_PRN (0xda)
case 'm':
FLANTERM_DEC_SPCL_PRN (0xc0)
case 'n':
FLANTERM_DEC_SPCL_PRN (0xc5)
case 'q':
FLANTERM_DEC_SPCL_PRN (0xc4)
case 's':
FLANTERM_DEC_SPCL_PRN (0x5f)
case 't':
FLANTERM_DEC_SPCL_PRN (0xc3)
case 'u':
FLANTERM_DEC_SPCL_PRN (0xb4)
case 'v':
FLANTERM_DEC_SPCL_PRN (0xc1)
case 'w':
FLANTERM_DEC_SPCL_PRN (0xc2)
case 'x':
FLANTERM_DEC_SPCL_PRN (0xb3)
case 'y':
FLANTERM_DEC_SPCL_PRN (0xf3)
case 'z':
FLANTERM_DEC_SPCL_PRN (0xf2)
case '~':
FLANTERM_DEC_SPCL_PRN (0xfa)
case '_':
FLANTERM_DEC_SPCL_PRN (0xff)
case '+':
FLANTERM_DEC_SPCL_PRN (0x1a)
case '{':
FLANTERM_DEC_SPCL_PRN (0xe3)
case '}':
FLANTERM_DEC_SPCL_PRN (0x9c)
}
#undef FLANTERM_DEC_SPCL_PRN
return false;
}
// Following wcwidth related code inherited from:
// https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
struct interval {
uint32_t first;
uint32_t last;
};
/* auxiliary function for binary search in interval table */
static int bisearch (uint32_t ucs, const struct interval* table, int max) {
int min = 0;
int mid;
if (ucs < table[0].first || ucs > table[max].last)
return 0;
while (max >= min) {
mid = (min + max) / 2;
if (ucs > table[mid].last)
min = mid + 1;
else if (ucs < table[mid].first)
max = mid - 1;
else
return 1;
}
return 0;
}
static int mk_wcwidth (uint32_t ucs) {
/* sorted list of non-overlapping intervals of zero-width characters */
/* Unicode 17.0.0 */
static const struct interval combining[] = {
{0x0300, 0x036F}, {0x0483, 0x0489}, {0x0591, 0x05BD}, {0x05BF, 0x05BF},
{0x05C1, 0x05C2}, {0x05C4, 0x05C5}, {0x05C7, 0x05C7}, {0x0610, 0x061A},
{0x061C, 0x061C}, {0x064B, 0x065F}, {0x0670, 0x0670}, {0x06D6, 0x06DC},
{0x06DF, 0x06E4}, {0x06E7, 0x06E8}, {0x06EA, 0x06ED}, {0x0711, 0x0711},
{0x0730, 0x074A}, {0x07A6, 0x07B0}, {0x07EB, 0x07F3}, {0x07FD, 0x07FD},
{0x0816, 0x0819}, {0x081B, 0x0823}, {0x0825, 0x0827}, {0x0829, 0x082D},
{0x0859, 0x085B}, {0x0897, 0x089F}, {0x08CA, 0x08E1}, {0x08E3, 0x0902},
{0x093A, 0x093A}, {0x093C, 0x093C}, {0x0941, 0x0948}, {0x094D, 0x094D},
{0x0951, 0x0957}, {0x0962, 0x0963}, {0x0981, 0x0981}, {0x09BC, 0x09BC},
{0x09C1, 0x09C4}, {0x09CD, 0x09CD}, {0x09E2, 0x09E3}, {0x09FE, 0x09FE},
{0x0A01, 0x0A02}, {0x0A3C, 0x0A3C}, {0x0A41, 0x0A42}, {0x0A47, 0x0A48},
{0x0A4B, 0x0A4D}, {0x0A51, 0x0A51}, {0x0A70, 0x0A71}, {0x0A75, 0x0A75},
{0x0A81, 0x0A82}, {0x0ABC, 0x0ABC}, {0x0AC1, 0x0AC5}, {0x0AC7, 0x0AC8},
{0x0ACD, 0x0ACD}, {0x0AE2, 0x0AE3}, {0x0AFA, 0x0AFF}, {0x0B01, 0x0B01},
{0x0B3C, 0x0B3C}, {0x0B3F, 0x0B3F}, {0x0B41, 0x0B44}, {0x0B4D, 0x0B4D},
{0x0B55, 0x0B56}, {0x0B62, 0x0B63}, {0x0B82, 0x0B82}, {0x0BC0, 0x0BC0},
{0x0BCD, 0x0BCD}, {0x0C00, 0x0C00}, {0x0C04, 0x0C04}, {0x0C3C, 0x0C3C},
{0x0C3E, 0x0C40}, {0x0C46, 0x0C48}, {0x0C4A, 0x0C4D}, {0x0C55, 0x0C56},
{0x0C62, 0x0C63}, {0x0C81, 0x0C81}, {0x0CBC, 0x0CBC}, {0x0CBF, 0x0CBF},
{0x0CC6, 0x0CC6}, {0x0CCC, 0x0CCD}, {0x0CE2, 0x0CE3}, {0x0D00, 0x0D01},
{0x0D3B, 0x0D3C}, {0x0D41, 0x0D44}, {0x0D4D, 0x0D4D}, {0x0D62, 0x0D63},
{0x0D81, 0x0D81}, {0x0DCA, 0x0DCA}, {0x0DD2, 0x0DD4}, {0x0DD6, 0x0DD6},
{0x0E31, 0x0E31}, {0x0E34, 0x0E3A}, {0x0E47, 0x0E4E}, {0x0EB1, 0x0EB1},
{0x0EB4, 0x0EBC}, {0x0EC8, 0x0ECE}, {0x0F18, 0x0F19}, {0x0F35, 0x0F35},
{0x0F37, 0x0F37}, {0x0F39, 0x0F39}, {0x0F71, 0x0F7E}, {0x0F80, 0x0F84},
{0x0F86, 0x0F87}, {0x0F8D, 0x0F97}, {0x0F99, 0x0FBC}, {0x0FC6, 0x0FC6},
{0x102D, 0x1030}, {0x1032, 0x1037}, {0x1039, 0x103A}, {0x103D, 0x103E},
{0x1058, 0x1059}, {0x105E, 0x1060}, {0x1071, 0x1074}, {0x1082, 0x1082},
{0x1085, 0x1086}, {0x108D, 0x108D}, {0x109D, 0x109D}, {0x1160, 0x11FF},
{0x135D, 0x135F}, {0x1712, 0x1714}, {0x1732, 0x1733}, {0x1752, 0x1753},
{0x1772, 0x1773}, {0x17B4, 0x17B5}, {0x17B7, 0x17BD}, {0x17C6, 0x17C6},
{0x17C9, 0x17D3}, {0x17DD, 0x17DD}, {0x180B, 0x180F}, {0x1885, 0x1886},
{0x18A9, 0x18A9}, {0x1920, 0x1922}, {0x1927, 0x1928}, {0x1932, 0x1932},
{0x1939, 0x193B}, {0x1A17, 0x1A18}, {0x1A1B, 0x1A1B}, {0x1A56, 0x1A56},
{0x1A58, 0x1A5E}, {0x1A60, 0x1A60}, {0x1A62, 0x1A62}, {0x1A65, 0x1A6C},
{0x1A73, 0x1A7C}, {0x1A7F, 0x1A7F}, {0x1AB0, 0x1ADD}, {0x1AE0, 0x1AEB},
{0x1B00, 0x1B03}, {0x1B34, 0x1B34}, {0x1B36, 0x1B3A}, {0x1B3C, 0x1B3C},
{0x1B42, 0x1B42}, {0x1B6B, 0x1B73}, {0x1B80, 0x1B81}, {0x1BA2, 0x1BA5},
{0x1BA8, 0x1BA9}, {0x1BAB, 0x1BAD}, {0x1BE6, 0x1BE6}, {0x1BE8, 0x1BE9},
{0x1BED, 0x1BED}, {0x1BEF, 0x1BF1}, {0x1C2C, 0x1C33}, {0x1C36, 0x1C37},
{0x1CD0, 0x1CD2}, {0x1CD4, 0x1CE0}, {0x1CE2, 0x1CE8}, {0x1CED, 0x1CED},
{0x1CF4, 0x1CF4}, {0x1CF8, 0x1CF9}, {0x1DC0, 0x1DFF}, {0x200B, 0x200F},
{0x2028, 0x202E}, {0x2060, 0x206F}, {0x20D0, 0x20F0}, {0x2CEF, 0x2CF1},
{0x2D7F, 0x2D7F}, {0x2DE0, 0x2DFF}, {0x302A, 0x302D}, {0x3099, 0x309A},
{0x3164, 0x3164}, {0xA66F, 0xA672}, {0xA674, 0xA67D}, {0xA69E, 0xA69F},
{0xA6F0, 0xA6F1}, {0xA802, 0xA802}, {0xA806, 0xA806}, {0xA80B, 0xA80B},
{0xA825, 0xA826}, {0xA82C, 0xA82C}, {0xA8C4, 0xA8C5}, {0xA8E0, 0xA8F1},
{0xA8FF, 0xA8FF}, {0xA926, 0xA92D}, {0xA947, 0xA951}, {0xA980, 0xA982},
{0xA9B3, 0xA9B3}, {0xA9B6, 0xA9B9}, {0xA9BC, 0xA9BD}, {0xA9E5, 0xA9E5},
{0xAA29, 0xAA2E}, {0xAA31, 0xAA32}, {0xAA35, 0xAA36}, {0xAA43, 0xAA43},
{0xAA4C, 0xAA4C}, {0xAA7C, 0xAA7C}, {0xAAB0, 0xAAB0}, {0xAAB2, 0xAAB4},
{0xAAB7, 0xAAB8}, {0xAABE, 0xAABF}, {0xAAC1, 0xAAC1}, {0xAAEC, 0xAAED},
{0xAAF6, 0xAAF6}, {0xABE5, 0xABE5}, {0xABE8, 0xABE8}, {0xABED, 0xABED},
{0xD7B0, 0xD7FF}, {0xFB1E, 0xFB1E}, {0xFE00, 0xFE0F}, {0xFE20, 0xFE2F},
{0xFEFF, 0xFEFF}, {0xFFA0, 0xFFA0}, {0xFFF0, 0xFFFB}, {0x101FD, 0x101FD},
{0x102E0, 0x102E0}, {0x10376, 0x1037A}, {0x10A01, 0x10A03}, {0x10A05, 0x10A06},
{0x10A0C, 0x10A0F}, {0x10A38, 0x10A3A}, {0x10A3F, 0x10A3F}, {0x10AE5, 0x10AE6},
{0x10D24, 0x10D27}, {0x10D69, 0x10D6D}, {0x10EAB, 0x10EAC}, {0x10EFA, 0x10EFF},
{0x10F46, 0x10F50}, {0x10F82, 0x10F85}, {0x11001, 0x11001}, {0x11038, 0x11046},
{0x11070, 0x11070}, {0x11073, 0x11074}, {0x1107F, 0x11081}, {0x110B3, 0x110B6},
{0x110B9, 0x110BA}, {0x110C2, 0x110C2}, {0x11100, 0x11102}, {0x11127, 0x1112B},
{0x1112D, 0x11134}, {0x11173, 0x11173}, {0x11180, 0x11181}, {0x111B6, 0x111BE},
{0x111C9, 0x111CC}, {0x111CF, 0x111CF}, {0x1122F, 0x11231}, {0x11234, 0x11234},
{0x11236, 0x11237}, {0x1123E, 0x1123E}, {0x11241, 0x11241}, {0x112DF, 0x112DF},
{0x112E3, 0x112EA}, {0x11300, 0x11301}, {0x1133B, 0x1133C}, {0x11340, 0x11340},
{0x11366, 0x1136C}, {0x11370, 0x11374}, {0x113BB, 0x113C0}, {0x113CE, 0x113CE},
{0x113D0, 0x113D0}, {0x113D2, 0x113D2}, {0x113E1, 0x113E2}, {0x11438, 0x1143F},
{0x11442, 0x11444}, {0x11446, 0x11446}, {0x1145E, 0x1145E}, {0x114B3, 0x114B8},
{0x114BA, 0x114BA}, {0x114BF, 0x114C0}, {0x114C2, 0x114C3}, {0x115B2, 0x115B5},
{0x115BC, 0x115BD}, {0x115BF, 0x115C0}, {0x115DC, 0x115DD}, {0x11633, 0x1163A},
{0x1163D, 0x1163D}, {0x1163F, 0x11640}, {0x116AB, 0x116AB}, {0x116AD, 0x116AD},
{0x116B0, 0x116B5}, {0x116B7, 0x116B7}, {0x1171D, 0x1171D}, {0x1171F, 0x1171F},
{0x11722, 0x11725}, {0x11727, 0x1172B}, {0x1182F, 0x11837}, {0x11839, 0x1183A},
{0x1193B, 0x1193C}, {0x1193E, 0x1193E}, {0x11943, 0x11943}, {0x119D4, 0x119D7},
{0x119DA, 0x119DB}, {0x119E0, 0x119E0}, {0x11A01, 0x11A0A}, {0x11A33, 0x11A38},
{0x11A3B, 0x11A3E}, {0x11A47, 0x11A47}, {0x11A51, 0x11A56}, {0x11A59, 0x11A5B},
{0x11A8A, 0x11A96}, {0x11A98, 0x11A99}, {0x11B60, 0x11B60}, {0x11B62, 0x11B64},
{0x11B66, 0x11B66}, {0x11C30, 0x11C36}, {0x11C38, 0x11C3D}, {0x11C3F, 0x11C3F},
{0x11C92, 0x11CA7}, {0x11CAA, 0x11CB0}, {0x11CB2, 0x11CB3}, {0x11CB5, 0x11CB6},
{0x11D31, 0x11D36}, {0x11D3A, 0x11D3A}, {0x11D3C, 0x11D3D}, {0x11D3F, 0x11D45},
{0x11D47, 0x11D47}, {0x11D90, 0x11D91}, {0x11D95, 0x11D95}, {0x11D97, 0x11D97},
{0x11EF3, 0x11EF4}, {0x11F00, 0x11F01}, {0x11F36, 0x11F3A}, {0x11F40, 0x11F40},
{0x11F42, 0x11F42}, {0x11F5A, 0x11F5A}, {0x13430, 0x13440}, {0x13447, 0x13455},
{0x1611E, 0x16129}, {0x1612D, 0x1612F}, {0x16AF0, 0x16AF4}, {0x16B30, 0x16B36},
{0x16F4F, 0x16F4F}, {0x16F8F, 0x16F92}, {0x16FE4, 0x16FE4}, {0x1BC9D, 0x1BC9E},
{0x1BCA0, 0x1BCA3}, {0x1CF00, 0x1CF2D}, {0x1CF30, 0x1CF46}, {0x1D167, 0x1D169},
{0x1D173, 0x1D182}, {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD}, {0x1D242, 0x1D244},
{0x1DA00, 0x1DA36}, {0x1DA3B, 0x1DA6C}, {0x1DA75, 0x1DA75}, {0x1DA84, 0x1DA84},
{0x1DA9B, 0x1DA9F}, {0x1DAA1, 0x1DAAF}, {0x1E000, 0x1E006}, {0x1E008, 0x1E018},
{0x1E01B, 0x1E021}, {0x1E023, 0x1E024}, {0x1E026, 0x1E02A}, {0x1E08F, 0x1E08F},
{0x1E130, 0x1E136}, {0x1E2AE, 0x1E2AE}, {0x1E2EC, 0x1E2EF}, {0x1E4EC, 0x1E4EF},
{0x1E5EE, 0x1E5EF}, {0x1E6E3, 0x1E6E3}, {0x1E6E6, 0x1E6E6}, {0x1E6EE, 0x1E6EF},
{0x1E6F5, 0x1E6F5}, {0x1E8D0, 0x1E8D6}, {0x1E944, 0x1E94A}, {0xE0000, 0xE0FFF}};
/* test for 8-bit control characters */
if (ucs == 0)
return 0;
if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
return -1;
/* binary search in table of non-spacing characters */
if (bisearch (ucs, combining, sizeof (combining) / sizeof (struct interval) - 1))
return 0;
/* sorted list of non-overlapping intervals of wide/fullwidth characters */
/* Unicode 17.0.0 - East_Asian_Width W and F */
static const struct interval wide[] = {
{0x1100, 0x115F}, {0x231A, 0x231B}, {0x2329, 0x232A}, {0x23E9, 0x23EC},
{0x23F0, 0x23F0}, {0x23F3, 0x23F3}, {0x25FD, 0x25FE}, {0x2614, 0x2615},
{0x2630, 0x2637}, {0x2648, 0x2653}, {0x267F, 0x267F}, {0x268A, 0x268F},
{0x2693, 0x2693}, {0x26A1, 0x26A1}, {0x26AA, 0x26AB}, {0x26BD, 0x26BE},
{0x26C4, 0x26C5}, {0x26CE, 0x26CE}, {0x26D4, 0x26D4}, {0x26EA, 0x26EA},
{0x26F2, 0x26F3}, {0x26F5, 0x26F5}, {0x26FA, 0x26FA}, {0x26FD, 0x26FD},
{0x2705, 0x2705}, {0x270A, 0x270B}, {0x2728, 0x2728}, {0x274C, 0x274C},
{0x274E, 0x274E}, {0x2753, 0x2755}, {0x2757, 0x2757}, {0x2795, 0x2797},
{0x27B0, 0x27B0}, {0x27BF, 0x27BF}, {0x2B1B, 0x2B1C}, {0x2B50, 0x2B50},
{0x2B55, 0x2B55}, {0x2E80, 0x2E99}, {0x2E9B, 0x2EF3}, {0x2F00, 0x2FD5},
{0x2FF0, 0x303E}, {0x3041, 0x3096}, {0x3099, 0x30FF}, {0x3105, 0x312F},
{0x3131, 0x318E}, {0x3190, 0x31E5}, {0x31EF, 0x321E}, {0x3220, 0x3247},
{0x3250, 0xA48C}, {0xA490, 0xA4C6}, {0xA960, 0xA97C}, {0xAC00, 0xD7A3},
{0xF900, 0xFAFF}, {0xFE10, 0xFE19}, {0xFE30, 0xFE52}, {0xFE54, 0xFE66},
{0xFE68, 0xFE6B}, {0xFF01, 0xFF60}, {0xFFE0, 0xFFE6}, {0x16FE0, 0x16FE4},
{0x16FF0, 0x16FF6}, {0x17000, 0x18CD5}, {0x18CFF, 0x18D1E}, {0x18D80, 0x18DF2},
{0x1AFF0, 0x1AFF3}, {0x1AFF5, 0x1AFFB}, {0x1AFFD, 0x1AFFE}, {0x1B000, 0x1B122},
{0x1B132, 0x1B132}, {0x1B150, 0x1B152}, {0x1B155, 0x1B155}, {0x1B164, 0x1B167},
{0x1B170, 0x1B2FB}, {0x1D300, 0x1D356}, {0x1D360, 0x1D376}, {0x1F004, 0x1F004},
{0x1F0CF, 0x1F0CF}, {0x1F18E, 0x1F18E}, {0x1F191, 0x1F19A}, {0x1F200, 0x1F202},
{0x1F210, 0x1F23B}, {0x1F240, 0x1F248}, {0x1F250, 0x1F251}, {0x1F260, 0x1F265},
{0x1F300, 0x1F320}, {0x1F32D, 0x1F335}, {0x1F337, 0x1F37C}, {0x1F37E, 0x1F393},
{0x1F3A0, 0x1F3CA}, {0x1F3CF, 0x1F3D3}, {0x1F3E0, 0x1F3F0}, {0x1F3F4, 0x1F3F4},
{0x1F3F8, 0x1F43E}, {0x1F440, 0x1F440}, {0x1F442, 0x1F4FC}, {0x1F4FF, 0x1F53D},
{0x1F54B, 0x1F54E}, {0x1F550, 0x1F567}, {0x1F57A, 0x1F57A}, {0x1F595, 0x1F596},
{0x1F5A4, 0x1F5A4}, {0x1F5FB, 0x1F64F}, {0x1F680, 0x1F6C5}, {0x1F6CC, 0x1F6CC},
{0x1F6D0, 0x1F6D2}, {0x1F6D5, 0x1F6D8}, {0x1F6DC, 0x1F6DF}, {0x1F6EB, 0x1F6EC},
{0x1F6F4, 0x1F6FC}, {0x1F7E0, 0x1F7EB}, {0x1F7F0, 0x1F7F0}, {0x1F90C, 0x1F93A},
{0x1F93C, 0x1F945}, {0x1F947, 0x1F9FF}, {0x1FA70, 0x1FA7C}, {0x1FA80, 0x1FA8A},
{0x1FA8E, 0x1FAC6}, {0x1FAC8, 0x1FAC8}, {0x1FACD, 0x1FADC}, {0x1FADF, 0x1FAEA},
{0x1FAEF, 0x1FAF8}, {0x20000, 0x2FFFD}, {0x30000, 0x3FFFD}};
if (bisearch (ucs, wide, sizeof (wide) / sizeof (struct interval) - 1))
return 2;
return 1;
}
// End of https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c inherited code
static int unicode_to_cp437 (uint64_t code_point) {
// Braille patterns U+2800-U+28FF: approximate using CP437 block/shade characters.
// Braille dot layout (bit positions):
// bit0 bit3 (row 0)
// bit1 bit4 (row 1)
// bit2 bit5 (row 2)
// bit6 bit7 (row 3)
if (code_point >= 0x2800 && code_point <= 0x28ff) {
uint32_t dots = (uint32_t)(code_point - 0x2800);
if (dots == 0)
return 0x20;
if (dots == 0xff)
return 0xdb;
bool has_top = dots & 0x1b;
bool has_bottom = dots & 0xe4;
bool has_left = dots & 0x47;
bool has_right = dots & 0xb8;
if (has_top && !has_bottom)
return 0xdf; // ▀
if (has_bottom && !has_top)
return 0xdc; // ▄
if (has_left && !has_right)
return 0xdd; // ▌
if (has_right && !has_left)
return 0xde; // ▐
// Count set bits for density-based shade
uint32_t n = dots - ((dots >> 1) & 0x55);
n = (n & 0x33) + ((n >> 2) & 0x33);
n = (n + (n >> 4)) & 0x0f;
if (n <= 2)
return 0xb0; // ░
if (n <= 4)
return 0xb1; // ▒
if (n <= 6)
return 0xb2; // ▓
return 0xdb; // █
}
switch (code_point) {
case 0x263a:
return 1;
case 0x263b:
return 2;
case 0x2665:
return 3;
case 0x2666:
return 4;
case 0x25c6:
return 4;
case 0x2663:
return 5;
case 0x2660:
return 6;
case 0x2022:
return 7;
case 0x25d8:
return 8;
case 0x25cb:
return 9;
case 0x25d9:
return 10;
case 0x2642:
return 11;
case 0x2640:
return 12;
case 0x266a:
return 13;
case 0x266b:
return 14;
case 0x263c:
return 15;
case 0x00a4:
return 15;
case 0x25ba:
return 16;
case 0x25b6:
return 16;
case 0x25c4:
return 17;
case 0x25c0:
return 17;
case 0x2195:
return 18;
case 0x203c:
return 19;
case 0x00b6:
return 20;
case 0x00a7:
return 21;
case 0x25ac:
return 22;
case 0x21a8:
return 23;
case 0x2191:
return 24;
case 0x2193:
return 25;
case 0x2192:
return 26;
case 0x2190:
return 27;
case 0x221f:
return 28;
case 0x2194:
return 29;
case 0x25b2:
return 30;
case 0x25bc:
return 31;
case 0x00a8:
return 0x22;
case 0x00b4:
return 0x27;
case 0x00b8:
return 0x2c;
case 0x00ad:
return 0x2d;
case 0x00c0:
return 0x41;
case 0x00c1:
return 0x41;
case 0x00c2:
return 0x41;
case 0x00c3:
return 0x41;
case 0x00a9:
return 0x43;
case 0x00d0:
return 0x44;
case 0x00c8:
return 0x45;
case 0x00ca:
return 0x45;
case 0x00cb:
return 0x45;
case 0x00cc:
return 0x49;
case 0x00cd:
return 0x49;
case 0x00ce:
return 0x49;
case 0x00cf:
return 0x49;
case 0x212a:
return 0x4b;
case 0x00d2:
return 0x4f;
case 0x00d3:
return 0x4f;
case 0x00d4:
return 0x4f;
case 0x00d5:
return 0x4f;
case 0x00ae:
return 0x52;
case 0x00d9:
return 0x55;
case 0x00da:
return 0x55;
case 0x00db:
return 0x55;
case 0x00dd:
return 0x59;
case 0x23bd:
return 0x5f;
case 0x00e3:
return 0x61;
case 0x00f5:
return 0x6f;
case 0x00d7:
return 0x78;
case 0x00fd:
return 0x79;
case 0x00a6:
return 0x7c;
case 0x2302:
return 127;
case 0x00c7:
return 128;
case 0x00fc:
return 129;
case 0x00e9:
return 130;
case 0x00e2:
return 131;
case 0x00e4:
return 132;
case 0x00e0:
return 133;
case 0x00e5:
return 134;
case 0x00e7:
return 135;
case 0x00ea:
return 136;
case 0x00eb:
return 137;
case 0x00e8:
return 138;
case 0x00ef:
return 139;
case 0x00ee:
return 140;
case 0x00ec:
return 141;
case 0x00c4:
return 142;
case 0x00c5:
return 143;
case 0x212b:
return 143;
case 0x00c9:
return 144;
case 0x00e6:
return 145;
case 0x00c6:
return 146;
case 0x00f4:
return 147;
case 0x00f6:
return 148;
case 0x00f2:
return 149;
case 0x00fb:
return 150;
case 0x00f9:
return 151;
case 0x00ff:
return 152;
case 0x00d6:
return 153;
case 0x00dc:
return 154;
case 0x00a2:
return 155;
case 0x00a3:
return 156;
case 0x00a5:
return 157;
case 0x20a7:
return 158;
case 0x0192:
return 159;
case 0x00e1:
return 160;
case 0x00ed:
return 161;
case 0x00f3:
return 162;
case 0x00fa:
return 163;
case 0x00f1:
return 164;
case 0x00d1:
return 165;
case 0x00aa:
return 166;
case 0x00ba:
return 167;
case 0x00bf:
return 168;
case 0x2310:
return 169;
case 0x00ac:
return 170;
case 0x00bd:
return 171;
case 0x00bc:
return 172;
case 0x00a1:
return 173;
case 0x00ab:
return 174;
case 0x00bb:
return 175;
case 0x2591:
return 176;
case 0x2592:
return 177;
case 0x2593:
return 178;
case 0x2502:
return 179;
case 0x2524:
return 180;
case 0x2561:
return 181;
case 0x2562:
return 182;
case 0x2556:
return 183;
case 0x2555:
return 184;
case 0x2563:
return 185;
case 0x2551:
return 186;
case 0x2557:
return 187;
case 0x255d:
return 188;
case 0x255c:
return 189;
case 0x255b:
return 190;
case 0x2510:
return 191;
case 0x2514:
return 192;
case 0x2534:
return 193;
case 0x252c:
return 194;
case 0x251c:
return 195;
case 0x2500:
return 196;
case 0x253c:
return 197;
case 0x255e:
return 198;
case 0x255f:
return 199;
case 0x255a:
return 200;
case 0x2554:
return 201;
case 0x2569:
return 202;
case 0x2566:
return 203;
case 0x2560:
return 204;
case 0x2550:
return 205;
case 0x256c:
return 206;
case 0x2567:
return 207;
case 0x2568:
return 208;
case 0x2564:
return 209;
case 0x2565:
return 210;
case 0x2559:
return 211;
case 0x2558:
return 212;
case 0x2552:
return 213;
case 0x2553:
return 214;
case 0x256b:
return 215;
case 0x256a:
return 216;
case 0x2518:
return 217;
case 0x250c:
return 218;
case 0x2588:
return 219;
case 0x2584:
return 220;
case 0x258c:
return 221;
case 0x2590:
return 222;
case 0x2580:
return 223;
case 0x03b1:
return 224;
case 0x00df:
return 225;
case 0x03b2:
return 225;
case 0x0393:
return 226;
case 0x03c0:
return 227;
case 0x03a3:
return 228;
case 0x03c3:
return 229;
case 0x00b5:
return 230;
case 0x03bc:
return 230;
case 0x03c4:
return 231;
case 0x03a6:
return 232;
case 0x00d8:
return 232;
case 0x0398:
return 233;
case 0x03a9:
return 234;
case 0x2126:
return 234;
case 0x03b4:
return 235;
case 0x00f0:
return 235;
case 0x221e:
return 236;
case 0x03c6:
return 237;
case 0x00f8:
return 237;
case 0x03b5:
return 238;
case 0x2208:
return 238;
case 0x2229:
return 239;
case 0x2261:
return 240;
case 0x00b1:
return 241;
case 0x2265:
return 242;
case 0x2264:
return 243;
case 0x2320:
return 244;
case 0x2321:
return 245;
case 0x00f7:
return 246;
case 0x2248:
return 247;
case 0x00b0:
return 248;
case 0x2219:
return 249;
case 0x00b7:
return 250;
case 0x221a:
return 251;
case 0x207f:
return 252;
case 0x00b2:
return 253;
case 0x25a0:
return 254;
case 0xfffd:
return 254;
case 0x00a0:
return 255;
// Approximate mappings for Unicode characters without exact CP437 equivalents
// Rounded/arc box drawing corners
case 0x256d:
return 0xda; // ╭ → ┌
case 0x256e:
return 0xbf; // ╮ → ┐
case 0x256f:
return 0xd9; // ╯ → ┘
case 0x2570:
return 0xc0; // ╰ → └
// Diagonal box drawing
case 0x2571:
return 0x2f; // → /
case 0x2572:
return 0x5c; // ╲ → \ (backslash)
case 0x2573:
return 0x58; // → X
// Heavy box drawing → single-line equivalents
case 0x2501:
return 0xc4; // ━ → ─
case 0x2503:
return 0xb3; // ┃ → │
case 0x250f:
return 0xda; // ┏ → ┌
case 0x2513:
return 0xbf; // ┓ → ┐
case 0x2517:
return 0xc0; // ┗ → └
case 0x251b:
return 0xd9; // ┛ → ┘
case 0x2523:
return 0xc3; // ┣ → ├
case 0x252b:
return 0xb4; // ┫ → ┤
case 0x2533:
return 0xc2; // ┳ → ┬
case 0x253b:
return 0xc1; // ┻ → ┴
case 0x254b:
return 0xc5; // ╋ → ┼
// Mixed heavy/light box drawing corners
case 0x250d:
return 0xda; // ┍ → ┌
case 0x250e:
return 0xda; // ┎ → ┌
case 0x2511:
return 0xbf; // ┑ → ┐
case 0x2512:
return 0xbf; // ┒ → ┐
case 0x2515:
return 0xc0; // ┕ → └
case 0x2516:
return 0xc0; // ┖ → └
case 0x2519:
return 0xd9; // ┙ → ┘
case 0x251a:
return 0xd9; // ┚ → ┘
// Mixed heavy/light box drawing T-pieces
case 0x251d:
return 0xc3; // ┝ → ├
case 0x251e:
return 0xc3; // ┞ → ├
case 0x251f:
return 0xc3; // ┟ → ├
case 0x2520:
return 0xc3; // ┠ → ├
case 0x2521:
return 0xc3; // ┡ → ├
case 0x2522:
return 0xc3; // ┢ → ├
case 0x2525:
return 0xb4; // ┥ → ┤
case 0x2526:
return 0xb4; // ┦ → ┤
case 0x2527:
return 0xb4; // ┧ → ┤
case 0x2528:
return 0xb4; // ┨ → ┤
case 0x2529:
return 0xb4; // ┩ → ┤
case 0x252a:
return 0xb4; // ┪ → ┤
case 0x252d:
return 0xc2; // ┭ → ┬
case 0x252e:
return 0xc2; // ┮ → ┬
case 0x252f:
return 0xc2; // ┯ → ┬
case 0x2530:
return 0xc2; // ┰ → ┬
case 0x2531:
return 0xc2; // ┱ → ┬
case 0x2532:
return 0xc2; // ┲ → ┬
case 0x2535:
return 0xc1; // ┵ → ┴
case 0x2536:
return 0xc1; // ┶ → ┴
case 0x2537:
return 0xc1; // ┷ → ┴
case 0x2538:
return 0xc1; // ┸ → ┴
case 0x2539:
return 0xc1; // ┹ → ┴
case 0x253a:
return 0xc1; // ┺ → ┴
// Mixed heavy/light box drawing crosses
case 0x253d:
return 0xc5; // ┽ → ┼
case 0x253e:
return 0xc5; // ┾ → ┼
case 0x253f:
return 0xc5; // ┿ → ┼
case 0x2540:
return 0xc5; // ╀ → ┼
case 0x2541:
return 0xc5; // ╁ → ┼
case 0x2542:
return 0xc5; // ╂ → ┼
case 0x2543:
return 0xc5; // ╃ → ┼
case 0x2544:
return 0xc5; // ╄ → ┼
case 0x2545:
return 0xc5; // ╅ → ┼
case 0x2546:
return 0xc5; // ╆ → ┼
case 0x2547:
return 0xc5; // ╇ → ┼
case 0x2548:
return 0xc5; // ╈ → ┼
case 0x2549:
return 0xc5; // ╉ → ┼
case 0x254a:
return 0xc5; // ╊ → ┼
// Dashed/dotted box drawing → solid equivalents
case 0x2504:
return 0xc4; // ┄ → ─
case 0x2505:
return 0xc4; // ┅ → ─
case 0x2506:
return 0xb3; // ┆ → │
case 0x2507:
return 0xb3; // ┇ → │
case 0x2508:
return 0xc4; // ┈ → ─
case 0x2509:
return 0xc4; // ┉ → ─
case 0x250a:
return 0xb3; // ┊ → │
case 0x250b:
return 0xb3; // ┋ → │
// Box drawing half-lines and fragments
case 0x2574:
return 0xc4; // ╴ → ─
case 0x2575:
return 0xb3; // ╵ → │
case 0x2576:
return 0xc4; // ╶ → ─
case 0x2577:
return 0xb3; // ╷ → │
case 0x2578:
return 0xc4; // ╸ → ─
case 0x2579:
return 0xb3; // ╹ → │
case 0x257a:
return 0xc4; // ╺ → ─
case 0x257b:
return 0xb3; // ╻ → │
case 0x257c:
return 0xc4; // ╼ → ─
case 0x257d:
return 0xb3; // ╽ → │
case 0x257e:
return 0xc4; // ╾ → ─
case 0x257f:
return 0xb3; // ╿ → │
// Triangle variants → filled equivalents
case 0x25b3:
return 30; // △ → ▲
case 0x25b5:
return 30; // ▵ → ▲
case 0x25b7:
return 16; // ▷ → ►
case 0x25b9:
return 16; // ▹ → ►
case 0x25bd:
return 31; // ▽ → ▼
case 0x25bf:
return 31; // ▿ → ▼
case 0x25c1:
return 17; // ◁ → ◄
case 0x25c3:
return 17; // ◃ → ◄
// Fractional block elements → closest CP437 block
case 0x2581:
return 0xdc; // ▁ (lower 1/8) → ▄
case 0x2582:
return 0xdc; // ▂ (lower 1/4) → ▄
case 0x2583:
return 0xdc; // ▃ (lower 3/8) → ▄
case 0x2585:
return 0xdc; // ▅ (lower 5/8) → ▄
case 0x2586:
return 0xdb; // ▆ (lower 3/4) → █
case 0x2587:
return 0xdb; // ▇ (lower 7/8) → █
case 0x2589:
return 0xdb; // ▉ (left 7/8) → █
case 0x258a:
return 0xdb; // ▊ (left 3/4) → █
case 0x258b:
return 0xdd; // ▋ (left 5/8) → ▌
case 0x258d:
return 0xdd; // ▍ (left 3/8) → ▌
case 0x258e:
return 0xdd; // ▎ (left 1/4) → ▌
case 0x258f:
return 0xdd; // ▏ (left 1/8) → ▌
case 0x2594:
return 0xdf; // ▔ (upper 1/8) → ▀
case 0x2595:
return 0xde; // ▕ (right 1/8) → ▐
// Quadrant block elements
case 0x2596:
return 0xdc; // ▖ → ▄
case 0x2597:
return 0xdc; // ▗ → ▄
case 0x2598:
return 0xdf; // ▘ → ▀
case 0x2599:
return 0xdb; // ▙ → █
case 0x259a:
return 0xb1; // ▚ → ▒
case 0x259b:
return 0xdb; // ▛ → █
case 0x259c:
return 0xdb; // ▜ → █
case 0x259d:
return 0xdf; // ▝ → ▀
case 0x259e:
return 0xb1; // ▞ → ▒
case 0x259f:
return 0xdb; // ▟ → █
// Circles and bullets
case 0x25cf:
return 0x07; // ● → •
case 0x25c9:
return 0x0a; // ◉ → ◙
case 0x25ef:
return 0x09; // ◯ → ○
case 0x25e6:
return 0x09; // ◦ → ○
case 0x25aa:
return 0xfe; // ▪ → ■
case 0x25fc:
return 0xfe; // ◼ → ■
// Typographic punctuation
case 0x2013:
return 0x2d; // (en dash) → -
case 0x2014:
return 0x2d; // — (em dash) → -
case 0x2018:
return 0x27; // ' (left single quote) → '
case 0x2019:
return 0x27; // ' (right single quote) → '
case 0x201c:
return 0x22; // " (left double quote) → "
case 0x201d:
return 0x22; // " (right double quote) → "
case 0x2026:
return 0xfa; // … (ellipsis) → ·
case 0x2212:
return 0x2d; // (minus sign) → -
// Check marks
case 0x2713:
return 0xfb; // ✓ → √
case 0x2714:
return 0xfb; // ✔ → √
// Double arrows → single arrow equivalents
case 0x21d0:
return 27; // ⇐ → ←
case 0x21d1:
return 24; // ⇑ → ↑
case 0x21d2:
return 26; // ⇒ → →
case 0x21d3:
return 25; // ⇓ → ↓
case 0x21d4:
return 29; // ⇔ → ↔
case 0x21d5:
return 18; // ⇕ → ↕
// Summation sign
case 0x2211:
return 0xe4; // ∑ → Σ
// Horizontal line extension
case 0x23af:
return 0xc4; // ⎯ → ─
// Media transport symbols
case 0x23f4:
return 17; // ⏴ → ◄
case 0x23f5:
return 16; // ⏵ → ►
case 0x23f6:
return 30; // ⏶ → ▲
case 0x23f7:
return 31; // ⏷ → ▼
case 0x23f8:
return 0xba; // ⏸ → ║
case 0x23f9:
return 0xfe; // ⏹ → ■
case 0x23fa:
return 0x07; // ⏺ → •
// Square bracket pieces
case 0x23a1:
return 0xda; // ⎡ → ┌
case 0x23a2:
return 0xb3; // ⎢ → │
case 0x23a3:
return 0xc0; // ⎣ → └
case 0x23a4:
return 0xbf; // ⎤ → ┐
case 0x23a5:
return 0xb3; // ⎥ → │
case 0x23a6:
return 0xd9; // ⎦ → ┘
// Curly bracket pieces
case 0x23a7:
return 0xda; // ⎧ → ┌
case 0x23a8:
return 0xc3; // ⎨ → ├
case 0x23a9:
return 0xc0; // ⎩ → └
case 0x23aa:
return 0xb3; // ⎪ → │
case 0x23ab:
return 0xbf; // ⎫ → ┐
case 0x23ac:
return 0xb4; // ⎬ → ┤
case 0x23ad:
return 0xd9; // ⎭ → ┘
case 0x23ae:
return 0xb3; // ⎮ → │
// Vertical box lines
case 0x23b8:
return 0xb3; // ⎸ → │
case 0x23b9:
return 0xb3; // ⎹ → │
// Horizontal scan lines (0x23bd already mapped above)
case 0x23ba:
return 0xc4; // ⎺ → ─
case 0x23bb:
return 0xc4; // ⎻ → ─
case 0x23bc:
return 0xc4; // ⎼ → ─
// Dentistry/angle symbols
case 0x23be:
return 0xb3; // ⎾ → │
case 0x23bf:
return 0xc0; // ⎿ → └
// Corner brackets
case 0x231c:
return 0xda; // ⌜ → ┌
case 0x231d:
return 0xbf; // ⌝ → ┐
case 0x231e:
return 0xc0; // ⌞ → └
case 0x231f:
return 0xd9; // ⌟ → ┘
}
return -1;
}
static void flanterm_putchar (struct flanterm_context* ctx, uint8_t c) {
if (ctx->discard_next || (c == 0x18 || c == 0x1a)) {
ctx->discard_next = false;
ctx->escape = false;
ctx->control_sequence = false;
ctx->unicode_remaining = 0;
ctx->osc = false;
ctx->osc_escape = false;
ctx->g_select = 0;
ctx->last_was_graphic = false;
return;
}
if (ctx->unicode_remaining != 0) {
if ((c & 0xc0) != 0x80) {
ctx->unicode_remaining = 0;
ctx->raw_putchar (ctx, 0xfe);
goto unicode_error;
}
ctx->unicode_remaining--;
ctx->code_point |= (uint64_t)(c & 0x3f) << (6 * ctx->unicode_remaining);
// Reject overlong encodings and out-of-range codepoints early
// by validating the first continuation byte against the lead byte.
// 3-byte lead E0: first continuation must be >= 0xA0 (code_point >= 0x800)
// 4-byte lead F0: first continuation must be >= 0x90 (code_point >= 0x10000)
// 4-byte lead F4: first continuation must be <= 0x8F (code_point <= 0x10FFFF)
if (ctx->unicode_remaining == 1 && ctx->code_point < 0x800) {
ctx->unicode_remaining = 0;
goto unicode_error;
}
if (ctx->unicode_remaining == 2 && ctx->code_point < 0x10000) {
ctx->unicode_remaining = 0;
goto unicode_error;
}
if (ctx->unicode_remaining == 2 && ctx->code_point > 0x10ffff) {
ctx->unicode_remaining = 0;
goto unicode_error;
}
if (ctx->unicode_remaining != 0) {
return;
}
if (ctx->code_point >= 0xd800 && ctx->code_point <= 0xdfff) {
goto unicode_error;
}
int cc = unicode_to_cp437 (ctx->code_point);
if (cc == -1) {
int replacement_width = mk_wcwidth (ctx->code_point);
if (replacement_width > 0) {
ctx->last_printed_char = 0xfe;
ctx->last_was_graphic = true;
ctx->raw_putchar (ctx, 0xfe);
}
for (int i = 1; i < replacement_width; i++) {
ctx->raw_putchar (ctx, ' ');
}
} else {
ctx->last_printed_char = cc;
ctx->last_was_graphic = true;
ctx->raw_putchar (ctx, cc);
}
return;
}
unicode_error:
if (c >= 0xc2 && c <= 0xf4) {
ctx->g_select = 0;
if (c >= 0xc2 && c <= 0xdf) {
ctx->unicode_remaining = 1;
ctx->code_point = (uint64_t)(c & 0x1f) << 6;
} else if (c >= 0xe0 && c <= 0xef) {
ctx->unicode_remaining = 2;
ctx->code_point = (uint64_t)(c & 0x0f) << (6 * 2);
} else if (c >= 0xf0 && c <= 0xf4) {
ctx->unicode_remaining = 3;
ctx->code_point = (uint64_t)(c & 0x07) << (6 * 3);
}
return;
}
if (ctx->escape == true) {
escape_parse (ctx, c);
return;
}
if (ctx->g_select) {
if (c <= 0x1f || c == 0x7f) {
ctx->g_select = 0;
} else {
ctx->g_select--;
switch (c) {
case 'B':
ctx->charsets[ctx->g_select] = CHARSET_DEFAULT;
break;
case '0':
ctx->charsets[ctx->g_select] = CHARSET_DEC_SPECIAL;
break;
}
ctx->g_select = 0;
return;
}
}
if ((c <= 0x1f && c != 0x1b) || c == 0x7f) {
ctx->last_was_graphic = false;
}
size_t x, y;
ctx->get_cursor_pos (ctx, &x, &y);
switch (c) {
case 0x00:
case 0x7f:
return;
case 0x1b:
ctx->escape_offset = 0;
ctx->escape = true;
return;
case '\t': {
size_t next_tab = (x / ctx->tab_size + 1) * ctx->tab_size;
if (next_tab >= ctx->cols) {
ctx->set_cursor_pos (ctx, ctx->cols - 1, y);
return;
}
ctx->set_cursor_pos (ctx, next_tab, y);
return;
}
case 0x0b:
case 0x0c:
case '\n':
if (y == ctx->scroll_bottom_margin - 1) {
ctx->scroll (ctx);
ctx->set_cursor_pos (ctx, x, y);
} else {
ctx->set_cursor_pos (ctx, x, y + 1);
}
return;
case '\b':
if (x > 0) {
ctx->set_cursor_pos (ctx, x - 1, y);
}
return;
case '\r':
ctx->set_cursor_pos (ctx, 0, y);
return;
case '\a':
// The bell is handled by the kernel
if (ctx->callback != NULL) {
ctx->callback (ctx, FLANTERM_CB_BELL, 0, 0, 0);
}
return;
case 14:
// Move to G1 set
ctx->current_charset = 1;
return;
case 15:
// Move to G0 set
ctx->current_charset = 0;
return;
}
if (ctx->insert_mode == true) {
for (size_t i = ctx->cols - 1; i > x; i--) {
ctx->move_character (ctx, i, y, i - 1, y);
}
}
// Translate character set
switch (ctx->charsets[ctx->current_charset]) {
case CHARSET_DEFAULT:
break;
case CHARSET_DEC_SPECIAL:
if (dec_special_print (ctx, c)) {
return;
}
break;
}
if (c >= 0x20 && c <= 0x7e) {
ctx->last_printed_char = c;
ctx->last_was_graphic = true;
ctx->raw_putchar (ctx, c);
} else if (c >= 0x80) {
ctx->last_printed_char = 0xfe;
ctx->last_was_graphic = true;
ctx->raw_putchar (ctx, 0xfe);
}
}
void flanterm_flush (struct flanterm_context* ctx) { ctx->double_buffer_flush (ctx); }
void flanterm_full_refresh (struct flanterm_context* ctx) { ctx->full_refresh (ctx); }
void flanterm_deinit (struct flanterm_context* ctx, void (*_free) (void*, size_t)) {
ctx->deinit (ctx, _free);
}
void flanterm_get_dimensions (struct flanterm_context* ctx, size_t* cols, size_t* rows) {
*cols = ctx->cols;
*rows = ctx->rows;
}
void flanterm_set_autoflush (struct flanterm_context* ctx, bool state) { ctx->autoflush = state; }
void flanterm_set_callback (struct flanterm_context* ctx,
void (*callback) (struct flanterm_context*, uint64_t, uint64_t,
uint64_t, uint64_t)) {
ctx->callback = callback;
}