All checks were successful
Build documentation / build-and-deploy (push) Successful in 55s
2502 lines
68 KiB
C
2502 lines
68 KiB
C
/* 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;
|
||
}
|