1/* SPDX-License-Identifier: BSD-2-Clause */
2
3/* Copyright (C) 2022-2026 Mintsuki and contributors.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
19 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25 * POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#ifdef __cplusplus
29#error "Please do not compile Flanterm as C++ code! Flanterm should be compiled as C99 or newer."
30#endif
31
32#ifndef __STDC_VERSION__
33#error "Flanterm must be compiled as C99 or newer."
34#endif
35
36#include <stdint.h>
37#include <stddef.h>
38#include <stdbool.h>
39
40#ifndef FLANTERM_IN_FLANTERM
41#define FLANTERM_IN_FLANTERM
42#endif
43
44#include "flanterm.h"
45
46// Tries to implement this standard for terminfo
47// https://man7.org/linux/man-pages/man4/console_codes.4.html
48
49static const uint32_t col256[] = {
50 0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f,
51 0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, 0x008787, 0x0087af,
52 0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff,
53 0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f,
54 0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af,
55 0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff,
56 0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f,
57 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af,
58 0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff,
59 0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f,
60 0x875f87, 0x875faf, 0x875fd7, 0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af,
61 0x8787d7, 0x8787ff, 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff,
62 0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f,
63 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af,
64 0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff,
65 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f,
66 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, 0xafd787, 0xafd7af,
67 0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff,
68 0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f,
69 0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af,
70 0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff,
71 0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f,
72 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, 0xff0000, 0xff005f, 0xff0087, 0xff00af,
73 0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff,
74 0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f,
75 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, 0xffd700, 0xffd75f, 0xffd787, 0xffd7af,
76 0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff,
77 0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e,
78 0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e,
79 0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee
80};
81
82#define CHARSET_DEFAULT 0
83#define CHARSET_DEC_SPECIAL 1
84
85void flanterm_context_reinit(struct flanterm_context *ctx) {
86 ctx->tab_size = 8;
87 ctx->autoflush = true;
88 ctx->cursor_enabled = true;
89 ctx->scroll_enabled = true;
90 ctx->wrap_enabled = true;
91 ctx->origin_mode = false;
92 ctx->control_sequence = false;
93 ctx->escape = false;
94 ctx->osc = false;
95 ctx->osc_escape = false;
96 ctx->rrr = false;
97 ctx->discard_next = false;
98 ctx->bold = false;
99 ctx->bg_bold = false;
100 ctx->reverse_video = false;
101 ctx->dec_private = false;
102 ctx->insert_mode = false;
103 ctx->csi_unhandled = false;
104 ctx->unicode_remaining = 0;
105 ctx->g_select = 0;
106 ctx->charsets[0] = CHARSET_DEFAULT;
107 ctx->charsets[1] = CHARSET_DEC_SPECIAL;
108 ctx->current_charset = 0;
109 ctx->escape_offset = 0;
110 ctx->esc_values_i = 0;
111 ctx->saved_cursor_x = 0;
112 ctx->saved_cursor_y = 0;
113 ctx->current_primary = (size_t)-1;
114 ctx->current_bg = (size_t)-1;
115 ctx->saved_state_bold = false;
116 ctx->saved_state_bg_bold = false;
117 ctx->saved_state_reverse_video = false;
118 ctx->saved_state_origin_mode = false;
119 ctx->saved_state_current_charset = 0;
120 ctx->saved_state_charsets[0] = CHARSET_DEFAULT;
121 ctx->saved_state_charsets[1] = CHARSET_DEC_SPECIAL;
122 ctx->saved_state_current_primary = (size_t)-1;
123 ctx->saved_state_current_bg = (size_t)-1;
124 ctx->last_printed_char = ' ';
125 ctx->last_was_graphic = false;
126 ctx->scroll_top_margin = 0;
127 ctx->scroll_bottom_margin = ctx->rows;
128}
129
130static void flanterm_putchar(struct flanterm_context *ctx, uint8_t c);
131
132void flanterm_write(struct flanterm_context *ctx, const char *buf, size_t count) {
133 for (size_t i = 0; i < count; i++) {
134 flanterm_putchar(ctx, c: buf[i]);
135 }
136
137 if (ctx->autoflush) {
138 ctx->double_buffer_flush(ctx);
139 }
140}
141
142static void sgr(struct flanterm_context *ctx) {
143 size_t i = 0;
144
145 if (!ctx->esc_values_i)
146 goto def;
147
148 for (; i < ctx->esc_values_i; i++) {
149 size_t offset;
150
151 if (ctx->esc_values[i] == 0) {
152def:
153 if (ctx->reverse_video) {
154 ctx->reverse_video = false;
155 ctx->swap_palette(ctx);
156 }
157 ctx->bold = false;
158 ctx->bg_bold = false;
159 ctx->current_primary = (size_t)-1;
160 ctx->current_bg = (size_t)-1;
161 ctx->set_text_bg_default(ctx);
162 ctx->set_text_fg_default(ctx);
163 continue;
164 }
165
166 else if (ctx->esc_values[i] == 1) {
167 ctx->bold = true;
168 if (ctx->current_primary == (size_t)-2) {
169 // RGB/256-color; bold does not alter the colour
170 } else if (ctx->current_primary != (size_t)-1) {
171 if (!ctx->reverse_video) {
172 ctx->set_text_fg_bright(ctx, ctx->current_primary);
173 } else {
174 ctx->set_text_bg_bright(ctx, ctx->current_primary);
175 }
176 } else {
177 if (!ctx->reverse_video) {
178 ctx->set_text_fg_default_bright(ctx);
179 } else {
180 ctx->set_text_bg_default_bright(ctx);
181 }
182 }
183 continue;
184 }
185
186 else if (ctx->esc_values[i] == 2
187 || ctx->esc_values[i] == 3
188 || ctx->esc_values[i] == 4
189 || ctx->esc_values[i] == 8) {
190 continue;
191 }
192
193 else if (ctx->esc_values[i] == 5) {
194 ctx->bg_bold = true;
195 if (ctx->current_bg == (size_t)-2) {
196 // RGB/256-color; bold does not alter the colour
197 } else if (ctx->current_bg != (size_t)-1) {
198 if (!ctx->reverse_video) {
199 ctx->set_text_bg_bright(ctx, ctx->current_bg);
200 } else {
201 ctx->set_text_fg_bright(ctx, ctx->current_bg);
202 }
203 } else {
204 if (!ctx->reverse_video) {
205 ctx->set_text_bg_default_bright(ctx);
206 } else {
207 ctx->set_text_fg_default_bright(ctx);
208 }
209 }
210 continue;
211 }
212
213 else if (ctx->esc_values[i] == 22) {
214 ctx->bold = false;
215 if (ctx->current_primary == (size_t)-2) {
216 // RGB/256-color; unbold does not alter the colour
217 } else if (ctx->current_primary != (size_t)-1) {
218 if (!ctx->reverse_video) {
219 ctx->set_text_fg(ctx, ctx->current_primary);
220 } else {
221 ctx->set_text_bg(ctx, ctx->current_primary);
222 }
223 } else {
224 if (!ctx->reverse_video) {
225 ctx->set_text_fg_default(ctx);
226 } else {
227 ctx->set_text_bg_default(ctx);
228 }
229 }
230 continue;
231 }
232
233 else if (ctx->esc_values[i] == 23
234 || ctx->esc_values[i] == 24
235 || ctx->esc_values[i] == 28) {
236 continue;
237 }
238
239 else if (ctx->esc_values[i] == 25) {
240 ctx->bg_bold = false;
241 if (ctx->current_bg == (size_t)-2) {
242 // RGB/256-color; unbold does not alter the colour
243 } else if (ctx->current_bg != (size_t)-1) {
244 if (!ctx->reverse_video) {
245 ctx->set_text_bg(ctx, ctx->current_bg);
246 } else {
247 ctx->set_text_fg(ctx, ctx->current_bg);
248 }
249 } else {
250 if (!ctx->reverse_video) {
251 ctx->set_text_bg_default(ctx);
252 } else {
253 ctx->set_text_fg_default(ctx);
254 }
255 }
256 continue;
257 }
258
259 else if (ctx->esc_values[i] >= 30 && ctx->esc_values[i] <= 37) {
260 offset = 30;
261 ctx->current_primary = ctx->esc_values[i] - offset;
262
263 if (ctx->reverse_video) {
264 goto set_bg;
265 }
266
267set_fg:
268 if ((ctx->bold && !ctx->reverse_video)
269 || (ctx->bg_bold && ctx->reverse_video)) {
270 ctx->set_text_fg_bright(ctx, ctx->esc_values[i] - offset);
271 } else {
272 ctx->set_text_fg(ctx, ctx->esc_values[i] - offset);
273 }
274 continue;
275 }
276
277 else if (ctx->esc_values[i] >= 40 && ctx->esc_values[i] <= 47) {
278 offset = 40;
279 ctx->current_bg = ctx->esc_values[i] - offset;
280
281 if (ctx->reverse_video) {
282 goto set_fg;
283 }
284
285set_bg:
286 if ((ctx->bold && ctx->reverse_video)
287 || (ctx->bg_bold && !ctx->reverse_video)) {
288 ctx->set_text_bg_bright(ctx, ctx->esc_values[i] - offset);
289 } else {
290 ctx->set_text_bg(ctx, ctx->esc_values[i] - offset);
291 }
292 continue;
293 }
294
295 else if (ctx->esc_values[i] >= 90 && ctx->esc_values[i] <= 97) {
296 offset = 90;
297 ctx->current_primary = ctx->esc_values[i] - offset;
298
299 if (ctx->reverse_video) {
300 goto set_bg_bright;
301 }
302
303set_fg_bright:
304 ctx->set_text_fg_bright(ctx, ctx->esc_values[i] - offset);
305 continue;
306 }
307
308 else if (ctx->esc_values[i] >= 100 && ctx->esc_values[i] <= 107) {
309 offset = 100;
310 ctx->current_bg = ctx->esc_values[i] - offset;
311
312 if (ctx->reverse_video) {
313 goto set_fg_bright;
314 }
315
316set_bg_bright:
317 ctx->set_text_bg_bright(ctx, ctx->esc_values[i] - offset);
318 continue;
319 }
320
321 else if (ctx->esc_values[i] == 39) {
322 ctx->current_primary = (size_t)-1;
323
324 if (ctx->reverse_video) {
325 ctx->swap_palette(ctx);
326 }
327
328 if (!ctx->bold) {
329 ctx->set_text_fg_default(ctx);
330 } else {
331 ctx->set_text_fg_default_bright(ctx);
332 }
333
334 if (ctx->reverse_video) {
335 ctx->swap_palette(ctx);
336 }
337
338 continue;
339 }
340
341 else if (ctx->esc_values[i] == 49) {
342 ctx->current_bg = (size_t)-1;
343
344 if (ctx->reverse_video) {
345 ctx->swap_palette(ctx);
346 }
347
348 if (!ctx->bg_bold) {
349 ctx->set_text_bg_default(ctx);
350 } else {
351 ctx->set_text_bg_default_bright(ctx);
352 }
353
354 if (ctx->reverse_video) {
355 ctx->swap_palette(ctx);
356 }
357
358 continue;
359 }
360
361 else if (ctx->esc_values[i] == 7) {
362 if (!ctx->reverse_video) {
363 ctx->reverse_video = true;
364 ctx->swap_palette(ctx);
365 }
366 continue;
367 }
368
369 else if (ctx->esc_values[i] == 27) {
370 if (ctx->reverse_video) {
371 ctx->reverse_video = false;
372 ctx->swap_palette(ctx);
373 }
374 continue;
375 }
376
377 // 256/RGB
378 else if (ctx->esc_values[i] == 38 || ctx->esc_values[i] == 48) {
379 bool fg = ctx->esc_values[i] == 38;
380 bool render_fg = ctx->reverse_video ? !fg : fg;
381
382 i++;
383 if (i >= ctx->esc_values_i) {
384 break;
385 }
386
387 switch (ctx->esc_values[i]) {
388 case 2: { // RGB
389 if (i + 3 >= ctx->esc_values_i) {
390 goto out;
391 }
392
393 uint32_t rgb_value = 0;
394
395 rgb_value |= (ctx->esc_values[i + 1] & 0xff) << 16;
396 rgb_value |= (ctx->esc_values[i + 2] & 0xff) << 8;
397 rgb_value |= (ctx->esc_values[i + 3] & 0xff);
398
399 i += 3;
400
401 if (fg) {
402 ctx->current_primary = (size_t)-2;
403 } else {
404 ctx->current_bg = (size_t)-2;
405 }
406
407 (render_fg ? ctx->set_text_fg_rgb : ctx->set_text_bg_rgb)(ctx, rgb_value);
408
409 break;
410 }
411 case 5: { // 256 colors
412 if (i + 1 >= ctx->esc_values_i) {
413 goto out;
414 }
415
416 uint32_t col = ctx->esc_values[i + 1];
417
418 i++;
419
420 if (col < 8) {
421 if (fg) {
422 ctx->current_primary = (size_t)-2;
423 } else {
424 ctx->current_bg = (size_t)-2;
425 }
426 (render_fg ? ctx->set_text_fg : ctx->set_text_bg)(ctx, col);
427 } else if (col < 16) {
428 if (fg) {
429 ctx->current_primary = (size_t)-2;
430 } else {
431 ctx->current_bg = (size_t)-2;
432 }
433 (render_fg ? ctx->set_text_fg_bright : ctx->set_text_bg_bright)(ctx, col - 8);
434 } else if (col < 256) {
435 if (fg) {
436 ctx->current_primary = (size_t)-2;
437 } else {
438 ctx->current_bg = (size_t)-2;
439 }
440 uint32_t rgb_value = col256[col - 16];
441 (render_fg ? ctx->set_text_fg_rgb : ctx->set_text_bg_rgb)(ctx, rgb_value);
442 }
443
444 break;
445 }
446 default: continue;
447 }
448 }
449 }
450
451out:;
452}
453
454static void save_state(struct flanterm_context *ctx);
455static void restore_state(struct flanterm_context *ctx);
456
457static void dec_private_parse(struct flanterm_context *ctx, uint8_t c) {
458 ctx->dec_private = false;
459
460 if (ctx->esc_values_i == 0) {
461 return;
462 }
463
464 bool set;
465
466 switch (c) {
467 case 'h':
468 set = true; break;
469 case 'l':
470 set = false; break;
471 default:
472 return;
473 }
474
475 for (size_t i = 0; i < ctx->esc_values_i; i++) {
476 switch (ctx->esc_values[i]) {
477 case 6:
478 ctx->origin_mode = set;
479 ctx->set_cursor_pos(ctx, 0, set ? ctx->scroll_top_margin : 0);
480 break;
481 case 7:
482 ctx->wrap_enabled = set;
483 break;
484 case 25:
485 ctx->cursor_enabled = set;
486 break;
487 case 1049:
488 if (set) {
489 save_state(ctx);
490 ctx->clear(ctx, true);
491 } else {
492 ctx->clear(ctx, true);
493 restore_state(ctx);
494 }
495 break;
496 }
497 }
498
499 if (ctx->callback != NULL) {
500 ctx->callback(ctx, FLANTERM_CB_DEC, ctx->esc_values_i, (uintptr_t)ctx->esc_values, c);
501 }
502}
503
504static void linux_private_parse(struct flanterm_context *ctx) {
505 if (ctx->esc_values_i == 0) {
506 return;
507 }
508
509 if (ctx->callback != NULL) {
510 ctx->callback(ctx, FLANTERM_CB_LINUX, ctx->esc_values_i, (uintptr_t)ctx->esc_values, 0);
511 }
512}
513
514static void mode_toggle(struct flanterm_context *ctx, uint8_t c) {
515 if (ctx->esc_values_i == 0) {
516 return;
517 }
518
519 bool set;
520
521 switch (c) {
522 case 'h':
523 set = true; break;
524 case 'l':
525 set = false; break;
526 default:
527 return;
528 }
529
530 switch (ctx->esc_values[0]) {
531 case 4:
532 ctx->insert_mode = set; return;
533 }
534
535 if (ctx->callback != NULL) {
536 ctx->callback(ctx, FLANTERM_CB_MODE, ctx->esc_values_i, (uintptr_t)ctx->esc_values, c);
537 }
538}
539
540static void osc_finalize(struct flanterm_context *ctx) {
541 if (ctx->callback != NULL) {
542 // Parse the leading OSC number and skip past the semicolon.
543 uint64_t osc_num = 0;
544 size_t i = 0;
545 bool overflow = false;
546 while (i < ctx->osc_buf_i && ctx->osc_buf[i] >= '0' && ctx->osc_buf[i] <= '9') {
547 uint64_t digit = (uint64_t)(ctx->osc_buf[i] - '0');
548 if (osc_num > UINT64_MAX / 10
549 || (osc_num == UINT64_MAX / 10 && digit > UINT64_MAX % 10)) {
550 overflow = true;
551 break;
552 }
553 osc_num = osc_num * 10 + digit;
554 i++;
555 }
556 if (overflow) {
557 return;
558 }
559 if (i < ctx->osc_buf_i && ctx->osc_buf[i] == ';') {
560 i++;
561 }
562 ctx->callback(ctx, FLANTERM_CB_OSC, osc_num, ctx->osc_buf_i - i, (uintptr_t)&ctx->osc_buf[i]);
563 }
564}
565
566static bool osc_parse(struct flanterm_context *ctx, uint8_t c) {
567 // ESC \ terminates an OSC sequence cleanly
568 // but if ESC is followed by non-\, report failure from osc_parse and
569 // try parsing the character as another escape code
570 if (ctx->osc_escape) {
571 if (c == '\\') {
572 osc_finalize(ctx);
573 ctx->osc = false;
574 ctx->osc_escape = false;
575 ctx->escape = false;
576 return true;
577 } else {
578 ctx->osc_escape = false;
579 ctx->osc = false;
580 // escape stays true here
581 return false;
582 }
583 }
584 switch (c) {
585 case 0x1b:
586 ctx->osc_escape = true;
587 break;
588 // BEL is the other terminator
589 case '\a':
590 osc_finalize(ctx);
591 ctx->osc_escape = false;
592 ctx->osc = false;
593 ctx->escape = false;
594 break;
595 default:
596 if (ctx->osc_buf_i < sizeof(ctx->osc_buf)) {
597 ctx->osc_buf[ctx->osc_buf_i++] = c;
598 }
599 break;
600 }
601 return true;
602}
603
604static bool execute_c0(struct flanterm_context *ctx, uint8_t c) {
605 size_t x, y;
606 switch (c) {
607 case '\a':
608 if (ctx->callback != NULL) {
609 ctx->callback(ctx, FLANTERM_CB_BELL, 0, 0, 0);
610 }
611 return true;
612 case '\b':
613 ctx->get_cursor_pos(ctx, &x, &y);
614 if (x > 0) {
615 ctx->set_cursor_pos(ctx, x - 1, y);
616 }
617 return true;
618 case '\t':
619 ctx->get_cursor_pos(ctx, &x, &y);
620 x = (x / ctx->tab_size + 1) * ctx->tab_size;
621 if (x >= ctx->cols) {
622 x = ctx->cols - 1;
623 }
624 ctx->set_cursor_pos(ctx, x, y);
625 return true;
626 case 0x0b:
627 case 0x0c:
628 case '\n':
629 ctx->get_cursor_pos(ctx, &x, &y);
630 if (y == ctx->scroll_bottom_margin - 1) {
631 ctx->scroll(ctx);
632 ctx->set_cursor_pos(ctx, x, y);
633 } else if (y < ctx->rows - 1) {
634 ctx->set_cursor_pos(ctx, x, y + 1);
635 }
636 return true;
637 case '\r':
638 ctx->get_cursor_pos(ctx, &x, &y);
639 ctx->set_cursor_pos(ctx, 0, y);
640 return true;
641 case 14:
642 ctx->current_charset = 1;
643 return true;
644 case 15:
645 ctx->current_charset = 0;
646 return true;
647 default:
648 return false;
649 }
650}
651
652static void control_sequence_parse(struct flanterm_context *ctx, uint8_t c) {
653 if (ctx->escape_offset == 2) {
654 switch (c) {
655 case '[':
656 ctx->discard_next = true;
657 goto cleanup;
658 case '?':
659 ctx->dec_private = true;
660 return;
661 }
662 }
663
664 if (c < 0x20 && c != 0x1b) {
665 execute_c0(ctx, c);
666 return;
667 }
668
669 if (c >= '0' && c <= '9') {
670 if (ctx->esc_values_i == FLANTERM_MAX_ESC_VALUES) {
671 return;
672 }
673 ctx->rrr = true;
674 if (ctx->esc_values[ctx->esc_values_i] > UINT32_MAX / 10) {
675 return;
676 }
677 ctx->esc_values[ctx->esc_values_i] *= 10;
678 uint32_t digit = c - '0';
679 if (ctx->esc_values[ctx->esc_values_i] > UINT32_MAX - digit) {
680 ctx->esc_values[ctx->esc_values_i] = UINT32_MAX;
681 return;
682 }
683 ctx->esc_values[ctx->esc_values_i] += digit;
684 return;
685 }
686
687 if (ctx->rrr == true) {
688 ctx->esc_values_i++;
689 ctx->rrr = false;
690 if (c == ';')
691 return;
692 } else if (c == ';') {
693 if (ctx->esc_values_i == FLANTERM_MAX_ESC_VALUES) {
694 return;
695 }
696 ctx->esc_values[ctx->esc_values_i] = 0;
697 ctx->esc_values_i++;
698 return;
699 }
700
701 size_t esc_default;
702 switch (c) {
703 case 'J': case 'K': case 'q': case 'm': case 'c': case ']':
704 esc_default = 0; break;
705 default:
706 esc_default = 1; break;
707 }
708
709 for (size_t i = ctx->esc_values_i; i < FLANTERM_MAX_ESC_VALUES; i++) {
710 ctx->esc_values[i] = esc_default;
711 }
712
713 if (esc_default != 0) {
714 for (size_t i = 0; i < ctx->esc_values_i; i++) {
715 if (ctx->esc_values[i] == 0) {
716 ctx->esc_values[i] = esc_default;
717 }
718 }
719 }
720
721 if (ctx->dec_private == true) {
722 // Intermediate bytes (0x20-0x2F) don't terminate the CSI; they
723 // just mean we don't recognize the sequence (e.g. DECRQM "CSI ? Ps $ p").
724 // Wait for the real final byte and drop it.
725 if (c >= 0x20 && c <= 0x2F) {
726 ctx->dec_private = false;
727 ctx->csi_unhandled = true;
728 return;
729 }
730 dec_private_parse(ctx, c);
731 goto cleanup;
732 }
733
734 // CSI sequences are terminated by a byte in [0x40,0x7E]
735 // so skip all bytes until the terminator byte
736 if (ctx->csi_unhandled) {
737 if (c == 0x1B) {
738 ctx->csi_unhandled = false;
739 ctx->control_sequence = false;
740 ctx->escape_offset = 0;
741 return;
742 }
743 if (c >= 0x40 && c <= 0x7E) {
744 ctx->csi_unhandled = false;
745 goto cleanup;
746 }
747 return;
748 }
749
750 bool r = ctx->scroll_enabled;
751 ctx->scroll_enabled = false;
752 bool saved_wrap = ctx->wrap_enabled;
753 ctx->wrap_enabled = true;
754 size_t x, y;
755 ctx->get_cursor_pos(ctx, &x, &y);
756
757 switch (c) {
758 // ESC aborts the current CSI and starts a new escape sequence
759 case 0x1B:
760 ctx->scroll_enabled = r;
761 ctx->wrap_enabled = saved_wrap;
762 ctx->control_sequence = false;
763 ctx->escape_offset = 0;
764 return;
765 case 'F':
766 x = 0;
767 // FALLTHRU
768 case 'A': {
769 if (ctx->esc_values[0] > y)
770 ctx->esc_values[0] = y;
771 size_t dest_y = y - ctx->esc_values[0];
772 size_t min_y = ctx->origin_mode ? ctx->scroll_top_margin : 0;
773 if (dest_y < min_y) {
774 dest_y = min_y;
775 }
776 ctx->set_cursor_pos(ctx, x, dest_y);
777 break;
778 }
779 case 'E':
780 x = 0;
781 // FALLTHRU
782 case 'e':
783 case 'B': {
784 if (y + ctx->esc_values[0] > ctx->rows - 1)
785 ctx->esc_values[0] = (ctx->rows - 1) - y;
786 size_t dest_y = y + ctx->esc_values[0];
787 size_t max_y = ctx->origin_mode ? ctx->scroll_bottom_margin : ctx->rows;
788 if (dest_y >= max_y) {
789 dest_y = max_y - 1;
790 }
791 ctx->set_cursor_pos(ctx, x, dest_y);
792 break;
793 }
794 case 'a':
795 case 'C':
796 if (x + ctx->esc_values[0] > ctx->cols - 1)
797 ctx->esc_values[0] = (ctx->cols - 1) - x;
798 ctx->set_cursor_pos(ctx, x + ctx->esc_values[0], y);
799 break;
800 case 'D':
801 if (ctx->esc_values[0] > x)
802 ctx->esc_values[0] = x;
803 ctx->set_cursor_pos(ctx, x - ctx->esc_values[0], y);
804 break;
805 case 'c':
806 if (ctx->callback != NULL) {
807 ctx->callback(ctx, FLANTERM_CB_PRIVATE_ID, 0, 0, 0);
808 }
809 break;
810 case 'd': {
811 if (ctx->esc_values[0] != 0) {
812 ctx->esc_values[0]--;
813 }
814 size_t max_row = ctx->rows;
815 size_t row_offset = 0;
816 if (ctx->origin_mode) {
817 max_row = ctx->scroll_bottom_margin - ctx->scroll_top_margin;
818 row_offset = ctx->scroll_top_margin;
819 }
820 if (ctx->esc_values[0] >= max_row)
821 ctx->esc_values[0] = max_row - 1;
822 ctx->set_cursor_pos(ctx, x, ctx->esc_values[0] + row_offset);
823 break;
824 }
825 case 'G':
826 case '`':
827 if (ctx->esc_values[0] != 0) {
828 ctx->esc_values[0]--;
829 }
830 if (ctx->esc_values[0] >= ctx->cols)
831 ctx->esc_values[0] = ctx->cols - 1;
832 ctx->set_cursor_pos(ctx, ctx->esc_values[0], y);
833 break;
834 case 'H':
835 case 'f': {
836 if (ctx->esc_values[0] != 0) {
837 ctx->esc_values[0]--;
838 }
839 if (ctx->esc_values[1] != 0) {
840 ctx->esc_values[1]--;
841 }
842 size_t max_row = ctx->rows;
843 size_t row_offset = 0;
844 if (ctx->origin_mode) {
845 max_row = ctx->scroll_bottom_margin - ctx->scroll_top_margin;
846 row_offset = ctx->scroll_top_margin;
847 }
848 if (ctx->esc_values[1] >= ctx->cols) {
849 ctx->esc_values[1] = ctx->cols - 1;
850 }
851 if (ctx->esc_values[0] >= max_row) {
852 ctx->esc_values[0] = max_row - 1;
853 }
854 ctx->set_cursor_pos(ctx, ctx->esc_values[1], ctx->esc_values[0] + row_offset);
855 break;
856 }
857 case 'M': {
858 if (y < ctx->scroll_top_margin || y >= ctx->scroll_bottom_margin) {
859 break;
860 }
861 size_t old_scroll_top_margin = ctx->scroll_top_margin;
862 ctx->scroll_top_margin = y;
863 size_t max_count = ctx->scroll_bottom_margin - y;
864 size_t count = ctx->esc_values[0] > max_count ? max_count : ctx->esc_values[0];
865 for (size_t i = 0; i < count; i++) {
866 ctx->scroll(ctx);
867 }
868 ctx->scroll_top_margin = old_scroll_top_margin;
869 break;
870 }
871 case 'L': {
872 if (y < ctx->scroll_top_margin || y >= ctx->scroll_bottom_margin) {
873 break;
874 }
875 size_t old_scroll_top_margin = ctx->scroll_top_margin;
876 ctx->scroll_top_margin = y;
877 size_t max_count = ctx->scroll_bottom_margin - y;
878 size_t count = ctx->esc_values[0] > max_count ? max_count : ctx->esc_values[0];
879 for (size_t i = 0; i < count; i++) {
880 ctx->revscroll(ctx);
881 }
882 ctx->scroll_top_margin = old_scroll_top_margin;
883 break;
884 }
885 case 'n':
886 switch (ctx->esc_values[0]) {
887 case 5:
888 if (ctx->callback != NULL) {
889 ctx->callback(ctx, FLANTERM_CB_STATUS_REPORT, 0, 0, 0);
890 }
891 break;
892 case 6:
893 if (ctx->callback != NULL) {
894 size_t report_y = ctx->origin_mode && y >= ctx->scroll_top_margin
895 ? y - ctx->scroll_top_margin : y;
896 ctx->callback(ctx, FLANTERM_CB_POS_REPORT, x + 1, report_y + 1, 0);
897 }
898 break;
899 }
900 break;
901 case 'q':
902 if (ctx->callback != NULL) {
903 ctx->callback(ctx, FLANTERM_CB_KBD_LEDS, ctx->esc_values[0], 0, 0);
904 }
905 break;
906 case 'J':
907 switch (ctx->esc_values[0]) {
908 case 0: {
909 // Erase from cursor to end: clear rest of current line,
910 // then clear full lines below, using explicit cursor
911 // positioning to avoid scroll region wrapping limits.
912 ctx->set_cursor_pos(ctx, x, y);
913 for (size_t xc = x; xc < ctx->cols; xc++) {
914 ctx->raw_putchar(ctx, ' ');
915 }
916 for (size_t yc = y + 1; yc < ctx->rows; yc++) {
917 ctx->set_cursor_pos(ctx, 0, yc);
918 for (size_t xc = 0; xc < ctx->cols; xc++) {
919 ctx->raw_putchar(ctx, ' ');
920 }
921 }
922 ctx->set_cursor_pos(ctx, x, y);
923 break;
924 }
925 case 1: {
926 // Erase from start to cursor: clear full lines above,
927 // then clear current line up to and including cursor.
928 for (size_t yc = 0; yc < y; yc++) {
929 ctx->set_cursor_pos(ctx, 0, yc);
930 for (size_t xc = 0; xc < ctx->cols; xc++) {
931 ctx->raw_putchar(ctx, ' ');
932 }
933 }
934 ctx->set_cursor_pos(ctx, 0, y);
935 for (size_t xc = 0; xc <= x; xc++) {
936 ctx->raw_putchar(ctx, ' ');
937 }
938 ctx->set_cursor_pos(ctx, x, y);
939 break;
940 }
941 case 2:
942 case 3:
943 ctx->clear(ctx, false);
944 break;
945 }
946 break;
947 case '@': {
948 size_t n = ctx->esc_values[0];
949 if (n == 0) {
950 break;
951 }
952 if (n > ctx->cols - x) {
953 n = ctx->cols - x;
954 }
955 for (size_t i = ctx->cols - 1; i >= x + n; i--) {
956 ctx->move_character(ctx, i, y, i - n, y);
957 }
958 ctx->set_cursor_pos(ctx, x, y);
959 for (size_t i = 0; i < n; i++) {
960 ctx->raw_putchar(ctx, ' ');
961 }
962 ctx->set_cursor_pos(ctx, x, y);
963 break;
964 }
965 case 'P':
966 if (ctx->esc_values[0] > ctx->cols - x)
967 ctx->esc_values[0] = ctx->cols - x;
968 for (size_t i = x + ctx->esc_values[0]; i < ctx->cols; i++)
969 ctx->move_character(ctx, i - ctx->esc_values[0], y, i, y);
970 ctx->set_cursor_pos(ctx, ctx->cols - ctx->esc_values[0], y);
971 // FALLTHRU
972 case 'X': {
973 size_t cx, cy;
974 ctx->get_cursor_pos(ctx, &cx, &cy);
975 ctx->set_cursor_pos(ctx, cx, cy);
976 size_t remaining = ctx->cols - cx;
977 size_t count = ctx->esc_values[0] > remaining ? remaining : ctx->esc_values[0];
978 for (size_t i = 0; i < count; i++)
979 ctx->raw_putchar(ctx, ' ');
980 ctx->set_cursor_pos(ctx, x, y);
981 break;
982 }
983 case 'm':
984 sgr(ctx);
985 break;
986 case 's':
987 ctx->get_cursor_pos(ctx, &ctx->saved_cursor_x, &ctx->saved_cursor_y);
988 break;
989 case 'u':
990 ctx->set_cursor_pos(ctx, ctx->saved_cursor_x, ctx->saved_cursor_y);
991 break;
992 case 'K':
993 switch (ctx->esc_values[0]) {
994 case 0: {
995 ctx->set_cursor_pos(ctx, x, y);
996 for (size_t i = x; i < ctx->cols; i++)
997 ctx->raw_putchar(ctx, ' ');
998 ctx->set_cursor_pos(ctx, x, y);
999 break;
1000 }
1001 case 1: {
1002 ctx->set_cursor_pos(ctx, 0, y);
1003 for (size_t i = 0; i <= x; i++)
1004 ctx->raw_putchar(ctx, ' ');
1005 ctx->set_cursor_pos(ctx, x, y);
1006 break;
1007 }
1008 case 2: {
1009 ctx->set_cursor_pos(ctx, 0, y);
1010 for (size_t i = 0; i < ctx->cols; i++)
1011 ctx->raw_putchar(ctx, ' ');
1012 ctx->set_cursor_pos(ctx, x, y);
1013 break;
1014 }
1015 }
1016 break;
1017 case 'r':
1018 ctx->scroll_top_margin = 0;
1019 ctx->scroll_bottom_margin = ctx->rows;
1020 if (ctx->esc_values_i > 0) {
1021 ctx->scroll_top_margin = ctx->esc_values[0] - 1;
1022 }
1023 if (ctx->esc_values_i > 1) {
1024 ctx->scroll_bottom_margin = ctx->esc_values[1];
1025 }
1026 if (ctx->scroll_top_margin >= ctx->rows
1027 || ctx->scroll_bottom_margin > ctx->rows
1028 || ctx->scroll_top_margin >= (ctx->scroll_bottom_margin - 1)) {
1029 ctx->scroll_top_margin = 0;
1030 ctx->scroll_bottom_margin = ctx->rows;
1031 }
1032 ctx->set_cursor_pos(ctx, 0, ctx->origin_mode ? ctx->scroll_top_margin : 0);
1033 break;
1034 case 'l':
1035 case 'h':
1036 mode_toggle(ctx, c);
1037 break;
1038 case 'S': {
1039 size_t region = ctx->scroll_bottom_margin - ctx->scroll_top_margin;
1040 size_t count = ctx->esc_values[0] > region ? region : ctx->esc_values[0];
1041 for (size_t i = 0; i < count; i++) {
1042 ctx->scroll(ctx);
1043 }
1044 break;
1045 }
1046 case 'T': {
1047 size_t region = ctx->scroll_bottom_margin - ctx->scroll_top_margin;
1048 size_t count = ctx->esc_values[0] > region ? region : ctx->esc_values[0];
1049 for (size_t i = 0; i < count; i++) {
1050 ctx->revscroll(ctx);
1051 }
1052 break;
1053 }
1054 case 'b': {
1055 if (!ctx->last_was_graphic) {
1056 break;
1057 }
1058 ctx->scroll_enabled = r;
1059 ctx->wrap_enabled = saved_wrap;
1060 size_t count = ctx->esc_values[0] > ctx->cols ? ctx->cols : ctx->esc_values[0];
1061 for (size_t i = 0; i < count; i++) {
1062 if (ctx->insert_mode == true) {
1063 size_t ix, iy;
1064 ctx->get_cursor_pos(ctx, &ix, &iy);
1065 for (size_t j = ctx->cols - 1; j > ix; j--) {
1066 ctx->move_character(ctx, j, iy, j - 1, iy);
1067 }
1068 }
1069 ctx->raw_putchar(ctx, ctx->last_printed_char);
1070 }
1071 break;
1072 }
1073 case ']':
1074 linux_private_parse(ctx);
1075 break;
1076 default:
1077 if (c >= 0x40 && c <= 0x7E) {
1078 break;
1079 }
1080 ctx->scroll_enabled = r;
1081 ctx->wrap_enabled = saved_wrap;
1082 ctx->csi_unhandled = true;
1083 return;
1084 }
1085
1086 ctx->scroll_enabled = r;
1087 ctx->wrap_enabled = saved_wrap;
1088
1089cleanup:
1090 ctx->control_sequence = false;
1091 ctx->escape = false;
1092}
1093
1094static void restore_state(struct flanterm_context *ctx) {
1095 ctx->bold = ctx->saved_state_bold;
1096 ctx->bg_bold = ctx->saved_state_bg_bold;
1097 ctx->reverse_video = ctx->saved_state_reverse_video;
1098 ctx->origin_mode = ctx->saved_state_origin_mode;
1099 ctx->current_charset = ctx->saved_state_current_charset;
1100 ctx->charsets[0] = ctx->saved_state_charsets[0];
1101 ctx->charsets[1] = ctx->saved_state_charsets[1];
1102 ctx->current_primary = ctx->saved_state_current_primary;
1103 ctx->current_bg = ctx->saved_state_current_bg;
1104
1105 ctx->restore_state(ctx);
1106}
1107
1108static void save_state(struct flanterm_context *ctx) {
1109 ctx->save_state(ctx);
1110
1111 ctx->saved_state_bold = ctx->bold;
1112 ctx->saved_state_bg_bold = ctx->bg_bold;
1113 ctx->saved_state_reverse_video = ctx->reverse_video;
1114 ctx->saved_state_origin_mode = ctx->origin_mode;
1115 ctx->saved_state_current_charset = ctx->current_charset;
1116 ctx->saved_state_charsets[0] = ctx->charsets[0];
1117 ctx->saved_state_charsets[1] = ctx->charsets[1];
1118 ctx->saved_state_current_primary = ctx->current_primary;
1119 ctx->saved_state_current_bg = ctx->current_bg;
1120}
1121
1122static void escape_parse(struct flanterm_context *ctx, uint8_t c) {
1123 ctx->escape_offset++;
1124
1125 if (ctx->osc == true) {
1126 // ESC \ is one of the two possible terminators of OSC sequences,
1127 // so osc_parse consumes ESC.
1128 // If it is then followed by \ it cleans correctly,
1129 // otherwise it returns false, and it tries parsing it as another escape sequence
1130 if (osc_parse(ctx, c)) {
1131 return;
1132 }
1133 // OSC aborted by ESC + non-backslash; reset offset for new sequence
1134 ctx->escape_offset = 1;
1135 }
1136
1137 if (ctx->control_sequence == true) {
1138 control_sequence_parse(ctx, c);
1139 return;
1140 }
1141
1142 size_t x, y;
1143 ctx->get_cursor_pos(ctx, &x, &y);
1144
1145 switch (c) {
1146 case 0x1b:
1147 ctx->escape_offset = 0;
1148 return;
1149 case ']':
1150 ctx->osc_escape = false;
1151 ctx->osc = true;
1152 ctx->osc_buf_i = 0;
1153 return;
1154 case '[':
1155 for (size_t i = 0; i < FLANTERM_MAX_ESC_VALUES; i++)
1156 ctx->esc_values[i] = 0;
1157 ctx->esc_values_i = 0;
1158 ctx->rrr = false;
1159 ctx->csi_unhandled = false;
1160 ctx->control_sequence = true;
1161 return;
1162 case '7':
1163 save_state(ctx);
1164 break;
1165 case '8':
1166 restore_state(ctx);
1167 break;
1168 case 'c':
1169 if (ctx->reverse_video) {
1170 ctx->swap_palette(ctx);
1171 }
1172 flanterm_context_reinit(ctx);
1173 ctx->set_text_bg_default(ctx);
1174 ctx->set_text_fg_default(ctx);
1175 ctx->clear(ctx, true);
1176 save_state(ctx);
1177 break;
1178 case 'D':
1179 if (y == ctx->scroll_bottom_margin - 1) {
1180 ctx->scroll(ctx);
1181 ctx->set_cursor_pos(ctx, x, y);
1182 } else if (y < ctx->rows - 1) {
1183 ctx->set_cursor_pos(ctx, x, y + 1);
1184 }
1185 break;
1186 case 'E':
1187 if (y == ctx->scroll_bottom_margin - 1) {
1188 ctx->scroll(ctx);
1189 ctx->set_cursor_pos(ctx, 0, y);
1190 } else if (y < ctx->rows - 1) {
1191 ctx->set_cursor_pos(ctx, 0, y + 1);
1192 } else {
1193 ctx->set_cursor_pos(ctx, 0, y);
1194 }
1195 break;
1196 case 'M':
1197 // "Reverse linefeed"
1198 if (y == ctx->scroll_top_margin) {
1199 ctx->revscroll(ctx);
1200 ctx->set_cursor_pos(ctx, x, y);
1201 } else if (y > 0) {
1202 ctx->set_cursor_pos(ctx, x, y - 1);
1203 }
1204 break;
1205 case 'Z':
1206 if (ctx->callback != NULL) {
1207 ctx->callback(ctx, FLANTERM_CB_PRIVATE_ID, 0, 0, 0);
1208 }
1209 break;
1210 case '(':
1211 case ')':
1212 ctx->g_select = c - '\'';
1213 break;
1214 }
1215
1216 ctx->escape = false;
1217}
1218
1219static bool dec_special_print(struct flanterm_context *ctx, uint8_t c) {
1220#define FLANTERM_DEC_SPCL_PRN(C) ctx->last_printed_char = (C); ctx->last_was_graphic = true; ctx->raw_putchar(ctx, (C)); return true;
1221 switch (c) {
1222 case '`': FLANTERM_DEC_SPCL_PRN(0x04)
1223 case '0': FLANTERM_DEC_SPCL_PRN(0xdb)
1224 case '-': FLANTERM_DEC_SPCL_PRN(0x18)
1225 case ',': FLANTERM_DEC_SPCL_PRN(0x1b)
1226 case '.': FLANTERM_DEC_SPCL_PRN(0x19)
1227 case 'a': FLANTERM_DEC_SPCL_PRN(0xb1)
1228 case 'f': FLANTERM_DEC_SPCL_PRN(0xf8)
1229 case 'g': FLANTERM_DEC_SPCL_PRN(0xf1)
1230 case 'h': FLANTERM_DEC_SPCL_PRN(0xb0)
1231 case 'j': FLANTERM_DEC_SPCL_PRN(0xd9)
1232 case 'k': FLANTERM_DEC_SPCL_PRN(0xbf)
1233 case 'l': FLANTERM_DEC_SPCL_PRN(0xda)
1234 case 'm': FLANTERM_DEC_SPCL_PRN(0xc0)
1235 case 'n': FLANTERM_DEC_SPCL_PRN(0xc5)
1236 case 'q': FLANTERM_DEC_SPCL_PRN(0xc4)
1237 case 's': FLANTERM_DEC_SPCL_PRN(0x5f)
1238 case 't': FLANTERM_DEC_SPCL_PRN(0xc3)
1239 case 'u': FLANTERM_DEC_SPCL_PRN(0xb4)
1240 case 'v': FLANTERM_DEC_SPCL_PRN(0xc1)
1241 case 'w': FLANTERM_DEC_SPCL_PRN(0xc2)
1242 case 'x': FLANTERM_DEC_SPCL_PRN(0xb3)
1243 case 'y': FLANTERM_DEC_SPCL_PRN(0xf3)
1244 case 'z': FLANTERM_DEC_SPCL_PRN(0xf2)
1245 case '~': FLANTERM_DEC_SPCL_PRN(0xfa)
1246 case '_': FLANTERM_DEC_SPCL_PRN(0xff)
1247 case '+': FLANTERM_DEC_SPCL_PRN(0x1a)
1248 case '{': FLANTERM_DEC_SPCL_PRN(0xe3)
1249 case '}': FLANTERM_DEC_SPCL_PRN(0x9c)
1250 }
1251#undef FLANTERM_DEC_SPCL_PRN
1252
1253 return false;
1254}
1255
1256// Following wcwidth related code inherited from:
1257// https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
1258
1259struct interval {
1260 uint32_t first;
1261 uint32_t last;
1262};
1263
1264/* auxiliary function for binary search in interval table */
1265static int bisearch(uint32_t ucs, const struct interval *table, int max) {
1266 int min = 0;
1267 int mid;
1268
1269 if (ucs < table[0].first || ucs > table[max].last)
1270 return 0;
1271 while (max >= min) {
1272 mid = (min + max) / 2;
1273 if (ucs > table[mid].last)
1274 min = mid + 1;
1275 else if (ucs < table[mid].first)
1276 max = mid - 1;
1277 else
1278 return 1;
1279 }
1280
1281 return 0;
1282}
1283
1284static int mk_wcwidth(uint32_t ucs) {
1285 /* sorted list of non-overlapping intervals of zero-width characters */
1286 /* Unicode 17.0.0 */
1287 static const struct interval combining[] = {
1288 { 0x0300, 0x036F }, { 0x0483, 0x0489 }, { 0x0591, 0x05BD },
1289 { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, { 0x05C4, 0x05C5 },
1290 { 0x05C7, 0x05C7 }, { 0x0610, 0x061A }, { 0x061C, 0x061C },
1291 { 0x064B, 0x065F }, { 0x0670, 0x0670 }, { 0x06D6, 0x06DC },
1292 { 0x06DF, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
1293 { 0x0711, 0x0711 }, { 0x0730, 0x074A }, { 0x07A6, 0x07B0 },
1294 { 0x07EB, 0x07F3 }, { 0x07FD, 0x07FD }, { 0x0816, 0x0819 },
1295 { 0x081B, 0x0823 }, { 0x0825, 0x0827 }, { 0x0829, 0x082D },
1296 { 0x0859, 0x085B }, { 0x0897, 0x089F }, { 0x08CA, 0x08E1 },
1297 { 0x08E3, 0x0902 }, { 0x093A, 0x093A }, { 0x093C, 0x093C },
1298 { 0x0941, 0x0948 }, { 0x094D, 0x094D }, { 0x0951, 0x0957 },
1299 { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, { 0x09BC, 0x09BC },
1300 { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, { 0x09E2, 0x09E3 },
1301 { 0x09FE, 0x09FE }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },
1302 { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
1303 { 0x0A51, 0x0A51 }, { 0x0A70, 0x0A71 }, { 0x0A75, 0x0A75 },
1304 { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, { 0x0AC1, 0x0AC5 },
1305 { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, { 0x0AE2, 0x0AE3 },
1306 { 0x0AFA, 0x0AFF }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
1307 { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B44 }, { 0x0B4D, 0x0B4D },
1308 { 0x0B55, 0x0B56 }, { 0x0B62, 0x0B63 }, { 0x0B82, 0x0B82 },
1309 { 0x0BC0, 0x0BC0 }, { 0x0BCD, 0x0BCD }, { 0x0C00, 0x0C00 },
1310 { 0x0C04, 0x0C04 }, { 0x0C3C, 0x0C3C }, { 0x0C3E, 0x0C40 },
1311 { 0x0C46, 0x0C48 }, { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 },
1312 { 0x0C62, 0x0C63 }, { 0x0C81, 0x0C81 }, { 0x0CBC, 0x0CBC },
1313 { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
1314 { 0x0CE2, 0x0CE3 }, { 0x0D00, 0x0D01 }, { 0x0D3B, 0x0D3C },
1315 { 0x0D41, 0x0D44 }, { 0x0D4D, 0x0D4D }, { 0x0D62, 0x0D63 },
1316 { 0x0D81, 0x0D81 }, { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 },
1317 { 0x0DD6, 0x0DD6 }, { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A },
1318 { 0x0E47, 0x0E4E }, { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EBC },
1319 { 0x0EC8, 0x0ECE }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
1320 { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
1321 { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F8D, 0x0F97 },
1322 { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
1323 { 0x1032, 0x1037 }, { 0x1039, 0x103A }, { 0x103D, 0x103E },
1324 { 0x1058, 0x1059 }, { 0x105E, 0x1060 }, { 0x1071, 0x1074 },
1325 { 0x1082, 0x1082 }, { 0x1085, 0x1086 }, { 0x108D, 0x108D },
1326 { 0x109D, 0x109D }, { 0x1160, 0x11FF }, { 0x135D, 0x135F },
1327 { 0x1712, 0x1714 }, { 0x1732, 0x1733 }, { 0x1752, 0x1753 },
1328 { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
1329 { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
1330 { 0x180B, 0x180F }, { 0x1885, 0x1886 }, { 0x18A9, 0x18A9 },
1331 { 0x1920, 0x1922 }, { 0x1927, 0x1928 }, { 0x1932, 0x1932 },
1332 { 0x1939, 0x193B }, { 0x1A17, 0x1A18 }, { 0x1A1B, 0x1A1B },
1333 { 0x1A56, 0x1A56 }, { 0x1A58, 0x1A5E }, { 0x1A60, 0x1A60 },
1334 { 0x1A62, 0x1A62 }, { 0x1A65, 0x1A6C }, { 0x1A73, 0x1A7C },
1335 { 0x1A7F, 0x1A7F }, { 0x1AB0, 0x1ADD }, { 0x1AE0, 0x1AEB },
1336 { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, { 0x1B36, 0x1B3A },
1337 { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, { 0x1B6B, 0x1B73 },
1338 { 0x1B80, 0x1B81 }, { 0x1BA2, 0x1BA5 }, { 0x1BA8, 0x1BA9 },
1339 { 0x1BAB, 0x1BAD }, { 0x1BE6, 0x1BE6 }, { 0x1BE8, 0x1BE9 },
1340 { 0x1BED, 0x1BED }, { 0x1BEF, 0x1BF1 }, { 0x1C2C, 0x1C33 },
1341 { 0x1C36, 0x1C37 }, { 0x1CD0, 0x1CD2 }, { 0x1CD4, 0x1CE0 },
1342 { 0x1CE2, 0x1CE8 }, { 0x1CED, 0x1CED }, { 0x1CF4, 0x1CF4 },
1343 { 0x1CF8, 0x1CF9 }, { 0x1DC0, 0x1DFF }, { 0x200B, 0x200F },
1344 { 0x2028, 0x202E }, { 0x2060, 0x206F }, { 0x20D0, 0x20F0 },
1345 { 0x2CEF, 0x2CF1 }, { 0x2D7F, 0x2D7F }, { 0x2DE0, 0x2DFF },
1346 { 0x302A, 0x302D }, { 0x3099, 0x309A }, { 0x3164, 0x3164 },
1347 { 0xA66F, 0xA672 }, { 0xA674, 0xA67D }, { 0xA69E, 0xA69F },
1348 { 0xA6F0, 0xA6F1 }, { 0xA802, 0xA802 }, { 0xA806, 0xA806 },
1349 { 0xA80B, 0xA80B }, { 0xA825, 0xA826 }, { 0xA82C, 0xA82C },
1350 { 0xA8C4, 0xA8C5 }, { 0xA8E0, 0xA8F1 }, { 0xA8FF, 0xA8FF },
1351 { 0xA926, 0xA92D }, { 0xA947, 0xA951 }, { 0xA980, 0xA982 },
1352 { 0xA9B3, 0xA9B3 }, { 0xA9B6, 0xA9B9 }, { 0xA9BC, 0xA9BD },
1353 { 0xA9E5, 0xA9E5 }, { 0xAA29, 0xAA2E }, { 0xAA31, 0xAA32 },
1354 { 0xAA35, 0xAA36 }, { 0xAA43, 0xAA43 }, { 0xAA4C, 0xAA4C },
1355 { 0xAA7C, 0xAA7C }, { 0xAAB0, 0xAAB0 }, { 0xAAB2, 0xAAB4 },
1356 { 0xAAB7, 0xAAB8 }, { 0xAABE, 0xAABF }, { 0xAAC1, 0xAAC1 },
1357 { 0xAAEC, 0xAAED }, { 0xAAF6, 0xAAF6 }, { 0xABE5, 0xABE5 },
1358 { 0xABE8, 0xABE8 }, { 0xABED, 0xABED }, { 0xD7B0, 0xD7FF },
1359 { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, { 0xFE20, 0xFE2F },
1360 { 0xFEFF, 0xFEFF }, { 0xFFA0, 0xFFA0 }, { 0xFFF0, 0xFFFB },
1361 { 0x101FD, 0x101FD }, { 0x102E0, 0x102E0 }, { 0x10376, 0x1037A },
1362 { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F },
1363 { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x10AE5, 0x10AE6 },
1364 { 0x10D24, 0x10D27 }, { 0x10D69, 0x10D6D }, { 0x10EAB, 0x10EAC },
1365 { 0x10EFA, 0x10EFF }, { 0x10F46, 0x10F50 }, { 0x10F82, 0x10F85 },
1366 { 0x11001, 0x11001 }, { 0x11038, 0x11046 }, { 0x11070, 0x11070 },
1367 { 0x11073, 0x11074 }, { 0x1107F, 0x11081 }, { 0x110B3, 0x110B6 },
1368 { 0x110B9, 0x110BA }, { 0x110C2, 0x110C2 }, { 0x11100, 0x11102 },
1369 { 0x11127, 0x1112B }, { 0x1112D, 0x11134 }, { 0x11173, 0x11173 },
1370 { 0x11180, 0x11181 }, { 0x111B6, 0x111BE }, { 0x111C9, 0x111CC },
1371 { 0x111CF, 0x111CF }, { 0x1122F, 0x11231 }, { 0x11234, 0x11234 },
1372 { 0x11236, 0x11237 }, { 0x1123E, 0x1123E }, { 0x11241, 0x11241 },
1373 { 0x112DF, 0x112DF }, { 0x112E3, 0x112EA }, { 0x11300, 0x11301 },
1374 { 0x1133B, 0x1133C }, { 0x11340, 0x11340 }, { 0x11366, 0x1136C },
1375 { 0x11370, 0x11374 }, { 0x113BB, 0x113C0 }, { 0x113CE, 0x113CE },
1376 { 0x113D0, 0x113D0 }, { 0x113D2, 0x113D2 }, { 0x113E1, 0x113E2 },
1377 { 0x11438, 0x1143F }, { 0x11442, 0x11444 }, { 0x11446, 0x11446 },
1378 { 0x1145E, 0x1145E }, { 0x114B3, 0x114B8 }, { 0x114BA, 0x114BA },
1379 { 0x114BF, 0x114C0 }, { 0x114C2, 0x114C3 }, { 0x115B2, 0x115B5 },
1380 { 0x115BC, 0x115BD }, { 0x115BF, 0x115C0 }, { 0x115DC, 0x115DD },
1381 { 0x11633, 0x1163A }, { 0x1163D, 0x1163D }, { 0x1163F, 0x11640 },
1382 { 0x116AB, 0x116AB }, { 0x116AD, 0x116AD }, { 0x116B0, 0x116B5 },
1383 { 0x116B7, 0x116B7 }, { 0x1171D, 0x1171D }, { 0x1171F, 0x1171F },
1384 { 0x11722, 0x11725 }, { 0x11727, 0x1172B }, { 0x1182F, 0x11837 },
1385 { 0x11839, 0x1183A }, { 0x1193B, 0x1193C }, { 0x1193E, 0x1193E },
1386 { 0x11943, 0x11943 }, { 0x119D4, 0x119D7 }, { 0x119DA, 0x119DB },
1387 { 0x119E0, 0x119E0 }, { 0x11A01, 0x11A0A }, { 0x11A33, 0x11A38 },
1388 { 0x11A3B, 0x11A3E }, { 0x11A47, 0x11A47 }, { 0x11A51, 0x11A56 },
1389 { 0x11A59, 0x11A5B }, { 0x11A8A, 0x11A96 }, { 0x11A98, 0x11A99 },
1390 { 0x11B60, 0x11B60 }, { 0x11B62, 0x11B64 }, { 0x11B66, 0x11B66 },
1391 { 0x11C30, 0x11C36 }, { 0x11C38, 0x11C3D }, { 0x11C3F, 0x11C3F },
1392 { 0x11C92, 0x11CA7 }, { 0x11CAA, 0x11CB0 }, { 0x11CB2, 0x11CB3 },
1393 { 0x11CB5, 0x11CB6 }, { 0x11D31, 0x11D36 }, { 0x11D3A, 0x11D3A },
1394 { 0x11D3C, 0x11D3D }, { 0x11D3F, 0x11D45 }, { 0x11D47, 0x11D47 },
1395 { 0x11D90, 0x11D91 }, { 0x11D95, 0x11D95 }, { 0x11D97, 0x11D97 },
1396 { 0x11EF3, 0x11EF4 }, { 0x11F00, 0x11F01 }, { 0x11F36, 0x11F3A },
1397 { 0x11F40, 0x11F40 }, { 0x11F42, 0x11F42 }, { 0x11F5A, 0x11F5A },
1398 { 0x13430, 0x13440 }, { 0x13447, 0x13455 }, { 0x1611E, 0x16129 },
1399 { 0x1612D, 0x1612F }, { 0x16AF0, 0x16AF4 }, { 0x16B30, 0x16B36 },
1400 { 0x16F4F, 0x16F4F }, { 0x16F8F, 0x16F92 }, { 0x16FE4, 0x16FE4 },
1401 { 0x1BC9D, 0x1BC9E }, { 0x1BCA0, 0x1BCA3 }, { 0x1CF00, 0x1CF2D },
1402 { 0x1CF30, 0x1CF46 }, { 0x1D167, 0x1D169 }, { 0x1D173, 0x1D182 },
1403 { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, { 0x1D242, 0x1D244 },
1404 { 0x1DA00, 0x1DA36 }, { 0x1DA3B, 0x1DA6C }, { 0x1DA75, 0x1DA75 },
1405 { 0x1DA84, 0x1DA84 }, { 0x1DA9B, 0x1DA9F }, { 0x1DAA1, 0x1DAAF },
1406 { 0x1E000, 0x1E006 }, { 0x1E008, 0x1E018 }, { 0x1E01B, 0x1E021 },
1407 { 0x1E023, 0x1E024 }, { 0x1E026, 0x1E02A }, { 0x1E08F, 0x1E08F },
1408 { 0x1E130, 0x1E136 }, { 0x1E2AE, 0x1E2AE }, { 0x1E2EC, 0x1E2EF },
1409 { 0x1E4EC, 0x1E4EF }, { 0x1E5EE, 0x1E5EF }, { 0x1E6E3, 0x1E6E3 },
1410 { 0x1E6E6, 0x1E6E6 }, { 0x1E6EE, 0x1E6EF }, { 0x1E6F5, 0x1E6F5 },
1411 { 0x1E8D0, 0x1E8D6 }, { 0x1E944, 0x1E94A }, { 0xE0000, 0xE0FFF }
1412 };
1413
1414 /* test for 8-bit control characters */
1415 if (ucs == 0)
1416 return 0;
1417 if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
1418 return -1;
1419
1420 /* binary search in table of non-spacing characters */
1421 if (bisearch(ucs, table: combining,
1422 max: sizeof(combining) / sizeof(struct interval) - 1))
1423 return 0;
1424
1425 /* sorted list of non-overlapping intervals of wide/fullwidth characters */
1426 /* Unicode 17.0.0 - East_Asian_Width W and F */
1427 static const struct interval wide[] = {
1428 { 0x1100, 0x115F }, { 0x231A, 0x231B }, { 0x2329, 0x232A },
1429 { 0x23E9, 0x23EC }, { 0x23F0, 0x23F0 }, { 0x23F3, 0x23F3 },
1430 { 0x25FD, 0x25FE }, { 0x2614, 0x2615 }, { 0x2630, 0x2637 },
1431 { 0x2648, 0x2653 }, { 0x267F, 0x267F }, { 0x268A, 0x268F },
1432 { 0x2693, 0x2693 }, { 0x26A1, 0x26A1 }, { 0x26AA, 0x26AB },
1433 { 0x26BD, 0x26BE }, { 0x26C4, 0x26C5 }, { 0x26CE, 0x26CE },
1434 { 0x26D4, 0x26D4 }, { 0x26EA, 0x26EA }, { 0x26F2, 0x26F3 },
1435 { 0x26F5, 0x26F5 }, { 0x26FA, 0x26FA }, { 0x26FD, 0x26FD },
1436 { 0x2705, 0x2705 }, { 0x270A, 0x270B }, { 0x2728, 0x2728 },
1437 { 0x274C, 0x274C }, { 0x274E, 0x274E }, { 0x2753, 0x2755 },
1438 { 0x2757, 0x2757 }, { 0x2795, 0x2797 }, { 0x27B0, 0x27B0 },
1439 { 0x27BF, 0x27BF }, { 0x2B1B, 0x2B1C }, { 0x2B50, 0x2B50 },
1440 { 0x2B55, 0x2B55 }, { 0x2E80, 0x2E99 }, { 0x2E9B, 0x2EF3 },
1441 { 0x2F00, 0x2FD5 }, { 0x2FF0, 0x303E }, { 0x3041, 0x3096 },
1442 { 0x3099, 0x30FF }, { 0x3105, 0x312F }, { 0x3131, 0x318E },
1443 { 0x3190, 0x31E5 }, { 0x31EF, 0x321E }, { 0x3220, 0x3247 },
1444 { 0x3250, 0xA48C }, { 0xA490, 0xA4C6 }, { 0xA960, 0xA97C },
1445 { 0xAC00, 0xD7A3 }, { 0xF900, 0xFAFF }, { 0xFE10, 0xFE19 },
1446 { 0xFE30, 0xFE52 }, { 0xFE54, 0xFE66 }, { 0xFE68, 0xFE6B },
1447 { 0xFF01, 0xFF60 }, { 0xFFE0, 0xFFE6 },
1448 { 0x16FE0, 0x16FE4 }, { 0x16FF0, 0x16FF6 },
1449 { 0x17000, 0x18CD5 }, { 0x18CFF, 0x18D1E }, { 0x18D80, 0x18DF2 },
1450 { 0x1AFF0, 0x1AFF3 }, { 0x1AFF5, 0x1AFFB },
1451 { 0x1AFFD, 0x1AFFE }, { 0x1B000, 0x1B122 }, { 0x1B132, 0x1B132 },
1452 { 0x1B150, 0x1B152 }, { 0x1B155, 0x1B155 }, { 0x1B164, 0x1B167 },
1453 { 0x1B170, 0x1B2FB }, { 0x1D300, 0x1D356 }, { 0x1D360, 0x1D376 },
1454 { 0x1F004, 0x1F004 }, { 0x1F0CF, 0x1F0CF }, { 0x1F18E, 0x1F18E },
1455 { 0x1F191, 0x1F19A }, { 0x1F200, 0x1F202 }, { 0x1F210, 0x1F23B },
1456 { 0x1F240, 0x1F248 }, { 0x1F250, 0x1F251 }, { 0x1F260, 0x1F265 },
1457 { 0x1F300, 0x1F320 }, { 0x1F32D, 0x1F335 }, { 0x1F337, 0x1F37C },
1458 { 0x1F37E, 0x1F393 }, { 0x1F3A0, 0x1F3CA }, { 0x1F3CF, 0x1F3D3 },
1459 { 0x1F3E0, 0x1F3F0 }, { 0x1F3F4, 0x1F3F4 }, { 0x1F3F8, 0x1F43E },
1460 { 0x1F440, 0x1F440 }, { 0x1F442, 0x1F4FC }, { 0x1F4FF, 0x1F53D },
1461 { 0x1F54B, 0x1F54E }, { 0x1F550, 0x1F567 }, { 0x1F57A, 0x1F57A },
1462 { 0x1F595, 0x1F596 }, { 0x1F5A4, 0x1F5A4 }, { 0x1F5FB, 0x1F64F },
1463 { 0x1F680, 0x1F6C5 }, { 0x1F6CC, 0x1F6CC }, { 0x1F6D0, 0x1F6D2 },
1464 { 0x1F6D5, 0x1F6D8 }, { 0x1F6DC, 0x1F6DF }, { 0x1F6EB, 0x1F6EC },
1465 { 0x1F6F4, 0x1F6FC }, { 0x1F7E0, 0x1F7EB }, { 0x1F7F0, 0x1F7F0 },
1466 { 0x1F90C, 0x1F93A }, { 0x1F93C, 0x1F945 }, { 0x1F947, 0x1F9FF },
1467 { 0x1FA70, 0x1FA7C }, { 0x1FA80, 0x1FA8A }, { 0x1FA8E, 0x1FAC6 },
1468 { 0x1FAC8, 0x1FAC8 }, { 0x1FACD, 0x1FADC }, { 0x1FADF, 0x1FAEA },
1469 { 0x1FAEF, 0x1FAF8 }, { 0x20000, 0x2FFFD }, { 0x30000, 0x3FFFD }
1470 };
1471
1472 if (bisearch(ucs, table: wide,
1473 max: sizeof(wide) / sizeof(struct interval) - 1))
1474 return 2;
1475
1476 return 1;
1477}
1478
1479// End of https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c inherited code
1480
1481static int unicode_to_cp437(uint64_t code_point) {
1482 // Braille patterns U+2800-U+28FF: approximate using CP437 block/shade characters.
1483 // Braille dot layout (bit positions):
1484 // bit0 bit3 (row 0)
1485 // bit1 bit4 (row 1)
1486 // bit2 bit5 (row 2)
1487 // bit6 bit7 (row 3)
1488 if (code_point >= 0x2800 && code_point <= 0x28ff) {
1489 uint32_t dots = (uint32_t)(code_point - 0x2800);
1490
1491 if (dots == 0) return 0x20;
1492 if (dots == 0xff) return 0xdb;
1493
1494 bool has_top = dots & 0x1b;
1495 bool has_bottom = dots & 0xe4;
1496 bool has_left = dots & 0x47;
1497 bool has_right = dots & 0xb8;
1498
1499 if (has_top && !has_bottom) return 0xdf; // ▀
1500 if (has_bottom && !has_top) return 0xdc; // ▄
1501 if (has_left && !has_right) return 0xdd; // ▌
1502 if (has_right && !has_left) return 0xde; // ▐
1503
1504 // Count set bits for density-based shade
1505 uint32_t n = dots - ((dots >> 1) & 0x55);
1506 n = (n & 0x33) + ((n >> 2) & 0x33);
1507 n = (n + (n >> 4)) & 0x0f;
1508
1509 if (n <= 2) return 0xb0; // ░
1510 if (n <= 4) return 0xb1; // ▒
1511 if (n <= 6) return 0xb2; // ▓
1512 return 0xdb; // █
1513 }
1514
1515 switch (code_point) {
1516 case 0x263a: return 1;
1517 case 0x263b: return 2;
1518 case 0x2665: return 3;
1519 case 0x2666: return 4;
1520 case 0x25c6: return 4;
1521 case 0x2663: return 5;
1522 case 0x2660: return 6;
1523 case 0x2022: return 7;
1524 case 0x25d8: return 8;
1525 case 0x25cb: return 9;
1526 case 0x25d9: return 10;
1527 case 0x2642: return 11;
1528 case 0x2640: return 12;
1529 case 0x266a: return 13;
1530 case 0x266b: return 14;
1531 case 0x263c: return 15;
1532 case 0x00a4: return 15;
1533 case 0x25ba: return 16;
1534 case 0x25b6: return 16;
1535 case 0x25c4: return 17;
1536 case 0x25c0: return 17;
1537 case 0x2195: return 18;
1538 case 0x203c: return 19;
1539 case 0x00b6: return 20;
1540 case 0x00a7: return 21;
1541 case 0x25ac: return 22;
1542 case 0x21a8: return 23;
1543 case 0x2191: return 24;
1544 case 0x2193: return 25;
1545 case 0x2192: return 26;
1546 case 0x2190: return 27;
1547 case 0x221f: return 28;
1548 case 0x2194: return 29;
1549 case 0x25b2: return 30;
1550 case 0x25bc: return 31;
1551
1552 case 0x00a8: return 0x22;
1553 case 0x00b4: return 0x27;
1554 case 0x00b8: return 0x2c;
1555 case 0x00ad: return 0x2d;
1556 case 0x00c0: return 0x41;
1557 case 0x00c1: return 0x41;
1558 case 0x00c2: return 0x41;
1559 case 0x00c3: return 0x41;
1560 case 0x00a9: return 0x43;
1561 case 0x00d0: return 0x44;
1562 case 0x00c8: return 0x45;
1563 case 0x00ca: return 0x45;
1564 case 0x00cb: return 0x45;
1565 case 0x00cc: return 0x49;
1566 case 0x00cd: return 0x49;
1567 case 0x00ce: return 0x49;
1568 case 0x00cf: return 0x49;
1569 case 0x212a: return 0x4b;
1570 case 0x00d2: return 0x4f;
1571 case 0x00d3: return 0x4f;
1572 case 0x00d4: return 0x4f;
1573 case 0x00d5: return 0x4f;
1574 case 0x00ae: return 0x52;
1575 case 0x00d9: return 0x55;
1576 case 0x00da: return 0x55;
1577 case 0x00db: return 0x55;
1578 case 0x00dd: return 0x59;
1579 case 0x23bd: return 0x5f;
1580 case 0x00e3: return 0x61;
1581 case 0x00f5: return 0x6f;
1582 case 0x00d7: return 0x78;
1583 case 0x00fd: return 0x79;
1584 case 0x00a6: return 0x7c;
1585
1586 case 0x2302: return 127;
1587 case 0x00c7: return 128;
1588 case 0x00fc: return 129;
1589 case 0x00e9: return 130;
1590 case 0x00e2: return 131;
1591 case 0x00e4: return 132;
1592 case 0x00e0: return 133;
1593 case 0x00e5: return 134;
1594 case 0x00e7: return 135;
1595 case 0x00ea: return 136;
1596 case 0x00eb: return 137;
1597 case 0x00e8: return 138;
1598 case 0x00ef: return 139;
1599 case 0x00ee: return 140;
1600 case 0x00ec: return 141;
1601 case 0x00c4: return 142;
1602 case 0x00c5: return 143;
1603 case 0x212b: return 143;
1604 case 0x00c9: return 144;
1605 case 0x00e6: return 145;
1606 case 0x00c6: return 146;
1607 case 0x00f4: return 147;
1608 case 0x00f6: return 148;
1609 case 0x00f2: return 149;
1610 case 0x00fb: return 150;
1611 case 0x00f9: return 151;
1612 case 0x00ff: return 152;
1613 case 0x00d6: return 153;
1614 case 0x00dc: return 154;
1615 case 0x00a2: return 155;
1616 case 0x00a3: return 156;
1617 case 0x00a5: return 157;
1618 case 0x20a7: return 158;
1619 case 0x0192: return 159;
1620 case 0x00e1: return 160;
1621 case 0x00ed: return 161;
1622 case 0x00f3: return 162;
1623 case 0x00fa: return 163;
1624 case 0x00f1: return 164;
1625 case 0x00d1: return 165;
1626 case 0x00aa: return 166;
1627 case 0x00ba: return 167;
1628 case 0x00bf: return 168;
1629 case 0x2310: return 169;
1630 case 0x00ac: return 170;
1631 case 0x00bd: return 171;
1632 case 0x00bc: return 172;
1633 case 0x00a1: return 173;
1634 case 0x00ab: return 174;
1635 case 0x00bb: return 175;
1636 case 0x2591: return 176;
1637 case 0x2592: return 177;
1638 case 0x2593: return 178;
1639 case 0x2502: return 179;
1640 case 0x2524: return 180;
1641 case 0x2561: return 181;
1642 case 0x2562: return 182;
1643 case 0x2556: return 183;
1644 case 0x2555: return 184;
1645 case 0x2563: return 185;
1646 case 0x2551: return 186;
1647 case 0x2557: return 187;
1648 case 0x255d: return 188;
1649 case 0x255c: return 189;
1650 case 0x255b: return 190;
1651 case 0x2510: return 191;
1652 case 0x2514: return 192;
1653 case 0x2534: return 193;
1654 case 0x252c: return 194;
1655 case 0x251c: return 195;
1656 case 0x2500: return 196;
1657 case 0x253c: return 197;
1658 case 0x255e: return 198;
1659 case 0x255f: return 199;
1660 case 0x255a: return 200;
1661 case 0x2554: return 201;
1662 case 0x2569: return 202;
1663 case 0x2566: return 203;
1664 case 0x2560: return 204;
1665 case 0x2550: return 205;
1666 case 0x256c: return 206;
1667 case 0x2567: return 207;
1668 case 0x2568: return 208;
1669 case 0x2564: return 209;
1670 case 0x2565: return 210;
1671 case 0x2559: return 211;
1672 case 0x2558: return 212;
1673 case 0x2552: return 213;
1674 case 0x2553: return 214;
1675 case 0x256b: return 215;
1676 case 0x256a: return 216;
1677 case 0x2518: return 217;
1678 case 0x250c: return 218;
1679 case 0x2588: return 219;
1680 case 0x2584: return 220;
1681 case 0x258c: return 221;
1682 case 0x2590: return 222;
1683 case 0x2580: return 223;
1684 case 0x03b1: return 224;
1685 case 0x00df: return 225;
1686 case 0x03b2: return 225;
1687 case 0x0393: return 226;
1688 case 0x03c0: return 227;
1689 case 0x03a3: return 228;
1690 case 0x03c3: return 229;
1691 case 0x00b5: return 230;
1692 case 0x03bc: return 230;
1693 case 0x03c4: return 231;
1694 case 0x03a6: return 232;
1695 case 0x00d8: return 232;
1696 case 0x0398: return 233;
1697 case 0x03a9: return 234;
1698 case 0x2126: return 234;
1699 case 0x03b4: return 235;
1700 case 0x00f0: return 235;
1701 case 0x221e: return 236;
1702 case 0x03c6: return 237;
1703 case 0x00f8: return 237;
1704 case 0x03b5: return 238;
1705 case 0x2208: return 238;
1706 case 0x2229: return 239;
1707 case 0x2261: return 240;
1708 case 0x00b1: return 241;
1709 case 0x2265: return 242;
1710 case 0x2264: return 243;
1711 case 0x2320: return 244;
1712 case 0x2321: return 245;
1713 case 0x00f7: return 246;
1714 case 0x2248: return 247;
1715 case 0x00b0: return 248;
1716 case 0x2219: return 249;
1717 case 0x00b7: return 250;
1718 case 0x221a: return 251;
1719 case 0x207f: return 252;
1720 case 0x00b2: return 253;
1721 case 0x25a0: return 254;
1722 case 0xfffd: return 254;
1723 case 0x00a0: return 255;
1724
1725 // Approximate mappings for Unicode characters without exact CP437 equivalents
1726
1727 // Rounded/arc box drawing corners
1728 case 0x256d: return 0xda; // ╭ → ┌
1729 case 0x256e: return 0xbf; // ╮ → ┐
1730 case 0x256f: return 0xd9; // ╯ → ┘
1731 case 0x2570: return 0xc0; // ╰ → └
1732
1733 // Diagonal box drawing
1734 case 0x2571: return 0x2f; // ╱ → /
1735 case 0x2572: return 0x5c; // ╲ → \ (backslash)
1736 case 0x2573: return 0x58; // ╳ → X
1737
1738 // Heavy box drawing → single-line equivalents
1739 case 0x2501: return 0xc4; // ━ → ─
1740 case 0x2503: return 0xb3; // ┃ → │
1741 case 0x250f: return 0xda; // ┏ → ┌
1742 case 0x2513: return 0xbf; // ┓ → ┐
1743 case 0x2517: return 0xc0; // ┗ → └
1744 case 0x251b: return 0xd9; // ┛ → ┘
1745 case 0x2523: return 0xc3; // ┣ → ├
1746 case 0x252b: return 0xb4; // ┫ → ┤
1747 case 0x2533: return 0xc2; // ┳ → ┬
1748 case 0x253b: return 0xc1; // ┻ → ┴
1749 case 0x254b: return 0xc5; // ╋ → ┼
1750
1751 // Mixed heavy/light box drawing corners
1752 case 0x250d: return 0xda; // ┍ → ┌
1753 case 0x250e: return 0xda; // ┎ → ┌
1754 case 0x2511: return 0xbf; // ┑ → ┐
1755 case 0x2512: return 0xbf; // ┒ → ┐
1756 case 0x2515: return 0xc0; // ┕ → └
1757 case 0x2516: return 0xc0; // ┖ → └
1758 case 0x2519: return 0xd9; // ┙ → ┘
1759 case 0x251a: return 0xd9; // ┚ → ┘
1760
1761 // Mixed heavy/light box drawing T-pieces
1762 case 0x251d: return 0xc3; // ┝ → ├
1763 case 0x251e: return 0xc3; // ┞ → ├
1764 case 0x251f: return 0xc3; // ┟ → ├
1765 case 0x2520: return 0xc3; // ┠ → ├
1766 case 0x2521: return 0xc3; // ┡ → ├
1767 case 0x2522: return 0xc3; // ┢ → ├
1768 case 0x2525: return 0xb4; // ┥ → ┤
1769 case 0x2526: return 0xb4; // ┦ → ┤
1770 case 0x2527: return 0xb4; // ┧ → ┤
1771 case 0x2528: return 0xb4; // ┨ → ┤
1772 case 0x2529: return 0xb4; // ┩ → ┤
1773 case 0x252a: return 0xb4; // ┪ → ┤
1774 case 0x252d: return 0xc2; // ┭ → ┬
1775 case 0x252e: return 0xc2; // ┮ → ┬
1776 case 0x252f: return 0xc2; // ┯ → ┬
1777 case 0x2530: return 0xc2; // ┰ → ┬
1778 case 0x2531: return 0xc2; // ┱ → ┬
1779 case 0x2532: return 0xc2; // ┲ → ┬
1780 case 0x2535: return 0xc1; // ┵ → ┴
1781 case 0x2536: return 0xc1; // ┶ → ┴
1782 case 0x2537: return 0xc1; // ┷ → ┴
1783 case 0x2538: return 0xc1; // ┸ → ┴
1784 case 0x2539: return 0xc1; // ┹ → ┴
1785 case 0x253a: return 0xc1; // ┺ → ┴
1786
1787 // Mixed heavy/light box drawing crosses
1788 case 0x253d: return 0xc5; // ┽ → ┼
1789 case 0x253e: return 0xc5; // ┾ → ┼
1790 case 0x253f: return 0xc5; // ┿ → ┼
1791 case 0x2540: return 0xc5; // ╀ → ┼
1792 case 0x2541: return 0xc5; // ╁ → ┼
1793 case 0x2542: return 0xc5; // ╂ → ┼
1794 case 0x2543: return 0xc5; // ╃ → ┼
1795 case 0x2544: return 0xc5; // ╄ → ┼
1796 case 0x2545: return 0xc5; // ╅ → ┼
1797 case 0x2546: return 0xc5; // ╆ → ┼
1798 case 0x2547: return 0xc5; // ╇ → ┼
1799 case 0x2548: return 0xc5; // ╈ → ┼
1800 case 0x2549: return 0xc5; // ╉ → ┼
1801 case 0x254a: return 0xc5; // ╊ → ┼
1802
1803 // Dashed/dotted box drawing → solid equivalents
1804 case 0x2504: return 0xc4; // ┄ → ─
1805 case 0x2505: return 0xc4; // ┅ → ─
1806 case 0x2506: return 0xb3; // ┆ → │
1807 case 0x2507: return 0xb3; // ┇ → │
1808 case 0x2508: return 0xc4; // ┈ → ─
1809 case 0x2509: return 0xc4; // ┉ → ─
1810 case 0x250a: return 0xb3; // ┊ → │
1811 case 0x250b: return 0xb3; // ┋ → │
1812
1813 // Box drawing half-lines and fragments
1814 case 0x2574: return 0xc4; // ╴ → ─
1815 case 0x2575: return 0xb3; // ╵ → │
1816 case 0x2576: return 0xc4; // ╶ → ─
1817 case 0x2577: return 0xb3; // ╷ → │
1818 case 0x2578: return 0xc4; // ╸ → ─
1819 case 0x2579: return 0xb3; // ╹ → │
1820 case 0x257a: return 0xc4; // ╺ → ─
1821 case 0x257b: return 0xb3; // ╻ → │
1822 case 0x257c: return 0xc4; // ╼ → ─
1823 case 0x257d: return 0xb3; // ╽ → │
1824 case 0x257e: return 0xc4; // ╾ → ─
1825 case 0x257f: return 0xb3; // ╿ → │
1826
1827 // Triangle variants → filled equivalents
1828 case 0x25b3: return 30; // △ → ▲
1829 case 0x25b5: return 30; // ▵ → ▲
1830 case 0x25b7: return 16; // ▷ → ►
1831 case 0x25b9: return 16; // ▹ → ►
1832 case 0x25bd: return 31; // ▽ → ▼
1833 case 0x25bf: return 31; // ▿ → ▼
1834 case 0x25c1: return 17; // ◁ → ◄
1835 case 0x25c3: return 17; // ◃ → ◄
1836
1837 // Fractional block elements → closest CP437 block
1838 case 0x2581: return 0xdc; // ▁ (lower 1/8) → ▄
1839 case 0x2582: return 0xdc; // ▂ (lower 1/4) → ▄
1840 case 0x2583: return 0xdc; // ▃ (lower 3/8) → ▄
1841 case 0x2585: return 0xdc; // ▅ (lower 5/8) → ▄
1842 case 0x2586: return 0xdb; // ▆ (lower 3/4) → █
1843 case 0x2587: return 0xdb; // ▇ (lower 7/8) → █
1844 case 0x2589: return 0xdb; // ▉ (left 7/8) → █
1845 case 0x258a: return 0xdb; // ▊ (left 3/4) → █
1846 case 0x258b: return 0xdd; // ▋ (left 5/8) → ▌
1847 case 0x258d: return 0xdd; // ▍ (left 3/8) → ▌
1848 case 0x258e: return 0xdd; // ▎ (left 1/4) → ▌
1849 case 0x258f: return 0xdd; // ▏ (left 1/8) → ▌
1850 case 0x2594: return 0xdf; // ▔ (upper 1/8) → ▀
1851 case 0x2595: return 0xde; // ▕ (right 1/8) → ▐
1852
1853 // Quadrant block elements
1854 case 0x2596: return 0xdc; // ▖ → ▄
1855 case 0x2597: return 0xdc; // ▗ → ▄
1856 case 0x2598: return 0xdf; // ▘ → ▀
1857 case 0x2599: return 0xdb; // ▙ → █
1858 case 0x259a: return 0xb1; // ▚ → ▒
1859 case 0x259b: return 0xdb; // ▛ → █
1860 case 0x259c: return 0xdb; // ▜ → █
1861 case 0x259d: return 0xdf; // ▝ → ▀
1862 case 0x259e: return 0xb1; // ▞ → ▒
1863 case 0x259f: return 0xdb; // ▟ → █
1864
1865 // Circles and bullets
1866 case 0x25cf: return 0x07; // ● → •
1867 case 0x25c9: return 0x0a; // ◉ → ◙
1868 case 0x25ef: return 0x09; // ◯ → ○
1869 case 0x25e6: return 0x09; // ◦ → ○
1870 case 0x25aa: return 0xfe; // ▪ → ■
1871 case 0x25fc: return 0xfe; // ◼ → ■
1872
1873 // Typographic punctuation
1874 case 0x2010: return 0x2d; // ‐ (hyphen) → -
1875 case 0x2011: return 0x2d; // ‑ (non-breaking hyphen) → -
1876 case 0x2012: return 0x2d; // ‒ (figure dash) → -
1877 case 0x2013: return 0x2d; // – (en dash) → -
1878 case 0x2014: return 0x2d; // — (em dash) → -
1879 case 0x2018: return 0x27; // ' (left single quote) → '
1880 case 0x2019: return 0x27; // ' (right single quote) → '
1881 case 0x201c: return 0x22; // " (left double quote) → "
1882 case 0x201d: return 0x22; // " (right double quote) → "
1883 case 0x2026: return 0xfa; // … (ellipsis) → ·
1884 case 0x2212: return 0x2d; // − (minus sign) → -
1885
1886 // Check marks
1887 case 0x2713: return 0xfb; // ✓ → √
1888 case 0x2714: return 0xfb; // ✔ → √
1889
1890 // Double arrows → single arrow equivalents
1891 case 0x21d0: return 27; // ⇐ → ←
1892 case 0x21d1: return 24; // ⇑ → ↑
1893 case 0x21d2: return 26; // ⇒ → →
1894 case 0x21d3: return 25; // ⇓ → ↓
1895 case 0x21d4: return 29; // ⇔ → ↔
1896 case 0x21d5: return 18; // ⇕ → ↕
1897
1898 // Summation sign
1899 case 0x2211: return 0xe4; // ∑ → Σ
1900
1901 // Horizontal line extension
1902 case 0x2015: return 0xc4; // ― (horizontal bar) → ─
1903 case 0x23af: return 0xc4; // ⎯ → ─
1904
1905 // Media transport symbols
1906 case 0x23f4: return 17; // ⏴ → ◄
1907 case 0x23f5: return 16; // ⏵ → ►
1908 case 0x23f6: return 30; // ⏶ → ▲
1909 case 0x23f7: return 31; // ⏷ → ▼
1910 case 0x23f8: return 0xba; // ⏸ → ║
1911 case 0x23f9: return 0xfe; // ⏹ → ■
1912 case 0x23fa: return 0x07; // ⏺ → •
1913
1914 // Square bracket pieces
1915 case 0x23a1: return 0xda; // ⎡ → ┌
1916 case 0x23a2: return 0xb3; // ⎢ → │
1917 case 0x23a3: return 0xc0; // ⎣ → └
1918 case 0x23a4: return 0xbf; // ⎤ → ┐
1919 case 0x23a5: return 0xb3; // ⎥ → │
1920 case 0x23a6: return 0xd9; // ⎦ → ┘
1921
1922 // Curly bracket pieces
1923 case 0x23a7: return 0xda; // ⎧ → ┌
1924 case 0x23a8: return 0xc3; // ⎨ → ├
1925 case 0x23a9: return 0xc0; // ⎩ → └
1926 case 0x23aa: return 0xb3; // ⎪ → │
1927 case 0x23ab: return 0xbf; // ⎫ → ┐
1928 case 0x23ac: return 0xb4; // ⎬ → ┤
1929 case 0x23ad: return 0xd9; // ⎭ → ┘
1930 case 0x23ae: return 0xb3; // ⎮ → │
1931
1932 // Vertical box lines
1933 case 0x23b8: return 0xb3; // ⎸ → │
1934 case 0x23b9: return 0xb3; // ⎹ → │
1935
1936 // Horizontal scan lines (0x23bd already mapped above)
1937 case 0x23ba: return 0xc4; // ⎺ → ─
1938 case 0x23bb: return 0xc4; // ⎻ → ─
1939 case 0x23bc: return 0xc4; // ⎼ → ─
1940
1941 // Dentistry/angle symbols
1942 case 0x23be: return 0xb3; // ⎾ → │
1943 case 0x23bf: return 0xc0; // ⎿ → └
1944
1945 // Corner brackets
1946 case 0x231c: return 0xda; // ⌜ → ┌
1947 case 0x231d: return 0xbf; // ⌝ → ┐
1948 case 0x231e: return 0xc0; // ⌞ → └
1949 case 0x231f: return 0xd9; // ⌟ → ┘
1950 }
1951
1952 return -1;
1953}
1954
1955static void insert_shift(struct flanterm_context *ctx, size_t count) {
1956 if (ctx->insert_mode && count > 0) {
1957 size_t x, y;
1958 ctx->get_cursor_pos(ctx, &x, &y);
1959 if (count > ctx->cols - x) {
1960 count = ctx->cols - x;
1961 }
1962 for (size_t i = ctx->cols - 1; i >= x + count; i--) {
1963 ctx->move_character(ctx, i, y, i - count, y);
1964 }
1965 }
1966}
1967
1968static void flanterm_putchar(struct flanterm_context *ctx, uint8_t c) {
1969 if (ctx->discard_next || (c == 0x18 || c == 0x1a)) {
1970 ctx->discard_next = false;
1971 ctx->escape = false;
1972 ctx->control_sequence = false;
1973 ctx->unicode_remaining = 0;
1974 ctx->osc = false;
1975 ctx->osc_escape = false;
1976 ctx->g_select = 0;
1977 ctx->last_was_graphic = false;
1978 return;
1979 }
1980
1981 if (ctx->escape == false && ctx->unicode_remaining != 0) {
1982 if ((c & 0xc0) != 0x80) {
1983 bool already_errored = ctx->code_point > 0x10ffff;
1984 ctx->unicode_remaining = 0;
1985 ctx->code_point = 0;
1986 if (!already_errored) {
1987 insert_shift(ctx, count: 1);
1988 ctx->raw_putchar(ctx, 0xfe);
1989 }
1990 goto unicode_error;
1991 }
1992
1993 ctx->unicode_remaining--;
1994 ctx->code_point |= (uint64_t)(c & 0x3f) << (6 * ctx->unicode_remaining);
1995
1996 // Drain remaining continuation bytes of a sequence already flagged bad.
1997 if (ctx->code_point > 0x10ffff) {
1998 return;
1999 }
2000
2001 // Reject overlong encodings and out-of-range codepoints as soon as the
2002 // partial codepoint proves the sequence invalid, emit one replacement,
2003 // and drain the remaining continuation bytes silently.
2004 // 3-byte lead E0: first continuation must be >= 0xA0 (code_point >= 0x800)
2005 // 4-byte lead F0: first continuation must be >= 0x90 (code_point >= 0x10000)
2006 // 4-byte lead F4: first continuation must be <= 0x8F (code_point <= 0x10FFFF)
2007 if ((ctx->unicode_remaining == 1 && ctx->code_point < 0x800) ||
2008 (ctx->unicode_remaining == 2 && ctx->code_point < 0x10000) ||
2009 (ctx->unicode_remaining == 2 && ctx->code_point > 0x10ffff)) {
2010 insert_shift(ctx, count: 1);
2011 ctx->last_printed_char = 0xfe;
2012 ctx->last_was_graphic = true;
2013 ctx->raw_putchar(ctx, 0xfe);
2014 ctx->code_point = UINT64_MAX;
2015 return;
2016 }
2017
2018 if (ctx->unicode_remaining != 0) {
2019 return;
2020 }
2021
2022 if (ctx->code_point >= 0xd800 && ctx->code_point <= 0xdfff) {
2023 goto unicode_error;
2024 }
2025
2026 int cc = unicode_to_cp437(code_point: ctx->code_point);
2027
2028 if (cc == -1) {
2029 int replacement_width = mk_wcwidth(ucs: ctx->code_point);
2030 if (replacement_width > 0) {
2031 insert_shift(ctx, count: replacement_width);
2032 ctx->last_printed_char = 0xfe;
2033 ctx->last_was_graphic = true;
2034 ctx->raw_putchar(ctx, 0xfe);
2035 }
2036 for (int i = 1; i < replacement_width; i++) {
2037 ctx->raw_putchar(ctx, ' ');
2038 }
2039 } else {
2040 insert_shift(ctx, count: 1);
2041 ctx->last_printed_char = cc;
2042 ctx->last_was_graphic = true;
2043 ctx->raw_putchar(ctx, cc);
2044 }
2045 return;
2046 }
2047
2048unicode_error:
2049 if (ctx->escape == false && c >= 0xc2 && c <= 0xf4) {
2050 ctx->g_select = 0;
2051 if (c >= 0xc2 && c <= 0xdf) {
2052 ctx->unicode_remaining = 1;
2053 ctx->code_point = (uint64_t)(c & 0x1f) << 6;
2054 } else if (c >= 0xe0 && c <= 0xef) {
2055 ctx->unicode_remaining = 2;
2056 ctx->code_point = (uint64_t)(c & 0x0f) << (6 * 2);
2057 } else if (c >= 0xf0 && c <= 0xf4) {
2058 ctx->unicode_remaining = 3;
2059 ctx->code_point = (uint64_t)(c & 0x07) << (6 * 3);
2060 }
2061 return;
2062 }
2063
2064 if (ctx->escape == true) {
2065 escape_parse(ctx, c);
2066 return;
2067 }
2068
2069 if (ctx->g_select) {
2070 if (c <= 0x1f || c == 0x7f) {
2071 ctx->g_select = 0;
2072 } else {
2073 ctx->g_select--;
2074 switch (c) {
2075 case 'B':
2076 ctx->charsets[ctx->g_select] = CHARSET_DEFAULT; break;
2077 case '0':
2078 ctx->charsets[ctx->g_select] = CHARSET_DEC_SPECIAL; break;
2079 }
2080 ctx->g_select = 0;
2081 return;
2082 }
2083 }
2084
2085 if ((c <= 0x1f && c != 0x1b) || c == 0x7f) {
2086 ctx->last_was_graphic = false;
2087 }
2088
2089 switch (c) {
2090 case 0x00:
2091 case 0x7f:
2092 return;
2093 case 0x1b:
2094 ctx->escape_offset = 0;
2095 ctx->escape = true;
2096 return;
2097 }
2098
2099 if (c < 0x20) {
2100 execute_c0(ctx, c);
2101 return;
2102 }
2103
2104 insert_shift(ctx, count: 1);
2105
2106 // Translate character set
2107 switch (ctx->charsets[ctx->current_charset]) {
2108 case CHARSET_DEFAULT:
2109 break;
2110 case CHARSET_DEC_SPECIAL:
2111 if (dec_special_print(ctx, c)) {
2112 return;
2113 }
2114 break;
2115 }
2116
2117 if (c >= 0x20 && c <= 0x7e) {
2118 ctx->last_printed_char = c;
2119 ctx->last_was_graphic = true;
2120 ctx->raw_putchar(ctx, c);
2121 } else if (c >= 0x80) {
2122 ctx->last_printed_char = 0xfe;
2123 ctx->last_was_graphic = true;
2124 ctx->raw_putchar(ctx, 0xfe);
2125 }
2126}
2127
2128void flanterm_flush(struct flanterm_context *ctx) {
2129 ctx->double_buffer_flush(ctx);
2130}
2131
2132void flanterm_full_refresh(struct flanterm_context *ctx) {
2133 ctx->full_refresh(ctx);
2134}
2135
2136void flanterm_deinit(struct flanterm_context *ctx, void (*_free)(void *, size_t)) {
2137 ctx->deinit(ctx, _free);
2138}
2139
2140void flanterm_get_dimensions(struct flanterm_context *ctx, size_t *cols, size_t *rows) {
2141 *cols = ctx->cols;
2142 *rows = ctx->rows;
2143}
2144
2145void flanterm_set_autoflush(struct flanterm_context *ctx, bool state) {
2146 ctx->autoflush = state;
2147}
2148
2149void flanterm_set_callback(struct flanterm_context *ctx, void (*callback)(struct flanterm_context *, uint64_t, uint64_t, uint64_t, uint64_t)) {
2150 ctx->callback = callback;
2151}
2152
2153void flanterm_get_cursor_pos(struct flanterm_context *ctx, size_t *x, size_t *y) {
2154 ctx->get_cursor_pos(ctx, x, y);
2155}
2156
2157void flanterm_set_cursor_pos(struct flanterm_context *ctx, size_t x, size_t y) {
2158 if (x >= ctx->cols) {
2159 x = ctx->cols - 1;
2160 }
2161
2162 if (y >= ctx->rows) {
2163 y = ctx->rows - 1;
2164 }
2165
2166 ctx->set_cursor_pos(ctx, x, y);
2167
2168 if (ctx->autoflush) {
2169 ctx->double_buffer_flush(ctx);
2170 }
2171}
2172
2173void flanterm_set_text_fg(struct flanterm_context *ctx, size_t colour, bool bright) {
2174 if (colour >= 8) {
2175 return;
2176 }
2177
2178 ctx->current_primary = colour;
2179
2180 if (bright) {
2181 if (!ctx->reverse_video) {
2182 ctx->set_text_fg_bright(ctx, colour);
2183 } else {
2184 ctx->set_text_bg_bright(ctx, colour);
2185 }
2186 } else {
2187 if (!ctx->reverse_video) {
2188 ctx->set_text_fg(ctx, colour);
2189 } else {
2190 ctx->set_text_bg(ctx, colour);
2191 }
2192 }
2193
2194 if (ctx->autoflush) {
2195 ctx->double_buffer_flush(ctx);
2196 }
2197}
2198
2199void flanterm_set_text_bg(struct flanterm_context *ctx, size_t colour, bool bright) {
2200 if (colour >= 8) {
2201 return;
2202 }
2203
2204 ctx->current_bg = colour;
2205
2206 if (bright) {
2207 if (!ctx->reverse_video) {
2208 ctx->set_text_bg_bright(ctx, colour);
2209 } else {
2210 ctx->set_text_fg_bright(ctx, colour);
2211 }
2212 } else {
2213 if (!ctx->reverse_video) {
2214 ctx->set_text_bg(ctx, colour);
2215 } else {
2216 ctx->set_text_fg(ctx, colour);
2217 }
2218 }
2219
2220 if (ctx->autoflush) {
2221 ctx->double_buffer_flush(ctx);
2222 }
2223}
2224
2225void flanterm_reset_text_fg(struct flanterm_context *ctx) {
2226 ctx->current_primary = (size_t)-1;
2227
2228 if (ctx->reverse_video) {
2229 ctx->swap_palette(ctx);
2230 }
2231
2232 if (!ctx->bold) {
2233 ctx->set_text_fg_default(ctx);
2234 } else {
2235 ctx->set_text_fg_default_bright(ctx);
2236 }
2237
2238 if (ctx->reverse_video) {
2239 ctx->swap_palette(ctx);
2240 }
2241
2242 if (ctx->autoflush) {
2243 ctx->double_buffer_flush(ctx);
2244 }
2245}
2246
2247void flanterm_reset_text_bg(struct flanterm_context *ctx) {
2248 ctx->current_bg = (size_t)-1;
2249
2250 if (ctx->reverse_video) {
2251 ctx->swap_palette(ctx);
2252 }
2253
2254 if (!ctx->bg_bold) {
2255 ctx->set_text_bg_default(ctx);
2256 } else {
2257 ctx->set_text_bg_default_bright(ctx);
2258 }
2259
2260 if (ctx->reverse_video) {
2261 ctx->swap_palette(ctx);
2262 }
2263
2264 if (ctx->autoflush) {
2265 ctx->double_buffer_flush(ctx);
2266 }
2267}
2268
2269void flanterm_clear(struct flanterm_context *ctx, bool move) {
2270 ctx->clear(ctx, move);
2271
2272 if (ctx->autoflush) {
2273 ctx->double_buffer_flush(ctx);
2274 }
2275}
2276