diff --git a/examples/termbox2-demo/CMakeLists.txt b/examples/termbox2-demo/CMakeLists.txt new file mode 100644 index 0000000..20d9026 --- /dev/null +++ b/examples/termbox2-demo/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.25) +project(clay_examples_termbox2_demo C) +set(CMAKE_C_STANDARD 11) + +include(FetchContent) +set(FETCHCONTENT_QUIET FALSE) + +FetchContent_Declare( + termbox2 + GIT_REPOSITORY "https://github.com/termbox/termbox2.git" + GIT_TAG "9c9281a9a4c971a2be57f8645e828ec99fd555e8" + GIT_PROGRESS TRUE + GIT_SHALLOW TRUE +) +FetchContent_MakeAvailable(termbox2) + +FetchContent_Declare( + stb + GIT_REPOSITORY "https://github.com/nothings/stb.git" + GIT_TAG "f58f558c120e9b32c217290b80bad1a0729fbb2c" + GIT_PROGRESS TRUE + GIT_SHALLOW TRUE +) +FetchContent_MakeAvailable(stb) + +add_executable(clay_examples_termbox2_demo main.c) + +target_compile_options(clay_examples_termbox2_demo PUBLIC) +target_include_directories(clay_examples_termbox2_demo PUBLIC . PRIVATE ${termbox2_SOURCE_DIR} PRIVATE ${stb_SOURCE_DIR}) +target_link_libraries(clay_examples_termbox2_demo PRIVATE m) # Used by stb_image.h + +add_custom_command( + TARGET clay_examples_termbox2_demo POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/resources + ${CMAKE_CURRENT_BINARY_DIR}/resources) diff --git a/examples/termbox2-demo/main.c b/examples/termbox2-demo/main.c new file mode 100644 index 0000000..a4ac094 --- /dev/null +++ b/examples/termbox2-demo/main.c @@ -0,0 +1,789 @@ +/* + Unlicense + + Copyright (c) 2025 Mivirl + + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ + +#define CLAY_IMPLEMENTATION +#include "../../clay.h" +#include "../../renderers/termbox2/clay_renderer_termbox2.c" + +#define TB_IMPL +#include "termbox2.h" + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#include "stb_image_resize2.h" + + +// ------------------------------------------------------------------------------------------------- +// -- Internal state + +// If the program should exit the main render/interaction loop +bool end_loop = false; + +// If the debug tools should be displayed +bool debugMode = false; + + +// ------------------------------------------------------------------------------------------------- +// -- Clay components + +void component_text_pair(const char *key, const char *value) +{ + size_t keylen = strlen(key); + size_t vallen = strlen(value); + Clay_String keytext = (Clay_String) { + .length = keylen, + .chars = key, + }; + Clay_String valtext = (Clay_String) { + .length = vallen, + .chars = value, + }; + Clay_TextElementConfig *textconfig = + CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } }); + CLAY({ + .layout = { + .sizing = { + .width = { + .size.minMax = { + .min = strlen("Border chars CLAY_TB_IMAGE_MODE_UNICODE_FAST") * Clay_Termbox_Cell_Width(), + } + }, + } + }, + }) { + CLAY_TEXT(keytext, textconfig); + CLAY({ .layout = { .sizing = CLAY_SIZING_GROW(1) } }) { } + CLAY_TEXT(valtext, textconfig); + } + +} + +void component_termbox_settings(void) +{ + CLAY({ + .floating = { + .attachTo = CLAY_ATTACH_TO_PARENT, + .zIndex = 1, + .attachPoints = { CLAY_ATTACH_POINT_CENTER_CENTER, CLAY_ATTACH_POINT_CENTER_TOP }, + .offset = { 0, 0 } + }, + }) { + CLAY({ + .layout = { + .sizing = CLAY_SIZING_FIT(), + .padding = { + 6 * Clay_Termbox_Cell_Width(), + 6 * Clay_Termbox_Cell_Width(), + 2 * Clay_Termbox_Cell_Height(), + 2 * Clay_Termbox_Cell_Height(), + } + }, + .border = { + .width = CLAY_BORDER_ALL(1), + .color = { 0x00, 0x00, 0x00, 0xff } + }, + .backgroundColor = { 0x7f, 0x00, 0x00, 0x7f } + }) { + const char *color_mode = NULL; + switch (clay_tb_color_mode) { + case TB_OUTPUT_NORMAL: { + color_mode = "TB_OUTPUT_NORMAL"; + break; + } + case TB_OUTPUT_256: { + color_mode = "TB_OUTPUT_256"; + break; + } + case TB_OUTPUT_216: { + color_mode = "TB_OUTPUT_216"; + break; + } + case TB_OUTPUT_GRAYSCALE: { + color_mode = "TB_OUTPUT_GRAYSCALE"; + break; + } + case TB_OUTPUT_TRUECOLOR: { + color_mode = "TB_OUTPUT_TRUECOLOR"; + break; + } + case CLAY_TB_OUTPUT_NOCOLOR: { + color_mode = "CLAY_TB_OUTPUT_NOCOLOR"; + break; + } + default: { + color_mode = "INVALID"; + break; + } + } + const char *border_mode = NULL; + switch (clay_tb_border_mode) { + case CLAY_TB_BORDER_MODE_ROUND: { + border_mode = "CLAY_TB_BORDER_MODE_ROUND"; + break; + } + case CLAY_TB_BORDER_MODE_MINIMUM: { + border_mode = "CLAY_TB_BORDER_MODE_MINIMUM"; + break; + } + default: { + border_mode = "INVALID"; + break; + } + } + const char *border_chars = NULL; + switch (clay_tb_border_chars) { + case CLAY_TB_BORDER_CHARS_ASCII: { + border_chars = "CLAY_TB_BORDER_CHARS_ASCII"; + break; + } + case CLAY_TB_BORDER_CHARS_UNICODE: { + border_chars = "CLAY_TB_BORDER_CHARS_UNICODE"; + break; + } + case CLAY_TB_BORDER_CHARS_BLANK: { + border_chars = "CLAY_TB_BORDER_CHARS_BLANK"; + break; + } + case CLAY_TB_BORDER_CHARS_NONE: { + border_chars = "CLAY_TB_BORDER_CHARS_NONE"; + break; + } + default: { + border_chars = "INVALID"; + break; + } + } + const char *image_mode = NULL; + switch (clay_tb_image_mode) { + case CLAY_TB_IMAGE_MODE_PLACEHOLDER: { + image_mode = "CLAY_TB_IMAGE_MODE_PLACEHOLDER"; + break; + } + case CLAY_TB_IMAGE_MODE_BG: { + image_mode = "CLAY_TB_IMAGE_MODE_BG"; + break; + } + case CLAY_TB_IMAGE_MODE_ASCII_FG: { + image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FG"; + break; + } + case CLAY_TB_IMAGE_MODE_ASCII_FG_FAST: { + image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FG_FAST"; + break; + } + case CLAY_TB_IMAGE_MODE_ASCII: { + image_mode = "CLAY_TB_IMAGE_MODE_ASCII"; + break; + } + case CLAY_TB_IMAGE_MODE_ASCII_FAST: { + image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FAST"; + break; + } + case CLAY_TB_IMAGE_MODE_UNICODE: { + image_mode = "CLAY_TB_IMAGE_MODE_UNICODE"; + break; + } + case CLAY_TB_IMAGE_MODE_UNICODE_FAST: { + image_mode = "CLAY_TB_IMAGE_MODE_UNICODE_FAST"; + break; + } + default: { + image_mode = "INVALID"; + break; + } + } + const char *transparency = NULL; + if (clay_tb_transparency) { + transparency = "true"; + } else { + transparency = "false"; + } + + CLAY({ + .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM }, + }) { + component_text_pair("Color mode", color_mode); + component_text_pair("Border mode", border_mode); + component_text_pair("Border chars", border_chars); + component_text_pair("Image mode", image_mode); + component_text_pair("Transparency", transparency); + } + } + } +} + +void component_color_palette(void) +{ + CLAY({ + .layout = { + .childGap = 16, + .padding = { + 2 * Clay_Termbox_Cell_Width(), + 2 * Clay_Termbox_Cell_Width(), + 2 * Clay_Termbox_Cell_Height(), + 2 * Clay_Termbox_Cell_Height(), + } + }, + .border = { + .width = CLAY_BORDER_OUTSIDE(2), + .color = { 0x00, 0x00, 0x00, 0xff } + }, + .backgroundColor = { 0x7f, 0x7f, 0x7f, 0xff } + }) { + for (int type = 0; type < 2; ++type) { + CLAY({ + .layout ={ + .layoutDirection = CLAY_TOP_TO_BOTTOM, + .sizing = CLAY_SIZING_FIT(), + .childGap = Clay_Termbox_Cell_Height() + }, + }) { + for (float ri = 0; ri < 4; ri += 1) { + CLAY({ + .layout ={ + .sizing = CLAY_SIZING_FIT(), + .childGap = Clay_Termbox_Cell_Width() + }, + }) { + for (float r = ri * 0x44; r < (ri + 1) * 0x44; r += 0x22) { + CLAY({ + .layout ={ + .layoutDirection = CLAY_TOP_TO_BOTTOM, + .sizing = CLAY_SIZING_FIT(), + }, + }) { + for (float g = 0; g < 0xff; g += 0x22) { + CLAY({ + .layout ={ + .sizing = CLAY_SIZING_FIT(), + }, + }) { + for (float b = 0; b < 0xff; b += 0x22) { + Clay_Color color = { r, g, b, 0x7f }; + + Clay_LayoutConfig layout = (Clay_LayoutConfig) { + .sizing = { + .width = CLAY_SIZING_FIXED(2 * Clay_Termbox_Cell_Width()), + .height = CLAY_SIZING_FIXED(1 * Clay_Termbox_Cell_Height()) + } + }; + if (0 == type) { + CLAY({ + .layout = layout, + .backgroundColor = color + }) {} + } else if (1 == type) { + CLAY({ + .layout = layout, + }) { + CLAY_TEXT(CLAY_STRING("#"), CLAY_TEXT_CONFIG({ .textColor = color })); + } + } + } + } + } + } + } + } + } + } + } + } +} + +void component_unicode_text(void) +{ + CLAY({ + .layout = { + .sizing = CLAY_SIZING_FIT(), + .padding = { + 2 * Clay_Termbox_Cell_Width(), + 2 * Clay_Termbox_Cell_Width(), + 2 * Clay_Termbox_Cell_Height(), + 2 * Clay_Termbox_Cell_Height(), + } + }, + .backgroundColor = { 0xcc, 0xbb, 0xaa, 0xff }, + .border = { + // This border should still be displayed in CLAY_TB_BORDER_MODE_ROUND mode + .width = { + 0.75 * Clay_Termbox_Cell_Width(), + 0.75 * Clay_Termbox_Cell_Width(), + 0.75 * Clay_Termbox_Cell_Height(), + 0.75 * Clay_Termbox_Cell_Height(), + }, + .color = { 0x33, 0x33, 0x33, 0xff }, + }, + }) { + CLAY_TEXT( + CLAY_STRING("Non-ascii character tests:\n" + "\n" + "(from https://www.w3.org/2001/06/utf-8-test/UTF-8-demo.html)\n" + " Mathematics and Sciences:\n" + " ∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ∀x∈ℝ: ⌈x⌉ = −⌊−x⌋, α ∧ ¬β = ¬(¬α ∨ β),\n" + " ℕ ⊆ ℕ₀ ⊂ ℤ ⊂ ℚ ⊂ ℝ ⊂ ℂ, ⊥ < a ≠ b ≡ c ≤ d ≪ ⊤ ⇒ (A ⇔ B),\n" + " 2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm\n" + "\n" + " Compact font selection example text:\n" + " ABCDEFGHIJKLMNOPQRSTUVWXYZ /0123456789\n" + " abcdefghijklmnopqrstuvwxyz £©µÀÆÖÞßéöÿ\n" + " –—‘“”„†•…‰™œŠŸž€ ΑΒΓΔΩαβγδω АБВГДабвгд\n" + " ∀∂∈ℝ∧∪≡∞ ↑↗↨↻⇣ ┐┼╔╘░►☺♀ fi�⑀₂ἠḂӥẄɐː⍎אԱა\n" + "\n" + "(from https://blog.denisbider.com/2015/09/when-monospace-fonts-arent-unicode.html):\n" + " aeioucsz\n" + " áéíóúčšž\n" + " 台北1234\n" + " QRS12\n" + " アイウ1234\n" + "\n" + "(from https://stackoverflow.com/a/1644280)\n" + " ٩(-̮̮̃-̃)۶ ٩(●̮̮̃•̃)۶ ٩(͡๏̯͡๏)۶ ٩(-̮̮̃•̃)." + ), + CLAY_TEXT_CONFIG({ .textColor = { 0x11, 0x11, 0x11, 0xff } }) + ); + } +} + +void component_keybinds(void) +{ + CLAY({ + .layout = { + .sizing = CLAY_SIZING_FIT(), + .padding = { + 4 * Clay_Termbox_Cell_Width(), + 4 * Clay_Termbox_Cell_Width(), + 2 * Clay_Termbox_Cell_Height(), + 2 * Clay_Termbox_Cell_Height(), + } + }, + .backgroundColor = { 0x00, 0x7f, 0x7f, 0xff } + }) { + CLAY_TEXT( + CLAY_STRING( + "Termbox2 renderer test\n" + "\n" + "Keybinds:\n" + " c/C - Cycle through color modes\n" + " b/B - Cycle through border modes\n" + " h/H - Cycle through border characters\n" + " i/I - Cycle through image modes\n" + " t/T - Toggle transparency\n" + " d/D - Toggle debug mode\n" + " q/Q - Quit\n" + ), + CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff }}) + ); + } +} + +void component_image(clay_tb_image *image, int width) +{ + CLAY({ + .layout = { + .sizing = { + .width = (0 == width) ? CLAY_SIZING_GROW() : CLAY_SIZING_FIXED(width), + }, + }, + .image = { + .imageData = image, + }, + .aspectRatio = { 512.0 / 406.0 } + }) { } +} + +void component_mouse_data(void) +{ + CLAY({ + .layout = { + .sizing = { + .width = CLAY_SIZING_GROW(), + }, + }, + }) { + Clay_Context* context = Clay_GetCurrentContext(); + Clay_Vector2 mouse_position = context->pointerInfo.position; + + Clay_LayoutConfig layout = (Clay_LayoutConfig) { + .sizing = { + .width = CLAY_SIZING_FIXED(2 * Clay_Termbox_Cell_Width()), + .height = CLAY_SIZING_FIXED(1 * Clay_Termbox_Cell_Height()) + } + }; + + float v = 255 * mouse_position.x / Clay_Termbox_Width(); + v = (0 > v) ? 0 : v; + v = (255 < v) ? 255 : v; + Clay_Color color = (Clay_Color) { v, v, v, 0xff }; + + CLAY({ + .layout = layout, + .backgroundColor = color + }) {} + + v = 255 * mouse_position.y / Clay_Termbox_Height(); + v = (0 > v) ? 0 : v; + v = (255 < v) ? 255 : v; + color = (Clay_Color) { v, v, v, 0xff }; + + + CLAY({ + .layout = layout, + .backgroundColor = color + }) {} + + } +} + +void component_bordered_text(void) +{ + CLAY({ + .layout = { + .layoutDirection = CLAY_TOP_TO_BOTTOM, + .sizing = { + .width = CLAY_SIZING_FIT(450), + .height = CLAY_SIZING_FIT(), + }, + .padding = CLAY_PADDING_ALL(32) + }, + .backgroundColor = { 0x24, 0x55, 0x34, 0xff }, + }) { + CLAY({ + .border = { .width = { 1, 1, 1, 1, 1 }, .color = { 0xaa, 0x00, 0x00, 0xff } }, + }) { + CLAY_TEXT( + CLAY_STRING("Test"), CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } })); + } + CLAY({ + .border = { .width = { 1, 1, 1, 1, 1 }, .color = { 0x00, 0xaa, 0x00, 0xff } }, + }) { + CLAY_TEXT(CLAY_STRING("of the border width"), + CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } })); + } + CLAY({ + .border = { .width = { 1, 1, 1, 1, 1 }, .color = { 0x00, 0x00, 0xaa, 0xff } }, + }) { + CLAY_TEXT(CLAY_STRING("and overlap for multiple lines\nof text"), + CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } })); + } + CLAY({ + .border = { .width = { 1, 1, 1, 1, 1 }, .color = { 0x00, 0x00, 0xaa, 0xff } }, + }) { + CLAY_TEXT(CLAY_STRING("this text\nis long enough\nto display all\n borders\naround it"), + CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } })); + } + } +} + +Clay_RenderCommandArray CreateLayout(clay_tb_image *image1, clay_tb_image *image2) +{ + Clay_BeginLayout(); + CLAY({ + .layout = { + .sizing = { + .width = CLAY_SIZING_GROW(), + .height = CLAY_SIZING_GROW() + }, + .childAlignment = { + .x = CLAY_ALIGN_X_CENTER, + .y = CLAY_ALIGN_Y_CENTER + }, + .childGap = 64 + }, + .backgroundColor = { 0x24, 0x24, 0x24, 0xff } + }) { + CLAY({ + .layout = { + .childAlignment = { + .x = CLAY_ALIGN_X_RIGHT, + }, + .layoutDirection = CLAY_TOP_TO_BOTTOM, + .sizing = CLAY_SIZING_FIT(), + }, + }) { + component_keybinds(); + component_unicode_text(); + } + CLAY({ + .layout = { + .layoutDirection = CLAY_TOP_TO_BOTTOM, + .childGap = 32, + .sizing = CLAY_SIZING_FIT(), + }, + }) { + component_termbox_settings(); + component_image(image1, 150); + component_image(image2, 0); + component_mouse_data(); + component_bordered_text(); + } + + component_color_palette(); + } + return Clay_EndLayout(); +} + + +// ------------------------------------------------------------------------------------------------- +// -- Interactive functions + +void handle_clay_errors(Clay_ErrorData errorData) +{ + Clay_Termbox_Close(); + fprintf(stderr, "%s", errorData.errorText.chars); + exit(1); +} + +/** + Process events received from termbox2 and handle interaction + */ +void handle_termbox_events(void) +{ + // Wait up to 100ms for an event (key/mouse press, terminal resize) before continuing + // If an event is already available, this doesn't wait. Will not wait due to the previous call + // to termbox_waitfor_event. Increasing the wait time reduces load without reducing + // responsiveness (but will of course prevent other code from running on this thread while it's + // waiting) + struct tb_event evt; + int ms_to_wait = 0; + int err = tb_peek_event(&evt, ms_to_wait); + + switch (err) { + default: + case TB_ERR_NO_EVENT: { + break; + } + case TB_ERR_POLL: { + if (EINTR != tb_last_errno()) { + Clay_Termbox_Close(); + fprintf(stderr, "Failed to read event from TTY\n"); + exit(1); + } + break; + } + case TB_OK: { + switch (evt.type) { + case TB_EVENT_RESIZE: { + Clay_SetLayoutDimensions((Clay_Dimensions) { + Clay_Termbox_Width(), + Clay_Termbox_Height() + }); + break; + } + case TB_EVENT_KEY: { + if (TB_KEY_CTRL_C == evt.key) { + end_loop = true; + break; + } + switch (evt.ch) { + case 'q': + case 'Q': { + end_loop = true; + break; + } + case 'd': + case 'D': { + debugMode = !debugMode; + Clay_SetDebugModeEnabled(debugMode); + break; + } + case 'c': { + int new_mode = clay_tb_color_mode - 1; + new_mode = (0 <= new_mode) ? new_mode : TB_OUTPUT_TRUECOLOR; + Clay_Termbox_Set_Color_Mode(new_mode); + break; + } + case 'C': { + int new_mode = (clay_tb_color_mode + 1) % (TB_OUTPUT_TRUECOLOR + 1); + Clay_Termbox_Set_Color_Mode(new_mode); + break; + } + case 'b': { + enum border_mode new_mode = clay_tb_border_mode - 1; + new_mode = (CLAY_TB_BORDER_MODE_DEFAULT < new_mode) + ? new_mode + : CLAY_TB_BORDER_MODE_MINIMUM; + Clay_Termbox_Set_Border_Mode(new_mode); + break; + } + case 'B': { + enum border_mode new_mode = (clay_tb_border_mode + 1) + % (CLAY_TB_BORDER_MODE_MINIMUM + 1); + new_mode = (CLAY_TB_BORDER_MODE_DEFAULT < new_mode) + ? new_mode + : CLAY_TB_BORDER_MODE_ROUND; + Clay_Termbox_Set_Border_Mode(new_mode); + break; + } + case 'h': { + enum border_chars new_chars = clay_tb_border_chars - 1; + new_chars = (CLAY_TB_BORDER_CHARS_DEFAULT < new_chars) + ? new_chars + : CLAY_TB_BORDER_CHARS_NONE; + Clay_Termbox_Set_Border_Chars(new_chars); + break; + } + case 'H': { + enum border_chars new_chars + = (clay_tb_border_chars + 1) % (CLAY_TB_BORDER_CHARS_NONE + 1); + new_chars = (CLAY_TB_BORDER_CHARS_DEFAULT < new_chars) + ? new_chars + : CLAY_TB_BORDER_CHARS_ASCII; + Clay_Termbox_Set_Border_Chars(new_chars); + break; + } + case 'i': { + enum image_mode new_mode = clay_tb_image_mode - 1; + new_mode = (CLAY_TB_IMAGE_MODE_DEFAULT < new_mode) + ? new_mode + : CLAY_TB_IMAGE_MODE_UNICODE_FAST; + Clay_Termbox_Set_Image_Mode(new_mode); + break; + } + case 'I': { + enum image_mode new_mode = (clay_tb_image_mode + 1) + % (CLAY_TB_IMAGE_MODE_UNICODE_FAST + 1); + new_mode = (CLAY_TB_IMAGE_MODE_DEFAULT < new_mode) + ? new_mode + : CLAY_TB_IMAGE_MODE_PLACEHOLDER; + Clay_Termbox_Set_Image_Mode(new_mode); + break; + } + case 't': + case 'T': { + Clay_Termbox_Set_Transparency(!clay_tb_transparency); + } + } + break; + } + case TB_EVENT_MOUSE: { + Clay_Vector2 mousePosition = { + (float)evt.x * Clay_Termbox_Cell_Width(), + (float)evt.y * Clay_Termbox_Cell_Height() + }; + + // Mouse release events may not be produced by all terminals, and will + // be sent during hover, so can't be used to detect when the mouse has + // been released + + switch (evt.key) { + case TB_KEY_MOUSE_LEFT: { + Clay_SetPointerState(mousePosition, true); + break; + } + case TB_KEY_MOUSE_RIGHT: { + Clay_SetPointerState(mousePosition, false); + break; + } + case TB_KEY_MOUSE_MIDDLE: { + Clay_SetPointerState(mousePosition, false); + break; + } + case TB_KEY_MOUSE_RELEASE: { + Clay_SetPointerState(mousePosition, false); + break; + } + case TB_KEY_MOUSE_WHEEL_UP: { + Clay_Vector2 scrollDelta = { 0, 1 * Clay_Termbox_Cell_Height() }; + Clay_UpdateScrollContainers(false, scrollDelta, 1); + break; + } + case TB_KEY_MOUSE_WHEEL_DOWN: { + Clay_Vector2 scrollDelta = { 0, -1 * Clay_Termbox_Cell_Height() }; + Clay_UpdateScrollContainers(false, scrollDelta, 1); + break; + } + default: { + break; + } + } + break; + } + default: { + break; + } + } + break; + } + } +} + +int main(void) +{ + clay_tb_image shark_image1 = Clay_Termbox_Image_Load_File("resources/512px-Shark_antwerp_zoo.jpeg"); + clay_tb_image shark_image2 = Clay_Termbox_Image_Load_File("resources/512px-Shark_antwerp_zoo.jpeg"); + if (NULL == shark_image1.pixel_data) { exit(1); } + if (NULL == shark_image2.pixel_data) { exit(1); } + + int num_elements = 3 * 8192; + Clay_SetMaxElementCount(num_elements); + + uint64_t size = Clay_MinMemorySize(); + void *memory = malloc(size); + if (NULL == memory) { exit(1); } + + Clay_Arena clay_arena = Clay_CreateArenaWithCapacityAndMemory(size, memory); + + Clay_Termbox_Initialize( + TB_OUTPUT_256, CLAY_TB_BORDER_MODE_DEFAULT, CLAY_TB_BORDER_CHARS_DEFAULT, CLAY_TB_IMAGE_MODE_DEFAULT, false); + + Clay_Initialize(clay_arena, (Clay_Dimensions) { Clay_Termbox_Width(), Clay_Termbox_Height() }, + (Clay_ErrorHandler) { handle_clay_errors, NULL }); + + Clay_SetMeasureTextFunction(Clay_Termbox_MeasureText, NULL); + + // Initial render before waiting for events + Clay_RenderCommandArray commands = CreateLayout(&shark_image1, &shark_image2); + Clay_Termbox_Render(commands); + tb_present(); + + while (!end_loop) { + // Block until event is available. Optional, but reduces load since this demo is purely + // synchronous to user input. + Clay_Termbox_Waitfor_Event(); + + handle_termbox_events(); + + commands = CreateLayout(&shark_image1, &shark_image2); + + tb_clear(); + Clay_Termbox_Render(commands); + tb_present(); + } + + Clay_Termbox_Close(); + Clay_Termbox_Image_Free(&shark_image1); + Clay_Termbox_Image_Free(&shark_image2); + free(memory); + return 0; +} diff --git a/examples/termbox2-demo/readme.md b/examples/termbox2-demo/readme.md new file mode 100644 index 0000000..0c1ecd6 --- /dev/null +++ b/examples/termbox2-demo/readme.md @@ -0,0 +1,72 @@ +# Termbox2 renderer demo + +Terminal-based renderer using [termbox2](https://github.com/termbox/termbox2) + +This demo shows a color palette and a few different components. It allows +changing configuration settings for colors, border size rounding mode, +characters used for borders, and transparency. + +``` +Keybinds: +c/C - Cycle through color modes +b/B - Cycle through border modes +h/H - Cycle through border characters +i/I - Cycle through image modes +t/T - Toggle transparency +d/D - Toggle debug mode +q/Q - Quit +``` + +Configuration can be also be overriden by environment variables: +- `CLAY_TB_COLOR_MODE` + - `NORMAL` + - `256` + - `216` + - `GRAYSCALE` + - `TRUECOLOR` + - `NOCOLOR` +- `CLAY_TB_BORDER_CHARS` + - `DEFAULT` + - `ASCII` + - `UNICODE` + - `NONE` +- `CLAY_TB_IMAGE_MODE` + - `DEFAULT` + - `PLACEHOLDER` + - `BG` + - `ASCII_FG` + - `ASCII` + - `UNICODE` + - `ASCII_FG_FAST` + - `ASCII_FAST` + - `UNICODE_FAST` +- `CLAY_TB_TRANSPARENCY` + - `1` + - `0` +- `CLAY_TB_CELL_PIXELS` + - `widthxheight` + +## Building + +Build the binary with cmake + +```sh +mkdir build +cd build +cmake .. +make +``` + +Then run the executable: + +```sh +./clay_examples_termbox2_demo +``` + +## Attributions + +Resources used: +- `512px-Shark_antwerp_zoo.jpeg` + - Retrieved from + - License: [Creative Commons Attribution 3.0 Unported](https://creativecommons.org/licenses/by/3.0/deed.en) + - No changes made diff --git a/examples/termbox2-demo/resources/512px-Shark_antwerp_zoo.jpeg b/examples/termbox2-demo/resources/512px-Shark_antwerp_zoo.jpeg new file mode 100644 index 0000000..a487d8e Binary files /dev/null and b/examples/termbox2-demo/resources/512px-Shark_antwerp_zoo.jpeg differ diff --git a/examples/termbox2-image-demo/CMakeLists.txt b/examples/termbox2-image-demo/CMakeLists.txt new file mode 100644 index 0000000..3efdf32 --- /dev/null +++ b/examples/termbox2-image-demo/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.25) +project(clay_examples_termbox2_image_demo C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_BUILD_TYPE Release) +set(CMAKE_C_FLAGS_RELEASE "-O3") + +include(FetchContent) +set(FETCHCONTENT_QUIET FALSE) + +FetchContent_Declare( + termbox2 + GIT_REPOSITORY "https://github.com/termbox/termbox2.git" + GIT_TAG "9c9281a9a4c971a2be57f8645e828ec99fd555e8" + GIT_PROGRESS TRUE + GIT_SHALLOW TRUE +) +FetchContent_MakeAvailable(termbox2) + +FetchContent_Declare( + stb + GIT_REPOSITORY "https://github.com/nothings/stb.git" + GIT_TAG "f58f558c120e9b32c217290b80bad1a0729fbb2c" + GIT_PROGRESS TRUE + GIT_SHALLOW TRUE +) +FetchContent_MakeAvailable(stb) + +add_executable(clay_examples_termbox2_image_demo main.c) + +target_compile_options(clay_examples_termbox2_image_demo PUBLIC) +target_include_directories(clay_examples_termbox2_image_demo PUBLIC . PRIVATE ${termbox2_SOURCE_DIR} PRIVATE ${stb_SOURCE_DIR}) +target_link_libraries(clay_examples_termbox2_image_demo PRIVATE m) # Used by stb_image.h + +add_custom_command( + TARGET clay_examples_termbox2_image_demo POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/resources + ${CMAKE_CURRENT_BINARY_DIR}/resources) diff --git a/examples/termbox2-image-demo/main.c b/examples/termbox2-image-demo/main.c new file mode 100644 index 0000000..03e1745 --- /dev/null +++ b/examples/termbox2-image-demo/main.c @@ -0,0 +1,707 @@ +/* + Unlicense + + Copyright (c) 2025 Mivirl + + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ + +#define CLAY_IMPLEMENTATION +#include "../../clay.h" +#include "../../renderers/termbox2/clay_renderer_termbox2.c" + +#define TB_IMPL +#include "termbox2.h" + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#include "stb_image_resize2.h" + + +// ------------------------------------------------------------------------------------------------- +// -- Data structures + +struct img_group { + clay_tb_image thumbnail; + clay_tb_image image; + clay_tb_image image_1; + clay_tb_image image_2; + int width; + int height; +}; +typedef struct img_group img_group; + + +// ------------------------------------------------------------------------------------------------- +// -- Internal state + +// If the program should exit the main render/interaction loop +bool end_loop = false; + +// If the debug tools should be displayed +bool debugMode = false; + + +// ------------------------------------------------------------------------------------------------- +// -- Internal utility functions + +img_group img_group_load(const char *filename) +{ + img_group rv; + rv.thumbnail = Clay_Termbox_Image_Load_File(filename); + rv.image = Clay_Termbox_Image_Load_File(filename); + rv.image_1 = Clay_Termbox_Image_Load_File(filename); + rv.image_2 = Clay_Termbox_Image_Load_File(filename); + if (NULL == rv.thumbnail.pixel_data + || NULL == rv.image.pixel_data + || NULL == rv.image_1.pixel_data + || NULL == rv.image_2.pixel_data) { + exit(1); + } + rv.width = rv.image.pixel_width; + rv.height = rv.image.pixel_height; + return rv; +} +void img_group_free(img_group *img) +{ + Clay_Termbox_Image_Free(&img->thumbnail); + Clay_Termbox_Image_Free(&img->image); + Clay_Termbox_Image_Free(&img->image_1); + Clay_Termbox_Image_Free(&img->image_2); +} + + +// ------------------------------------------------------------------------------------------------- +// -- Clay components + +void component_text_pair(const char *key, const char *value) +{ + size_t keylen = strlen(key); + size_t vallen = strlen(value); + Clay_String keytext = (Clay_String) { + .length = keylen, + .chars = key, + }; + Clay_String valtext = (Clay_String) { + .length = vallen, + .chars = value, + }; + Clay_TextElementConfig *textconfig = + CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } }); + CLAY({ + .layout = { + .sizing = { + .width = { + .size.minMax = { + .min = strlen("Border chars CLAY_TB_IMAGE_MODE_UNICODE_FAST") * Clay_Termbox_Cell_Width(), + } + }, + } + }, + }) { + CLAY_TEXT(keytext, textconfig); + CLAY({ .layout = { .sizing = CLAY_SIZING_GROW(1) } }) { } + CLAY_TEXT(valtext, textconfig); + } + +} + +void component_termbox_settings(void) +{ + CLAY({ + .layout = { + .sizing = CLAY_SIZING_FIT(), + .padding = { + 6 * Clay_Termbox_Cell_Width(), + 6 * Clay_Termbox_Cell_Width(), + 2 * Clay_Termbox_Cell_Height(), + 2 * Clay_Termbox_Cell_Height(), + } + }, + .border = { + .width = CLAY_BORDER_ALL(1), + .color = { 0x00, 0x00, 0x00, 0xff } + }, + .backgroundColor = { 0x7f, 0x00, 0x00, 0x7f } + }) { + const char *color_mode = NULL; + switch (clay_tb_color_mode) { + case TB_OUTPUT_NORMAL: { + color_mode = "TB_OUTPUT_NORMAL"; + break; + } + case TB_OUTPUT_256: { + color_mode = "TB_OUTPUT_256"; + break; + } + case TB_OUTPUT_216: { + color_mode = "TB_OUTPUT_216"; + break; + } + case TB_OUTPUT_GRAYSCALE: { + color_mode = "TB_OUTPUT_GRAYSCALE"; + break; + } + case TB_OUTPUT_TRUECOLOR: { + color_mode = "TB_OUTPUT_TRUECOLOR"; + break; + } + case CLAY_TB_OUTPUT_NOCOLOR: { + color_mode = "CLAY_TB_OUTPUT_NOCOLOR"; + break; + } + default: { + color_mode = "INVALID"; + break; + } + } + const char *border_mode = NULL; + switch (clay_tb_border_mode) { + case CLAY_TB_BORDER_MODE_ROUND: { + border_mode = "CLAY_TB_BORDER_MODE_ROUND"; + break; + } + case CLAY_TB_BORDER_MODE_MINIMUM: { + border_mode = "CLAY_TB_BORDER_MODE_MINIMUM"; + break; + } + default: { + border_mode = "INVALID"; + break; + } + } + const char *border_chars = NULL; + switch (clay_tb_border_chars) { + case CLAY_TB_BORDER_CHARS_ASCII: { + border_chars = "CLAY_TB_BORDER_CHARS_ASCII"; + break; + } + case CLAY_TB_BORDER_CHARS_UNICODE: { + border_chars = "CLAY_TB_BORDER_CHARS_UNICODE"; + break; + } + case CLAY_TB_BORDER_CHARS_BLANK: { + border_chars = "CLAY_TB_BORDER_CHARS_BLANK"; + break; + } + case CLAY_TB_BORDER_CHARS_NONE: { + border_chars = "CLAY_TB_BORDER_CHARS_NONE"; + break; + } + default: { + border_chars = "INVALID"; + break; + } + } + const char *image_mode = NULL; + switch (clay_tb_image_mode) { + case CLAY_TB_IMAGE_MODE_PLACEHOLDER: { + image_mode = "CLAY_TB_IMAGE_MODE_PLACEHOLDER"; + break; + } + case CLAY_TB_IMAGE_MODE_BG: { + image_mode = "CLAY_TB_IMAGE_MODE_BG"; + break; + } + case CLAY_TB_IMAGE_MODE_ASCII_FG: { + image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FG"; + break; + } + case CLAY_TB_IMAGE_MODE_ASCII_FG_FAST: { + image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FG_FAST"; + break; + } + case CLAY_TB_IMAGE_MODE_ASCII: { + image_mode = "CLAY_TB_IMAGE_MODE_ASCII"; + break; + } + case CLAY_TB_IMAGE_MODE_ASCII_FAST: { + image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FAST"; + break; + } + case CLAY_TB_IMAGE_MODE_UNICODE: { + image_mode = "CLAY_TB_IMAGE_MODE_UNICODE"; + break; + } + case CLAY_TB_IMAGE_MODE_UNICODE_FAST: { + image_mode = "CLAY_TB_IMAGE_MODE_UNICODE_FAST"; + break; + } + default: { + image_mode = "INVALID"; + break; + } + } + const char *transparency = NULL; + if (clay_tb_transparency) { + transparency = "true"; + } else { + transparency = "false"; + } + + CLAY({ + .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM }, + }) { + component_text_pair("Color mode", color_mode); + component_text_pair("Border mode", border_mode); + component_text_pair("Border chars", border_chars); + component_text_pair("Image mode", image_mode); + component_text_pair("Transparency", transparency); + } + } +} + +void component_keybinds(void) +{ + CLAY({ + .layout = { + .sizing = CLAY_SIZING_FIT(), + .padding = { + 4 * Clay_Termbox_Cell_Width(), + 4 * Clay_Termbox_Cell_Width(), + 2 * Clay_Termbox_Cell_Height(), + 2 * Clay_Termbox_Cell_Height(), + } + }, + .backgroundColor = { 0x00, 0x7f, 0x7f, 0xff } + }) { + CLAY_TEXT( + CLAY_STRING( + "Termbox2 renderer test\n" + "\n" + "Keybinds:\n" + " up/down arrows - Change selected image\n" + " c/C - Cycle through color modes\n" + " b/B - Cycle through border modes\n" + " h/H - Cycle through border characters\n" + " i/I - Cycle through image modes\n" + " t/T - Toggle transparency\n" + " d/D - Toggle debug mode\n" + " q/Q - Quit\n" + ), + CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff }}) + ); + } +} + +void component_image(img_group *img_pair) +{ + CLAY({ + .layout = { + .sizing = { + .width = CLAY_SIZING_GROW(), + .height = CLAY_SIZING_GROW() + }, + .layoutDirection = CLAY_TOP_TO_BOTTOM, + .childAlignment = { + .x = CLAY_ALIGN_X_CENTER, + .y = CLAY_ALIGN_Y_CENTER + }, + .childGap = 1 * Clay_Termbox_Cell_Height() + }, + .backgroundColor = { 0x24, 0x24, 0x24, 0xff } + }) { + CLAY({ + .layout = { + .sizing = { + .width = CLAY_SIZING_GROW(), + }, + }, + .image = { + .imageData = &img_pair->image, + }, + .aspectRatio = { (float)img_pair->width / img_pair->height } + }) { } + component_keybinds(); + } +} + +void component_image_small(img_group **img_pairs, int count, int selected_index) +{ + CLAY({ + .layout = { + .sizing = { + .width = CLAY_SIZING_PERCENT(0.25), + }, + .layoutDirection = CLAY_TOP_TO_BOTTOM, + .childGap = 20, + .childAlignment = { + .x = CLAY_ALIGN_X_CENTER, + .y = CLAY_ALIGN_Y_CENTER + }, + }, + }) { + CLAY({ + .layout = { + .sizing = { + .width = CLAY_SIZING_PERCENT(0.7), + }, + }, + .image = { + .imageData = &img_pairs[selected_index]->image_1, + }, + .aspectRatio = { (float)img_pairs[selected_index]->width / img_pairs[selected_index]->height } + }) { } + CLAY({ + .layout = { + .sizing = { + .width = CLAY_SIZING_GROW(), + }, + }, + .image = { + .imageData = &img_pairs[selected_index]->image_2, + }, + .aspectRatio = { (float)img_pairs[selected_index]->width / img_pairs[selected_index]->height } + }) { } + component_termbox_settings(); + } +} + +void component_thumbnails(img_group **img_pairs, int count, int selected_index) +{ + CLAY({ + .layout = { + .sizing = { + .width = CLAY_SIZING_PERCENT(0.1), + .height = CLAY_SIZING_GROW() + }, + .layoutDirection = CLAY_TOP_TO_BOTTOM, + .childGap = 20 + }, + .backgroundColor = { 0x42, 0x42, 0x42, 0xff } + }) { + for (int i = 0; i < count; ++i) { + Clay_BorderElementConfig border; + if (i == selected_index) { + border = (Clay_BorderElementConfig) { + .width =CLAY_BORDER_OUTSIDE(10), + .color = { 0x00, 0x30, 0xc0, 0x8f } + }; + } else { + border = (Clay_BorderElementConfig) { + .width = CLAY_BORDER_OUTSIDE(0), + }; + } + CLAY({ + .layout = { + .sizing = { + .width = CLAY_SIZING_GROW(), + }, + }, + .border = border, + .image = { + .imageData = &img_pairs[i]->thumbnail, + }, + .aspectRatio = { (float)img_pairs[i]->width / img_pairs[i]->height } + }) { } + } + } +} + +int selected_thumbnail = 0; +const int thumbnail_count = 5; +Clay_RenderCommandArray CreateLayout(struct img_group **imgs) +{ + Clay_BeginLayout(); + CLAY({ + .layout = { + .sizing = { + .width = CLAY_SIZING_GROW(), + .height = CLAY_SIZING_GROW() + }, + .childAlignment = { + .x = CLAY_ALIGN_X_LEFT, + .y = CLAY_ALIGN_Y_CENTER + }, + .childGap = 64 + }, + .backgroundColor = { 0x24, 0x24, 0x24, 0xff } + }) { + component_thumbnails(imgs, thumbnail_count, selected_thumbnail); + component_image_small(imgs, thumbnail_count, selected_thumbnail); + component_image(imgs[selected_thumbnail]); + } + return Clay_EndLayout(); +} + + +// ------------------------------------------------------------------------------------------------- +// -- Interactive functions + +void handle_clay_errors(Clay_ErrorData errorData) +{ + Clay_Termbox_Close(); + fprintf(stderr, "%s", errorData.errorText.chars); + exit(1); +} + +/** + Process events received from termbox2 and handle interaction + */ +void handle_termbox_events(void) +{ + // Wait up to 100ms for an event (key/mouse press, terminal resize) before continuing + // If an event is already available, this doesn't wait. Will not wait due to the previous call + // to termbox_waitfor_event. Increasing the wait time reduces load without reducing + // responsiveness (but will of course prevent other code from running on this thread while it's + // waiting) + struct tb_event evt; + int ms_to_wait = 0; + int err = tb_peek_event(&evt, ms_to_wait); + + switch (err) { + default: + case TB_ERR_NO_EVENT: { + break; + } + case TB_ERR_POLL: { + if (EINTR != tb_last_errno()) { + Clay_Termbox_Close(); + fprintf(stderr, "Failed to read event from TTY\n"); + exit(1); + } + break; + } + case TB_OK: { + switch (evt.type) { + case TB_EVENT_RESIZE: { + Clay_SetLayoutDimensions((Clay_Dimensions) { + Clay_Termbox_Width(), + Clay_Termbox_Height() + }); + break; + } + case TB_EVENT_KEY: { + if (TB_KEY_CTRL_C == evt.key) { + end_loop = true; + break; + } + if (0 != evt.ch) { + switch (evt.ch) { + case 'q': + case 'Q': { + end_loop = true; + break; + } + case 'd': + case 'D': { + debugMode = !debugMode; + Clay_SetDebugModeEnabled(debugMode); + break; + } + case 'c': { + int new_mode = clay_tb_color_mode - 1; + new_mode = (0 <= new_mode) ? new_mode : TB_OUTPUT_TRUECOLOR; + Clay_Termbox_Set_Color_Mode(new_mode); + break; + } + case 'C': { + int new_mode = (clay_tb_color_mode + 1) % (TB_OUTPUT_TRUECOLOR + 1); + Clay_Termbox_Set_Color_Mode(new_mode); + break; + } + case 'b': { + enum border_mode new_mode = clay_tb_border_mode - 1; + new_mode = (CLAY_TB_BORDER_MODE_DEFAULT < new_mode) + ? new_mode + : CLAY_TB_BORDER_MODE_MINIMUM; + Clay_Termbox_Set_Border_Mode(new_mode); + break; + } + case 'B': { + enum border_mode new_mode = (clay_tb_border_mode + 1) + % (CLAY_TB_BORDER_MODE_MINIMUM + 1); + new_mode = (CLAY_TB_BORDER_MODE_DEFAULT < new_mode) + ? new_mode + : CLAY_TB_BORDER_MODE_ROUND; + Clay_Termbox_Set_Border_Mode(new_mode); + break; + } + case 'h': { + enum border_chars new_chars = clay_tb_border_chars - 1; + new_chars = (CLAY_TB_BORDER_CHARS_DEFAULT < new_chars) + ? new_chars + : CLAY_TB_BORDER_CHARS_NONE; + Clay_Termbox_Set_Border_Chars(new_chars); + break; + } + case 'H': { + enum border_chars new_chars + = (clay_tb_border_chars + 1) % (CLAY_TB_BORDER_CHARS_NONE + 1); + new_chars = (CLAY_TB_BORDER_CHARS_DEFAULT < new_chars) + ? new_chars + : CLAY_TB_BORDER_CHARS_ASCII; + Clay_Termbox_Set_Border_Chars(new_chars); + break; + } + case 'i': { + enum image_mode new_mode = clay_tb_image_mode - 1; + new_mode = (CLAY_TB_IMAGE_MODE_DEFAULT < new_mode) + ? new_mode + : CLAY_TB_IMAGE_MODE_UNICODE_FAST; + Clay_Termbox_Set_Image_Mode(new_mode); + break; + } + case 'I': { + enum image_mode new_mode = (clay_tb_image_mode + 1) + % (CLAY_TB_IMAGE_MODE_UNICODE_FAST + 1); + new_mode = (CLAY_TB_IMAGE_MODE_DEFAULT < new_mode) + ? new_mode + : CLAY_TB_IMAGE_MODE_PLACEHOLDER; + Clay_Termbox_Set_Image_Mode(new_mode); + break; + } + case 't': + case 'T': { + Clay_Termbox_Set_Transparency(!clay_tb_transparency); + } + } + } else if (0 != evt.key) { + switch (evt.key) { + case TB_KEY_ARROW_UP: { + selected_thumbnail = (selected_thumbnail > 0) ? selected_thumbnail - 1 : 0; + break; + } + case TB_KEY_ARROW_DOWN: { + selected_thumbnail = (selected_thumbnail < thumbnail_count - 1) ? selected_thumbnail + 1 : thumbnail_count - 1; + break; + } + } + } + break; + } + case TB_EVENT_MOUSE: { + Clay_Vector2 mousePosition = { + (float)evt.x * Clay_Termbox_Cell_Width(), + (float)evt.y * Clay_Termbox_Cell_Height() + }; + + // Mouse release events may not be produced by all terminals, and will + // be sent during hover, so can't be used to detect when the mouse has + // been released + + switch (evt.key) { + case TB_KEY_MOUSE_LEFT: { + Clay_SetPointerState(mousePosition, true); + break; + } + case TB_KEY_MOUSE_RIGHT: { + Clay_SetPointerState(mousePosition, false); + break; + } + case TB_KEY_MOUSE_MIDDLE: { + Clay_SetPointerState(mousePosition, false); + break; + } + case TB_KEY_MOUSE_RELEASE: { + Clay_SetPointerState(mousePosition, false); + break; + } + case TB_KEY_MOUSE_WHEEL_UP: { + Clay_Vector2 scrollDelta = { 0, 1 * Clay_Termbox_Cell_Height() }; + Clay_UpdateScrollContainers(false, scrollDelta, 1); + break; + } + case TB_KEY_MOUSE_WHEEL_DOWN: { + Clay_Vector2 scrollDelta = { 0, -1 * Clay_Termbox_Cell_Height() }; + Clay_UpdateScrollContainers(false, scrollDelta, 1); + break; + } + default: { + break; + } + } + break; + } + default: { + break; + } + } + break; + } + } +} + +int main(void) +{ + img_group *imgs[thumbnail_count]; + img_group img_shark = img_group_load("resources/512px-Shark_antwerp_zoo.jpeg"); + img_group img_castle = img_group_load("resources/512px-Balmoral_Castle_30_July_2011.jpeg"); + img_group img_dog = img_group_load("resources/512px-German_Shepherd_(aka_Alsatian_and_Alsatian_Wolf_Dog),_Deutscher_Schäferhund_(Folder_(IV)_22.jpeg"); + img_group img_rosa = img_group_load("resources/512px-Rosa_Cubana_2018-09-21_1610.jpeg"); + img_group img_vorderer = img_group_load("resources/512px-Vorderer_Graben_10_Bamberg_20190830_001.jpeg"); + imgs[0] = &img_shark; + imgs[1] = &img_castle; + imgs[2] = &img_dog; + imgs[3] = &img_rosa; + imgs[4] = &img_vorderer; + + int num_elements = 3 * 8192; + Clay_SetMaxElementCount(num_elements); + + uint64_t size = Clay_MinMemorySize(); + void *memory = malloc(size); + if (NULL == memory) { exit(1); } + + Clay_Arena clay_arena = Clay_CreateArenaWithCapacityAndMemory(size, memory); + + Clay_Termbox_Initialize( + TB_OUTPUT_256, CLAY_TB_BORDER_MODE_DEFAULT, CLAY_TB_BORDER_CHARS_DEFAULT, CLAY_TB_IMAGE_MODE_DEFAULT, false); + + Clay_Initialize(clay_arena, (Clay_Dimensions) { Clay_Termbox_Width(), Clay_Termbox_Height() }, + (Clay_ErrorHandler) { handle_clay_errors, NULL }); + + Clay_SetMeasureTextFunction(Clay_Termbox_MeasureText, NULL); + + // Initial render before waiting for events + Clay_RenderCommandArray commands = CreateLayout(imgs); + Clay_Termbox_Render(commands); + tb_present(); + + while (!end_loop) { + // Block until event is available. Optional, but reduces load since this demo is purely + // synchronous to user input. + Clay_Termbox_Waitfor_Event(); + + handle_termbox_events(); + + commands = CreateLayout(imgs); + + Clay_Termbox_Render(commands); + tb_present(); + } + + Clay_Termbox_Close(); + img_group_free(&img_shark); + img_group_free(&img_castle); + img_group_free(&img_dog); + img_group_free(&img_rosa); + img_group_free(&img_vorderer); + free(memory); + return 0; +} diff --git a/examples/termbox2-image-demo/readme.md b/examples/termbox2-image-demo/readme.md new file mode 100644 index 0000000..6241a40 --- /dev/null +++ b/examples/termbox2-image-demo/readme.md @@ -0,0 +1,88 @@ +# Termbox2 renderer demo + +Terminal-based renderer using [termbox2](https://github.com/termbox/termbox2) + +This demo shows a color palette and a few different components. It allows +changing configuration settings for colors, border size rounding mode, +characters used for borders, and transparency. + +``` +Keybinds: +c/C - Cycle through color modes +b/B - Cycle through border modes +h/H - Cycle through border characters +i/I - Cycle through image modes +t/T - Toggle transparency +d/D - Toggle debug mode +q/Q - Quit +``` + +Configuration can be also be overriden by environment variables: +- `CLAY_TB_COLOR_MODE` + - `NORMAL` + - `256` + - `216` + - `GRAYSCALE` + - `TRUECOLOR` + - `NOCOLOR` +- `CLAY_TB_BORDER_CHARS` + - `DEFAULT` + - `ASCII` + - `UNICODE` + - `NONE` +- `CLAY_TB_IMAGE_MODE` + - `DEFAULT` + - `PLACEHOLDER` + - `BG` + - `ASCII_FG` + - `ASCII` + - `UNICODE` + - `ASCII_FG_FAST` + - `ASCII_FAST` + - `UNICODE_FAST` +- `CLAY_TB_TRANSPARENCY` + - `1` + - `0` +- `CLAY_TB_CELL_PIXELS` + - `widthxheight` + +## Building + +Build the binary with cmake + +```sh +mkdir build +cd build +cmake .. +make +``` + +Then run the executable: + +```sh +./clay_examples_termbox2_demo +``` + +## Attributions + +Resources used: +- `512px-Shark_antwerp_zoo.jpeg` + - Retrieved from + - License: [Creative Commons Attribution 3.0 Unported](https://creativecommons.org/licenses/by/3.0/) + - No changes made +- `512px-Balmoral_Castle_30_July_2011.jpg` + - Retrieved from + - License: [Creative Commons Attribution-ShareAlike 3.0 Unported](https://creativecommons.org/licenses/by-sa/3.0/) + - No changes made +- `512px-German_Shepherd_(aka_Alsatian_and_Alsatian_Wolf_Dog),_Deutscher_Schäferhund_(Folder_(IV)_22.jpeg` + - Retrieved from + - License: [Creative Commons Attribution-ShareAlike 3.0 Unported](https://creativecommons.org/licenses/by-sa/3.0/) + - No changes made +- `512px-Rosa_Cubana_2018-09-21_1610.jpeg` + - Retrieved from + - License: [Creative Commons Attribution-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-sa/4.0/) + - No changes made +- `512px-Vorderer_Graben_10_Bamberg_20190830_001.jpeg` + - Retrieved from + - License: [Creative Commons Attribution-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-sa/4.0/) + - No changes made diff --git a/examples/termbox2-image-demo/resources/512px-Balmoral_Castle_30_July_2011.jpeg b/examples/termbox2-image-demo/resources/512px-Balmoral_Castle_30_July_2011.jpeg new file mode 100644 index 0000000..051a604 Binary files /dev/null and b/examples/termbox2-image-demo/resources/512px-Balmoral_Castle_30_July_2011.jpeg differ diff --git a/examples/termbox2-image-demo/resources/512px-German_Shepherd_(aka_Alsatian_and_Alsatian_Wolf_Dog),_Deutscher_Schäferhund_(Folder_(IV)_22.jpeg b/examples/termbox2-image-demo/resources/512px-German_Shepherd_(aka_Alsatian_and_Alsatian_Wolf_Dog),_Deutscher_Schäferhund_(Folder_(IV)_22.jpeg new file mode 100644 index 0000000..2c79f5e Binary files /dev/null and b/examples/termbox2-image-demo/resources/512px-German_Shepherd_(aka_Alsatian_and_Alsatian_Wolf_Dog),_Deutscher_Schäferhund_(Folder_(IV)_22.jpeg differ diff --git a/examples/termbox2-image-demo/resources/512px-Rosa_Cubana_2018-09-21_1610.jpeg b/examples/termbox2-image-demo/resources/512px-Rosa_Cubana_2018-09-21_1610.jpeg new file mode 100644 index 0000000..82c79fe Binary files /dev/null and b/examples/termbox2-image-demo/resources/512px-Rosa_Cubana_2018-09-21_1610.jpeg differ diff --git a/examples/termbox2-image-demo/resources/512px-Shark_antwerp_zoo.jpeg b/examples/termbox2-image-demo/resources/512px-Shark_antwerp_zoo.jpeg new file mode 100644 index 0000000..a487d8e Binary files /dev/null and b/examples/termbox2-image-demo/resources/512px-Shark_antwerp_zoo.jpeg differ diff --git a/examples/termbox2-image-demo/resources/512px-Vorderer_Graben_10_Bamberg_20190830_001.jpeg b/examples/termbox2-image-demo/resources/512px-Vorderer_Graben_10_Bamberg_20190830_001.jpeg new file mode 100644 index 0000000..8bd8a22 Binary files /dev/null and b/examples/termbox2-image-demo/resources/512px-Vorderer_Graben_10_Bamberg_20190830_001.jpeg differ diff --git a/renderers/termbox2/clay_renderer_termbox2.c b/renderers/termbox2/clay_renderer_termbox2.c new file mode 100644 index 0000000..338b88b --- /dev/null +++ b/renderers/termbox2/clay_renderer_termbox2.c @@ -0,0 +1,1776 @@ +/* + zlib/libpng license + + Copyright (c) 2025 Mivirl + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "../../clay.h" + +#include "image_character_masks.h" + +#define TB_OPT_ATTR_W 32 // Required for truecolor support +#include "termbox2.h" + +#include "stb_image.h" +#include "stb_image_resize2.h" + +// ------------------------------------------------------------------------------------------------- +// -- Data structures + +typedef struct { + int width, height; +} clay_tb_dimensions; + +typedef struct { + float width, height; +} clay_tb_pixel_dimensions; + +typedef struct { + int x, y; + int width, height; +} clay_tb_cell_bounding_box; + +typedef struct { + Clay_Color clay; + uintattr_t termbox; +} clay_tb_color_pair; + +enum border_mode { + CLAY_TB_BORDER_MODE_DEFAULT, + CLAY_TB_BORDER_MODE_ROUND, + CLAY_TB_BORDER_MODE_MINIMUM, +}; + +enum border_chars { + CLAY_TB_BORDER_CHARS_DEFAULT, + CLAY_TB_BORDER_CHARS_ASCII, + CLAY_TB_BORDER_CHARS_UNICODE, + CLAY_TB_BORDER_CHARS_BLANK, + CLAY_TB_BORDER_CHARS_NONE, +}; + +enum image_mode { + CLAY_TB_IMAGE_MODE_DEFAULT, + CLAY_TB_IMAGE_MODE_PLACEHOLDER, + CLAY_TB_IMAGE_MODE_BG, + CLAY_TB_IMAGE_MODE_ASCII_FG, + CLAY_TB_IMAGE_MODE_ASCII_FG_FAST, + CLAY_TB_IMAGE_MODE_ASCII, + CLAY_TB_IMAGE_MODE_ASCII_FAST, + CLAY_TB_IMAGE_MODE_UNICODE, + CLAY_TB_IMAGE_MODE_UNICODE_FAST, +}; + +typedef struct { + // Stores information about image loaded from stb + int pixel_width, pixel_height; + unsigned char *pixel_data; + + // Internal cached data from previous renders + struct { + enum image_mode last_image_mode; + int width, height; + size_t size_max; + uint32_t *characters; + Clay_Color *foreground; + Clay_Color *background; + + // Data storing progress of partially complete image conversions that take multiple renders + struct clay_tb_partial_render { + bool in_progress; + unsigned char *resized_pixel_data; + int cursor_x, cursor_y; + int cursor_mask; + int min_difference_squared_sum; + int best_mask; + Clay_Color best_foreground, best_background; + } partial_render; + } internal; +} clay_tb_image; + +// Truecolor is only enabled if TB_OPT_ATTR_W is set to 32 or 64. The default is 16, so it must be +// defined to reference the constant +#ifndef TB_OUTPUT_TRUECOLOR +#define TB_OUTPUT_TRUECOLOR (TB_OUTPUT_GRAYSCALE + 1) +#endif + +// Constant that doesn't collide with termbox2's existing output modes +#define CLAY_TB_OUTPUT_NOCOLOR 0 + +#if !(defined NDEBUG || defined CLAY_TB_NDEBUG) +#define clay_tb_assert(condition, ...) \ + if (!(condition)) { \ + Clay_Termbox_Close(); \ + fprintf(stderr, "%s %d (%s): Assertion failure: ", __FILE__, __LINE__, __func__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + exit(1); \ + } +#else +#define clay_tb_assert(condition, ...) +#endif // NDEBUG || CLAY_TB_NDEBUG + + +// ------------------------------------------------------------------------------------------------- +// -- Public API + +/** + Set the equivalent size for a terminal cell in pixels. + + This size is used to convert Clay's pixel measurements to terminal cells, and + affects scaling. + + Default dimensions were measured on Debian 12: (9, 21) + + \param width Width of a terminal cell in pixels + \param height Height of a terminal cell in pixels +*/ +void Clay_Termbox_Set_Cell_Pixel_Size(float width, float height); + +/** + Sets the color rendering mode for the terminal + + \param color_mode Termbox output mode as defined in termbox2.h, excluding truecolor + - TB_OUTPUT_NORMAL - Use default ANSI colors + - TB_OUTPUT_256 - Use 256 terminal colors + - TB_OUTPUT_216 - Use 216 terminal colors from 256 color mode + - TB_OUTPUT_GRAYSCALE - Use 24 gray colors from 256 color mode + - CLAY_TB_OUTPUT_NOCOLOR - Don't use ANSI colors at all + */ +void Clay_Termbox_Set_Color_Mode(int color_mode); + +/** + Sets the method for converting the width of borders to terminal cells + + \param border_mode Method for adjusting border sizes to fit terminal cells + - CLAY_TB_BORDER_MODE_DEFAULT - same as CLAY_TB_BORDER_MODE_MINIMUM + - CLAY_TB_BORDER_MODE_ROUND - borders will be rounded to nearest cell + size + - CLAY_TB_BORDER_MODE_MINIMUM - borders will have a minimum width of one + cell + */ +void Clay_Termbox_Set_Border_Mode(enum border_mode border_mode); + +/** + Sets the character style to use for rendering borders + + \param border_chars Characters used for rendering borders + - CLAY_TB_BORDER_CHARS_DEFAULT - same as BORDER_UNICODE + - CLAY_TB_BORDER_CHARS_ASCII - Uses ascii characters: '+', '|', '-' + - CLAY_TB_BORDER_CHARS_UNICODE - Uses unicode box drawing characters + - CLAY_TB_BORDER_CHARS_BLANK - Draws background colors only + - CLAY_TB_BORDER_CHARS_NONE - Don't draw borders + */ +void Clay_Termbox_Set_Border_Chars(enum border_chars border_chars); + +/** + Sets the method for drawing images + + \param image_mode Method for adjusting border sizes to fit terminal cells + - CLAY_TB_IMAGE_MODE_DEFAULT - same as CLAY_TB_IMAGE_MODE_UNICODE + - CLAY_TB_IMAGE_MODE_PLACEHOLDER - Draw a placeholder pattern in place of + images + - CLAY_TB_IMAGE_MODE_BG - Draw image by setting the background color + for space characters + - CLAY_TB_IMAGE_MODE_ASCII_FG - Draw image by setting the foreground color + for ascii characters + - CLAY_TB_IMAGE_MODE_ASCII - Draw image by setting the foreground and + background colors for ascii characters + - CLAY_TB_IMAGE_MODE_UNICODE - Draw image by setting the foreground and + background colors for unicode characters + - CLAY_TB_IMAGE_MODE_ASCII_FG_FAST - Draw image by setting the foreground color + for ascii characters. Checks fewer + characters to draw faster + - CLAY_TB_IMAGE_MODE_ASCII_FAST - Draw image by setting the foreground and + background colors for ascii characters. + Checks fewer characters to draw faster + - CLAY_TB_IMAGE_MODE_UNICODE_FAST - Draw image by setting the foreground and + background colors for unicode characters. + Checks fewer characters to draw faster + */ +void Clay_Termbox_Set_Image_Mode(enum image_mode image_mode); + +/** + Fuel corresponds to the amount of time spent per render on drawing images. Increasing this has + the image render faster, but the program will be less responsive until it finishes + + Cost to draw one cell (lengths of arrays in image_character_masks.h): + - 1 : CLAY_TB_IMAGE_MODE_BG + - 15 : CLAY_TB_IMAGE_MODE_UNICODE_FAST, CLAY_TB_IMAGE_MODE_ASCII_FAST, + CLAY_TB_IMAGE_MODE_ASCII_FG_FAST + - 52 : CLAY_TB_IMAGE_MODE_UNICODE + - 95 : CLAY_TB_IMAGE_MODE_ASCII, CLAY_TB_IMAGE_MODE_ASCII_FG + + \param fuel_max Maximum amount of fuel used per render (shared between all images) + \param fuel_per_image Maximum amount of fuel used per render per image + */ +void Clay_Termbox_Set_Image_Fuel(int fuel_max, int fuel_per_image); + +/** + Enables or disables emulated transparency + + If the color mode is TB_OUTPUT_NORMAL or CLAY_TB_OUTPUT_NOCOLOR, transparency will not be enabled + + \param transparency Transparency value to set + */ +void Clay_Termbox_Set_Transparency(bool transparency); + +/** + Current width of the terminal in pixels +*/ +float Clay_Termbox_Width(void); + +/** + Current height of the terminal in pixels +*/ +float Clay_Termbox_Height(void); + +/** + Current width of a terminal cell in pixels +*/ +float Clay_Termbox_Cell_Width(void); + +/** + Current height of a terminal cell in pixels +*/ +float Clay_Termbox_Cell_Height(void); + +/** + Callback function used to measure the dimensions in pixels of a text string + + \param text Text to measure + \param config Ignored + \param userData Ignored + */ +static inline Clay_Dimensions Clay_Termbox_MeasureText( + Clay_StringSlice text, Clay_TextElementConfig *config, void *userData); + +/** + Load an image from a file into a format usable with this renderer + + Supports image formats from stb_image (JPG, PNG, TGA, BMP, PSD, GIF, HDR, PIC) + + Note that rendered characters are cached in the returned `clay_tb_image`. If the same image is + used in multiple places, load it a separate time for each use to reduce unecessary reprocessing + every render. + + \param filename File to load image from + */ +clay_tb_image Clay_Termbox_Image_Load_File(const char *filename); + +/** + Load an image from memory into a format usable with this renderer + + Supports image formats from stb_image (JPG, PNG, TGA, BMP, PSD, GIF, HDR, PIC) + + Note that rendered characters are cached in the returned `clay_tb_image`. If the same image is + used in multiple places, load it a separate time for each use to reduce unecessary reprocessing + every render. + + \param image Image to load. Should be the whole file copied into memory + \param size Size of the image in bytes + */ +clay_tb_image Clay_Termbox_Image_Load_Memory(const void *image, int size); + +/** + Free an image + + \param image Image to free + */ +void Clay_Termbox_Image_Free(clay_tb_image *image); + +/** + Set up configuration, start termbox2, and allocate internal structures. + + Configuration can be overriden by environment variables: + - CLAY_TB_COLOR_MODE + - NORMAL + - 256 + - 216 + - GRAYSCALE + - TRUECOLOR + - NOCOLOR + - CLAY_TB_BORDER_CHARS + - DEFAULT + - ASCII + - UNICODE + - BLANK + - NONE + - CLAY_TB_IMAGE_MODE + - DEFAULT + - PLACEHOLDER + - BG + - ASCII_FG + - ASCII + - UNICODE + - ASCII_FG_FAST + - ASCII_FAST + - UNICODE_FAST + - CLAY_TB_TRANSPARENCY + - 1 + - 0 + - CLAY_TB_CELL_PIXELS + - 10x20 + + Must be run before using this renderer. + + \param color_mode Termbox output mode as defined in termbox2.h, excluding truecolor + \param border_mode Method for adjusting border sizes to fit terminal cells + \param border_chars Characters used for rendering borders + \param image_mode Method for drawing images + \param transparency Emulate transparency using background colors + */ +void Clay_Termbox_Initialize(int color_mode, enum border_mode border_mode, + enum border_chars border_chars, enum image_mode image_mode, bool transparency); + +/** + Stop termbox2 and release internal structures + */ +void Clay_Termbox_Close(void); + +/** + Render a set of commands to the terminal + + \param commands Array of render commands from Clay's CreateLayout() function + */ +void Clay_Termbox_Render(Clay_RenderCommandArray commands); + +/** + Convenience function to block until an event is received from termbox. If an image is only + partially rendered, this returns immediately. + */ +void Clay_Termbox_Waitfor_Event(void); + + +// ------------------------------------------------------------------------------------------------- +// -- Internal state + +// Settings/options +static bool clay_tb_initialized = false; +static int clay_tb_color_mode = TB_OUTPUT_NORMAL; +static bool clay_tb_transparency = false; +static enum border_mode clay_tb_border_mode = CLAY_TB_BORDER_MODE_DEFAULT; +static enum border_chars clay_tb_border_chars = CLAY_TB_BORDER_CHARS_DEFAULT; +static enum image_mode clay_tb_image_mode = CLAY_TB_IMAGE_MODE_DEFAULT; + +// Dimensions of a cell are specified in pixels +// Default dimensions were measured from the default terminal on Debian 12: +// Terminal: gnome-terminal +// Font: "Monospace Regular" +// Font size: 11 +static clay_tb_pixel_dimensions clay_tb_cell_size = { .width = 9, .height = 21 }; + +// Scissor mode prevents drawing outside of the specified bounding box +static bool clay_tb_scissor_enabled = false; +clay_tb_cell_bounding_box clay_tb_scissor_box; + +// Images may be drawn across multiple renders to improve responsiveness. The initial draw will be +// approximate, then further partial draws will replace characters with more accurate ones +static bool clay_tb_partial_image_drawn = false; + + +// Maximum fuel used per render across all images +static int clay_tb_image_fuel_max = 200 * 1024; +// Maximum fuel used per render per image +static int clay_tb_image_fuel_per_image = 100 * 1024; +// Fuel used this render +static int clay_tb_image_fuel_used = 0; +// ----------------------------------------------- +// -- Color buffer + +// Buffer storing background colors from previously drawn items. Used to emulate transparency and +// set the background color for text. +static Clay_Color *clay_tb_color_buffer_clay = NULL; +// Dimensions are specified in cells +static clay_tb_dimensions clay_tb_color_buffer_dimensions = { 0, 0 }; +static clay_tb_dimensions clay_tb_color_buffer_max_dimensions = { 0, 0 }; + + +// ------------------------------------------------------------------------------------------------- +// -- Internal utility functions + +static inline bool clay_tb_valid_color(Clay_Color color) +{ + return ( + 0x00 <= color.r && color.r <= 0xff && + 0x00 <= color.g && color.g <= 0xff && + 0x00 <= color.b && color.b <= 0xff && + 0x00 <= color.a && color.a <= 0xff + ); +} + +/** + In 256-color mode, there are 216 colors (excluding default terminal colors and gray colors), + with 6 different magnitudes for each of r, g, b. + + This function clamps to the nearest intensity (represented by 0-5) that can be output in this mode + + Possible intensities per component: 0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff + + Examples: + - 0x20 -> 0 + - 0x2f -> 1 + - 0x85 -> 2 + - 0xff -> 5 + + \param color 8-bit intensity of one RGB component +*/ +static int clay_tb_rgb_intensity_to_index(int color) +{ + clay_tb_assert(0x00 <= color && color <= 0xff, "Invalid intensity (allowed range 0x00-0xff)"); + return (color < 0x2f) ? 0 + : (color < 0x73) ? 1 + : 2 + ((color - 0x73) / 0x28); +} + +/** + Convert an RGB color from Clay's representation to the nearest representable color in the current + termbox2 output mode + + \param color Color to convert + */ +static uintattr_t clay_tb_color_convert(Clay_Color color) +{ + clay_tb_assert(clay_tb_valid_color(color), "Invalid Clay color: (%f, %f, %f, %f)", color.r, + color.g, color.b, color.a); + + uintattr_t tb_color = TB_DEFAULT; + + switch (clay_tb_color_mode) { + default: { + clay_tb_assert(false, "Invalid or unimplemented Termbox color output mode (%d)", + clay_tb_color_mode); + break; + } + case TB_OUTPUT_NORMAL: { + const int color_lut_count = 16; + const uintattr_t color_lut[][4] = { + { TB_BLACK, 0x00, 0x00, 0x00 }, + { TB_RED, 0xaa, 0x00, 0x00 }, + { TB_GREEN, 0x00, 0xaa, 0x00 }, + { TB_YELLOW, 0xaa, 0x55, 0x00 }, + { TB_BLUE, 0x00, 0x00, 0xaa }, + { TB_MAGENTA, 0xaa, 0x00, 0xaa }, + { TB_CYAN, 0x00, 0xaa, 0xaa }, + { TB_WHITE, 0xaa, 0xaa, 0xaa }, + { TB_BLACK | TB_BRIGHT, 0x55, 0x55, 0x55 }, + { TB_RED | TB_BRIGHT, 0xff, 0x55, 0x55 }, + { TB_GREEN | TB_BRIGHT, 0x55, 0xff, 0x55 }, + { TB_YELLOW | TB_BRIGHT, 0xff, 0xff, 0x55 }, + { TB_BLUE | TB_BRIGHT, 0x55, 0x55, 0xff }, + { TB_MAGENTA | TB_BRIGHT, 0xff, 0x55, 0xff }, + { TB_CYAN | TB_BRIGHT, 0x55, 0xff, 0xff }, + { TB_WHITE | TB_BRIGHT, 0xff, 0xff, 0xff } + }; + + // Find nearest color on the lookup table + int color_index = 0; + float min_distance_squared = 0xff * 0xff * 3; + for (int i = 0; i < color_lut_count; ++i) { + float r_distance = color.r - (float)color_lut[i][1]; + float g_distance = color.g - (float)color_lut[i][2]; + float b_distance = color.b - (float)color_lut[i][3]; + + float distance_squared = + (r_distance * r_distance) + + (g_distance * g_distance) + + (b_distance * b_distance); + + // Penalize pure black and white to display faded colors more often + if (TB_BLACK == color_lut[i][0] || TB_WHITE == color_lut[i][0] + || (TB_BLACK | TB_BRIGHT) == color_lut[i][0] + || (TB_WHITE | TB_BRIGHT) == color_lut[i][0]) { + distance_squared *= 2; + } + + if (distance_squared < min_distance_squared) { + min_distance_squared = distance_squared; + color_index = i; + } + } + tb_color = color_lut[color_index][0]; + break; + } + case TB_OUTPUT_216: { + int r_index = clay_tb_rgb_intensity_to_index((int)color.r); + int g_index = clay_tb_rgb_intensity_to_index((int)color.g); + int b_index = clay_tb_rgb_intensity_to_index((int)color.b); + + tb_color = 0x01 + (36 * r_index) + (6 * g_index) + (b_index); + break; + } + case TB_OUTPUT_256: { + const int index_lut_count = 6; + const uintattr_t index_lut[] = { 0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff }; + + int r_index = clay_tb_rgb_intensity_to_index((int)color.r); + int g_index = clay_tb_rgb_intensity_to_index((int)color.g); + int b_index = clay_tb_rgb_intensity_to_index((int)color.b); + + int rgb_color = 0x10 + (36 * r_index) + (6 * g_index) + (b_index); + + float rgb_r_distance = color.r - (float)index_lut[r_index]; + float rgb_g_distance = color.g - (float)index_lut[g_index]; + float rgb_b_distance = color.b - (float)index_lut[b_index]; + + float rgb_distance_squared = + (rgb_r_distance * rgb_r_distance) + + (rgb_g_distance * rgb_g_distance) + + (rgb_b_distance * rgb_b_distance); + + int avg_color = (int)((color.r + color.g + color.b) / 3); + int gray_avg_color = (avg_color * 24 / 0x100); + int gray_color = 0xe8 + gray_avg_color; + + float gray_r_distance = color.r - (float)gray_avg_color; + float gray_g_distance = color.g - (float)gray_avg_color; + float gray_b_distance = color.b - (float)gray_avg_color; + + float gray_distance_squared = + (gray_r_distance * gray_r_distance) + + (gray_g_distance * gray_g_distance) + + (gray_b_distance * gray_b_distance); + + tb_color = (rgb_distance_squared < gray_distance_squared) ? rgb_color : gray_color; + + break; + } + case TB_OUTPUT_GRAYSCALE: { + // 24 shades of gray + float avg_color = ((color.r + color.g + color.b) / 3); + tb_color = 0x01 + (int)(avg_color * 24 / 0x100); + break; + } + case TB_OUTPUT_TRUECOLOR: { + clay_tb_assert(32 <= TB_OPT_ATTR_W, "Truecolor requires TB_OPT_ATTR_W to be 32 or 64"); + tb_color = ((uintattr_t)color.r << 4 * 4) + ((uintattr_t)color.g << 2 * 4) + + ((uintattr_t)color.b); + if (0x000000 == tb_color) { + tb_color = TB_HI_BLACK; + } + break; + } + case CLAY_TB_OUTPUT_NOCOLOR: { + // Uses default terminal colors + tb_color = TB_DEFAULT; + break; + } + } + return tb_color; +} + +/** + Round float to nearest integer value + + Used instead of roundf() so math.h doesn't need to be linked + + \param f Float to round + */ +static inline int clay_tb_roundf(float f) +{ + int i = f; + return (f - i > 0.5f) ? i + 1 : i; +} + +/** + Snap pixel values from Clay to nearest cell values + + Width/height accounts for offset from x/y, so a box at x=(1.2 * cell_width) and + width=(1.4 * cell_width) is snapped to x=1 and width=2. + + \param box Bounding box with pixel measurements to convert + */ +static inline clay_tb_cell_bounding_box cell_snap_bounding_box(Clay_BoundingBox box) +{ + return (clay_tb_cell_bounding_box) { + .x = clay_tb_roundf(box.x / clay_tb_cell_size.width), + .y = clay_tb_roundf(box.y / clay_tb_cell_size.height), + .width = clay_tb_roundf((box.x + box.width) / clay_tb_cell_size.width) + - clay_tb_roundf(box.x / clay_tb_cell_size.width), + .height = clay_tb_roundf((box.y + box.height) / clay_tb_cell_size.height) + - clay_tb_roundf(box.y / clay_tb_cell_size.height), + }; +} + +/** + Snap pixel values from Clay to nearest cell values without considering x and y position when + calculating width/height. + + Width/height ignores offset from x/y, so a box at x=(1.2 * cell_width) and + width=(1.4 * cell_width) is snapped to x=1 and width=1. + + \param box Bounding box with pixel measurements to convert + */ +static inline clay_tb_cell_bounding_box cell_snap_pos_ind_bounding_box(Clay_BoundingBox box) +{ + return (clay_tb_cell_bounding_box) { + .x = clay_tb_roundf(box.x / clay_tb_cell_size.width), + .y = clay_tb_roundf(box.y / clay_tb_cell_size.height), + .width = clay_tb_roundf(box.width / clay_tb_cell_size.width), + .height = clay_tb_roundf(box.height / clay_tb_cell_size.height), + }; +} + +/** + Get stored clay color for a position from the internal color buffer + + \param x X position of cell + \param y Y position of cell + */ +static inline Clay_Color clay_tb_color_buffer_clay_get(int x, int y) +{ + clay_tb_assert(0 <= x && x < clay_tb_color_buffer_dimensions.width, + "Cell buffer x position (%d) offscreen (range 0-%d)", x, + clay_tb_color_buffer_dimensions.width); + clay_tb_assert(0 <= y && y < clay_tb_color_buffer_dimensions.height, + "Cell buffer y position (%d) offscreen (range 0-%d)", y, + clay_tb_color_buffer_dimensions.height); + return clay_tb_color_buffer_clay[x + (y * clay_tb_color_buffer_dimensions.width)]; +} + +/** + Set stored clay color for a position in the internal color buffer + + \param x X position of cell + \param y Y position of cell + \param color Color to store + */ +static inline void clay_tb_color_buffer_clay_set(int x, int y, Clay_Color color) +{ + clay_tb_assert(0 <= x && x < clay_tb_color_buffer_dimensions.width, + "Cell buffer x position (%d) offscreen (range 0-%d)", x, + clay_tb_color_buffer_dimensions.width); + clay_tb_assert(0 <= y && y < clay_tb_color_buffer_dimensions.height, + "Cell buffer y position (%d) offscreen (range 0-%d)", y, + clay_tb_color_buffer_dimensions.height); + clay_tb_color_buffer_clay[x + (y * clay_tb_color_buffer_dimensions.width)] = color; +} + +/** + Resize internal color buffer to the current terminal size + */ +static void clay_tb_resize_buffer(void) +{ + int current_width = tb_width(); + int current_height = tb_height(); + + // Reallocate if the new size is larger than the maximum size of the buffer + size_t max_size = (size_t)clay_tb_color_buffer_max_dimensions.width + * clay_tb_color_buffer_max_dimensions.height; + size_t new_size = (size_t)current_width * current_height; + if (max_size < new_size) { + Clay_Color *tmp_clay = tb_realloc(clay_tb_color_buffer_clay, sizeof(Clay_Color) * new_size); + if (NULL == tmp_clay) { + clay_tb_assert(false, "Reallocation failure for internal clay color buffer"); + } + clay_tb_color_buffer_clay = tmp_clay; + for (size_t i = max_size; i < new_size; ++i) { + clay_tb_color_buffer_clay[i] = (Clay_Color) { 0 }; + } + + clay_tb_color_buffer_max_dimensions.width = current_width; + clay_tb_color_buffer_max_dimensions.height = current_height; + } + clay_tb_color_buffer_dimensions.width = current_width; + clay_tb_color_buffer_dimensions.height = current_height; +} + +/** + Calculate color at a given position after emulating transparency. + + This isn't true transparency, just the background colors changing to emulate it + + \param color Pair of termbox and clay color representations to overlay with the background color + \param x X position of cell + \param y Y position of cell + */ +static inline clay_tb_color_pair clay_tb_get_transparency_color( + int x, int y, clay_tb_color_pair color) +{ + if (!clay_tb_transparency) { + return color; + } + + Clay_Color color_bg = clay_tb_color_buffer_clay_get(x, y); + Clay_Color new_color = { + .r = color_bg.r + (color.clay.a / 255) * (color.clay.r - color_bg.r), + .g = color_bg.g + (color.clay.a / 255) * (color.clay.g - color_bg.g), + .b = color_bg.b + (color.clay.a / 255) * (color.clay.b - color_bg.b), + .a = 255 + }; + + return (clay_tb_color_pair) { + .clay = new_color, + .termbox = clay_tb_color_convert(new_color) + }; +} + +/** + Draw a character cell at a position on screen. + + Accounts for scissor mode and stores the cell to the internal color buffer for transparency and + text backgrounds. + + \param x X position of cell + \param y Y position of cell + \param ch Utf32 representation of character to draw + \param tb_fg Foreground color in termbox representation + \param tb_bg Background color in termbox representation + \param fg Foreground color in clay representation + \param bg Background color in clay representation + */ +static int clay_tb_set_cell( + int x, int y, uint32_t ch, uintattr_t tb_fg, uintattr_t tb_bg, Clay_Color bg) +{ + clay_tb_assert(0 <= x && x < tb_width(), "Cell buffer x position (%d) offscreen (range 0-%d)", + x, tb_width()); + clay_tb_assert(0 <= y && y < tb_height(), "Cell buffer y position (%d) offscreen (range 0-%d)", + y, tb_height()); + + if (!clay_tb_scissor_enabled + || (clay_tb_scissor_enabled + && (clay_tb_scissor_box.x <= x + && x < clay_tb_scissor_box.x + clay_tb_scissor_box.width) + && (clay_tb_scissor_box.y <= y + && y < clay_tb_scissor_box.y + clay_tb_scissor_box.height))) { + int codepoint_width = tb_wcwidth(ch); + if (-1 == codepoint_width) { + // Nonprintable character, use REPLACEMENT CHARACTER (U+FFFD) + ch = U'\ufffd'; + codepoint_width = tb_wcwidth(ch); + } + + int err; + int max_x = CLAY__MIN(x + codepoint_width, tb_width()); + for (int i = x; i < max_x; ++i) { + clay_tb_color_buffer_clay_set(i, y, bg); + err = tb_set_cell(i, y, ch, tb_fg, tb_bg); + if (TB_OK != err) { + break; + } + } + + return err; + } + return -1; +} + +/** + Convert a pixel-based image to a cell-based image of the specified width and height. Stores the + converted/resized result in the cache of the input image. + + If the image has not changed size or image mode since the last convert it is returned unchanged + + \param image Image to convert/resize + \param width Target width in cells for the converted image + \param height Target height in cells for the converted image + */ +bool clay_tb_image_convert(clay_tb_image *image, int width, int height) +{ + clay_tb_assert(NULL != image->pixel_data, "Image must be loaded"); + + bool image_unchanged = (width == image->internal.width && height == image->internal.height + && (clay_tb_image_mode == image->internal.last_image_mode)); + + if (image_unchanged && !image->internal.partial_render.in_progress) { + return true; + } + if (!image_unchanged) { + free(image->internal.partial_render.resized_pixel_data); + image->internal.partial_render = (struct clay_tb_partial_render) { + .in_progress = false, + .resized_pixel_data = NULL, + .cursor_x = 0, + .cursor_y = 0, + .cursor_mask = 0, + .min_difference_squared_sum = INT_MAX, + .best_mask = 0, + .best_foreground = { 0, 0, 0, 0 }, + .best_background = { 0, 0, 0, 0 } + }; + } + + const size_t size = (size_t)width * height; + + // Allocate/resize internal cache data + if (size > image->internal.size_max) { + uint32_t *tmp_characters = realloc(image->internal.characters, size * sizeof(uint32_t)); + Clay_Color *tmp_foreground = realloc(image->internal.foreground, size * sizeof(Clay_Color)); + Clay_Color *tmp_background = realloc(image->internal.background, size * sizeof(Clay_Color)); + + if (NULL == tmp_characters || NULL == tmp_foreground || NULL == tmp_background) { + image->internal.size_max = 0; + free(tmp_characters); + free(tmp_foreground); + free(tmp_background); + image->internal.characters = NULL; + image->internal.foreground = NULL; + image->internal.background = NULL; + return false; + } + image->internal.characters = tmp_characters; + image->internal.foreground = tmp_foreground; + image->internal.background = tmp_background; + image->internal.size_max = size; + } + + image->internal.width = width; + image->internal.height = height; + + // Resize image using the same width/height in cells, but with the pixel sizes of the character + // masks instead of the cell size. The pixel data for each character mask will be compared to + // the pixel data of a small section of the image under the mask. The closest mask to the image + // data is chosen as the character to draw. + const int character_mask_pixel_width = 6; + const int character_mask_pixel_height = 12; + const int pixel_width = width * character_mask_pixel_width; + const int pixel_height = height * character_mask_pixel_height; + + unsigned char *resized_pixel_data; + if (image->internal.partial_render.in_progress) { + resized_pixel_data = image->internal.partial_render.resized_pixel_data; + } else { + resized_pixel_data = stbir_resize_uint8_linear(image->pixel_data, image->pixel_width, + image->pixel_height, 0, NULL, pixel_width, pixel_height, 0, STBIR_RGB); + image->internal.partial_render.resized_pixel_data = resized_pixel_data; + } + + int num_character_masks = 1; + const clay_tb_character_mask *character_masks = NULL; + switch (clay_tb_image_mode) { + case CLAY_TB_IMAGE_MODE_BG: { + num_character_masks = 1; + character_masks = &clay_tb_image_shapes_ascii_fast[0]; + break; + } + case CLAY_TB_IMAGE_MODE_ASCII: + case CLAY_TB_IMAGE_MODE_ASCII_FG: { + num_character_masks = CLAY_TB_IMAGE_SHAPES_ASCII_BEST_COUNT; + character_masks = &clay_tb_image_shapes_ascii_best[0]; + break; + } + case CLAY_TB_IMAGE_MODE_UNICODE: { + num_character_masks = CLAY_TB_IMAGE_SHAPES_UNICODE_BEST_COUNT; + character_masks = &clay_tb_image_shapes_unicode_best[0]; + break; + } + case CLAY_TB_IMAGE_MODE_ASCII_FAST: + case CLAY_TB_IMAGE_MODE_ASCII_FG_FAST: { + num_character_masks = CLAY_TB_IMAGE_SHAPES_ASCII_FAST_COUNT; + character_masks = &clay_tb_image_shapes_ascii_fast[0]; + break; + } + case CLAY_TB_IMAGE_MODE_UNICODE_FAST: { + num_character_masks = CLAY_TB_IMAGE_SHAPES_UNICODE_FAST_COUNT; + character_masks = &clay_tb_image_shapes_unicode_fast[0]; + break; + } + }; + + // The number of character masks to check before exiting the render for this step + // Used to improve responsiveness by splitting renders across multiple frames + const int fuel_amount_initial + = CLAY__MIN(clay_tb_image_fuel_per_image, clay_tb_image_fuel_max - clay_tb_image_fuel_used); + int fuel_remaining = fuel_amount_initial; + bool partial_character_render = false; + + // Do a quick initial render to set the background + if (!image->internal.partial_render.in_progress) { + image->internal.last_image_mode = clay_tb_image_mode; + for (int y = image->internal.partial_render.cursor_y; y < height; ++y) { + for (int x = image->internal.partial_render.cursor_x; x < width; ++x) { + const int cell_top_left_pixel_x = x * character_mask_pixel_width; + const int cell_top_left_pixel_y = y * character_mask_pixel_height; + const int image_index = 3 + * (((cell_top_left_pixel_y + character_mask_pixel_height / 2) * pixel_width) + + (cell_top_left_pixel_x + character_mask_pixel_width / 2)); + Clay_Color pixel_color = { + (float)resized_pixel_data[image_index], + (float)resized_pixel_data[image_index + 1], + (float)resized_pixel_data[image_index + 2], + }; + + const int cell_index = y * width + x; + image->internal.characters[cell_index] = '.'; + image->internal.foreground[cell_index] = pixel_color; + image->internal.background[cell_index] = pixel_color; + + fuel_remaining = CLAY__MAX(0, fuel_remaining - 1); + } + } + } + + if (0 == fuel_remaining) { + image->internal.partial_render.in_progress = true; + clay_tb_partial_image_drawn = true; + goto done; + } + + for (int y = image->internal.partial_render.cursor_y; y < height; ++y) { + for (int x = image->internal.partial_render.cursor_x; x < width; ++x) { + const int cell_top_left_pixel_x = x * character_mask_pixel_width; + const int cell_top_left_pixel_y = y * character_mask_pixel_height; + + // For each possible cell character, use the mask to find the average color for the + // foreground ('1's) and background ('0's). + int min_difference_squared_sum + = image->internal.partial_render.min_difference_squared_sum; + int best_mask = image->internal.partial_render.best_mask; + Clay_Color best_foreground = image->internal.partial_render.best_foreground; + Clay_Color best_background = image->internal.partial_render.best_background; + + for (int i = image->internal.partial_render.cursor_mask; i < num_character_masks; ++i) { + int color_avg_background_r = 0; + int color_avg_background_g = 0; + int color_avg_background_b = 0; + int color_avg_foreground_r = 0; + int color_avg_foreground_g = 0; + int color_avg_foreground_b = 0; + int foreground_count = 0; + int background_count = 0; + + for (int cell_pixel_y = 0; cell_pixel_y < character_mask_pixel_height; + ++cell_pixel_y) { + for (int cell_pixel_x = 0; cell_pixel_x < character_mask_pixel_width; + ++cell_pixel_x) { + const int index = 3 + * (((cell_top_left_pixel_y + cell_pixel_y) * pixel_width) + + (cell_top_left_pixel_x + cell_pixel_x)); + + const int mask_index + = (cell_pixel_y * character_mask_pixel_width) + cell_pixel_x; + if (0 == character_masks[i].data[mask_index]) { + if (CLAY_TB_IMAGE_MODE_ASCII_FG != clay_tb_image_mode + && CLAY_TB_IMAGE_MODE_ASCII_FG_FAST != clay_tb_image_mode) { + color_avg_background_r += resized_pixel_data[index]; + color_avg_background_g += resized_pixel_data[index + 1]; + color_avg_background_b += resized_pixel_data[index + 2]; + background_count += 1; + } + } else { + color_avg_foreground_r += resized_pixel_data[index]; + color_avg_foreground_g += resized_pixel_data[index + 1]; + color_avg_foreground_b += resized_pixel_data[index + 2]; + foreground_count += 1; + } + } + } + + if (CLAY_TB_IMAGE_MODE_ASCII_FG != clay_tb_image_mode + && CLAY_TB_IMAGE_MODE_ASCII_FG_FAST != clay_tb_image_mode) { + color_avg_background_r /= CLAY__MAX(1, background_count); + color_avg_background_g /= CLAY__MAX(1, background_count); + color_avg_background_b /= CLAY__MAX(1, background_count); + } else { + color_avg_background_r = 0; + color_avg_background_g = 0; + color_avg_background_b = 0; + } + + color_avg_foreground_r /= CLAY__MAX(1, foreground_count); + color_avg_foreground_g /= CLAY__MAX(1, foreground_count); + color_avg_foreground_b /= CLAY__MAX(1, foreground_count); + + // Determine the difference between the mask with colors and the actual pixel data + int difference_squared_sum = 0; + for (int cell_pixel_y = 0; cell_pixel_y < character_mask_pixel_height; + ++cell_pixel_y) { + for (int cell_pixel_x = 0; cell_pixel_x < character_mask_pixel_width; + ++cell_pixel_x) { + const int index = 3 + * (((cell_top_left_pixel_y + cell_pixel_y) * pixel_width) + + (cell_top_left_pixel_x + cell_pixel_x)); + int rdiff, gdiff, bdiff, adiff; + + const int mask_index + = (cell_pixel_y * character_mask_pixel_width) + cell_pixel_x; + if (0 == character_masks[i].data[mask_index]) { + rdiff = (color_avg_background_r - resized_pixel_data[index]); + gdiff = (color_avg_background_g - resized_pixel_data[index + 1]); + bdiff = (color_avg_background_b - resized_pixel_data[index + 2]); + } else { + rdiff = (color_avg_foreground_r - resized_pixel_data[index]); + gdiff = (color_avg_foreground_g - resized_pixel_data[index + 1]); + bdiff = (color_avg_foreground_b - resized_pixel_data[index + 2]); + } + + difference_squared_sum += ( + (rdiff * rdiff) + + (gdiff * gdiff) + + (bdiff * bdiff)); + } + } + + // Choose the closest character mask to the image data + if (difference_squared_sum < min_difference_squared_sum) { + min_difference_squared_sum = difference_squared_sum; + best_mask = i; + best_background = (Clay_Color) { + .r = (float)color_avg_background_r, + .g = (float)color_avg_background_g, + .b = (float)color_avg_background_b, + .a = 255 + }; + best_foreground = (Clay_Color) { + .r = (float)color_avg_foreground_r, + .g = (float)color_avg_foreground_g, + .b = (float)color_avg_foreground_b, + .a = 255 + }; + } + + fuel_remaining -= 1; + if (0 == fuel_remaining) { + // Set progress for partial render + image->internal.partial_render = (struct clay_tb_partial_render) { + .in_progress = true, + .resized_pixel_data = resized_pixel_data, + .cursor_x = x, + .cursor_y = y, + .cursor_mask = i + 1, + .min_difference_squared_sum = min_difference_squared_sum, + .best_mask = best_mask, + .best_foreground = best_foreground, + .best_background = best_background + }; + partial_character_render = true; + clay_tb_partial_image_drawn = true; + goto done; + } + } + image->internal.partial_render.cursor_mask = 0; + + // Set data in cache for this character + const int index = y * width + x; + image->internal.characters[index] = character_masks[best_mask].character; + image->internal.foreground[index] = best_foreground; + image->internal.background[index] = best_background; + + image->internal.partial_render = (struct clay_tb_partial_render) { + .in_progress = true, + .resized_pixel_data = resized_pixel_data, + .cursor_x = x + 1, + .cursor_y = y, + .cursor_mask = 0, + .min_difference_squared_sum = INT_MAX, + .best_mask = 0, + .best_foreground = { 0, 0, 0, 0 }, + .best_background = { 0, 0, 0, 0 }, + }; + if (0 == fuel_remaining) { + clay_tb_partial_image_drawn = true; + goto done; + } + } + image->internal.partial_render.cursor_x = 0; + } + image->internal.partial_render.cursor_y = 0; + image->internal.partial_render.in_progress = false; + free(resized_pixel_data); + image->internal.partial_render.resized_pixel_data = NULL; + +done: + clay_tb_image_fuel_used += fuel_amount_initial - fuel_remaining; + return true; +} + + +// ------------------------------------------------------------------------------------------------- +// -- Public API implementation + +void Clay_Termbox_Set_Cell_Pixel_Size(float width, float height) +{ + clay_tb_assert(0 <= width, "Cell pixel width must be > 0"); + clay_tb_assert(0 <= height, "Cell pixel height must be > 0"); + clay_tb_cell_size = (clay_tb_pixel_dimensions) { .width = width, .height = height }; +} + +void Clay_Termbox_Set_Color_Mode(int color_mode) +{ + clay_tb_assert(clay_tb_initialized, "Clay_Termbox_Initialize must be run first"); + clay_tb_assert(CLAY_TB_OUTPUT_NOCOLOR <= color_mode && color_mode <= TB_OUTPUT_TRUECOLOR, + "Color mode invalid (%d)", color_mode); + + if (CLAY_TB_OUTPUT_NOCOLOR == color_mode) { + tb_set_output_mode(TB_OUTPUT_NORMAL); + } else { + tb_set_output_mode(color_mode); + } + + // Force complete re-render to ensure all colors are redrawn + tb_invalidate(); + + clay_tb_color_mode = color_mode; + + // Re-set transparency value. It will be toggled off if the new output mode doesn't support it + Clay_Termbox_Set_Transparency(clay_tb_transparency); +} + +void Clay_Termbox_Set_Border_Mode(enum border_mode border_mode) +{ + clay_tb_assert(CLAY_TB_BORDER_MODE_DEFAULT <= border_mode + && border_mode <= CLAY_TB_BORDER_MODE_MINIMUM, + "Border mode invalid (%d)", border_mode); + if (CLAY_TB_BORDER_MODE_DEFAULT == border_mode) { + clay_tb_border_mode = CLAY_TB_BORDER_MODE_MINIMUM; + } else { + clay_tb_border_mode = border_mode; + } +} + +void Clay_Termbox_Set_Border_Chars(enum border_chars border_chars) +{ + clay_tb_assert( + CLAY_TB_BORDER_CHARS_DEFAULT <= border_chars && border_chars <= CLAY_TB_BORDER_CHARS_NONE, + "Border mode invalid (%d)", border_chars); + if (CLAY_TB_BORDER_CHARS_DEFAULT == border_chars) { + clay_tb_border_chars = CLAY_TB_BORDER_CHARS_UNICODE; + } else { + clay_tb_border_chars = border_chars; + } +} + +void Clay_Termbox_Set_Image_Mode(enum image_mode image_mode) +{ + clay_tb_assert(CLAY_TB_IMAGE_MODE_DEFAULT <= image_mode + && image_mode <= CLAY_TB_IMAGE_MODE_UNICODE_FAST, + "Image mode invalid (%d)", image_mode); + if (CLAY_TB_IMAGE_MODE_DEFAULT == image_mode) { + clay_tb_image_mode = CLAY_TB_IMAGE_MODE_UNICODE; + } else { + clay_tb_image_mode = image_mode; + } +} + +void Clay_Termbox_Set_Image_Fuel(int fuel_max, int fuel_per_image) +{ + clay_tb_assert(0 < fuel_max && 0 < fuel_per_image, + "Fuel must be positive (%d, %d)", fuel_max, fuel_per_image); + clay_tb_image_fuel_max = fuel_max; + clay_tb_image_fuel_per_image = fuel_per_image; +} + +void Clay_Termbox_Set_Transparency(bool transparency) +{ + clay_tb_transparency = transparency; + if (TB_OUTPUT_NORMAL == clay_tb_color_mode || CLAY_TB_OUTPUT_NOCOLOR == clay_tb_color_mode) { + clay_tb_transparency = false; + } +} + +float Clay_Termbox_Width(void) +{ + clay_tb_assert(clay_tb_initialized, "Clay_Termbox_Initialize must be run first"); + + return (float)tb_width() * clay_tb_cell_size.width; +} + +float Clay_Termbox_Height(void) +{ + clay_tb_assert(clay_tb_initialized, "Clay_Termbox_Initialize must be run first"); + + return (float)tb_height() * clay_tb_cell_size.height; +} + +float Clay_Termbox_Cell_Width(void) +{ + return clay_tb_cell_size.width; +} + +float Clay_Termbox_Cell_Height(void) +{ + return clay_tb_cell_size.height; +} + +static inline Clay_Dimensions Clay_Termbox_MeasureText( + Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) +{ + clay_tb_assert(clay_tb_initialized, "Clay_Termbox_Initialize must be run first"); + + int width = 0; + int height = 1; + + // Convert to utf32 so termbox2's internal wcwidth function can get the printed width of each + // codepoint + for (int32_t i = 0; i < text.length;) { + uint32_t ch; + int codepoint_bytes = tb_utf8_char_to_unicode(&ch, text.chars + i); + if (0 > codepoint_bytes) { + clay_tb_assert(false, "Invalid utf8"); + } + i += codepoint_bytes; + + int codepoint_width = tb_wcwidth(ch); + if (-1 == codepoint_width) { + // Nonprintable character, use width of REPLACEMENT CHARACTER (U+FFFD) + codepoint_width = tb_wcwidth(0xfffd); + } + width += codepoint_width; + } + return (Clay_Dimensions) { + (float)width * clay_tb_cell_size.width, + (float)height * clay_tb_cell_size.height + }; +} + +clay_tb_image Clay_Termbox_Image_Load_File(const char *filename) +{ + clay_tb_assert(NULL != filename, "Filename cannot be null"); + + clay_tb_image rv = { 0 }; + + FILE *image_file = NULL; + + image_file = fopen(filename, "r"); + if (NULL == image_file) { + fprintf(stderr, "Failed to open image %s: %s\n", filename, strerror(errno)); + return rv; + } + + int channels_in_file; + const int desired_color_channels = 3; + rv.pixel_data = stbi_load_from_file( + image_file, &rv.pixel_width, &rv.pixel_height, &channels_in_file, desired_color_channels); + + fclose(image_file); + + return rv; +} + +clay_tb_image Clay_Termbox_Image_Load_Memory(const void *image, int size) +{ + clay_tb_assert(NULL != image, "Image cannot be null"); + clay_tb_assert(0 < size, "Image size must be > 0"); + + clay_tb_image rv = { 0 }; + + int channels_in_file; + const int desired_color_channels = 3; + rv.pixel_data = stbi_load_from_memory( + image, size, &rv.pixel_width, &rv.pixel_height, &channels_in_file, desired_color_channels); + + return rv; +} + +void Clay_Termbox_Image_Free(clay_tb_image *image) +{ + free(image->pixel_data); + free(image->internal.partial_render.resized_pixel_data); + free(image->internal.characters); + free(image->internal.foreground); + free(image->internal.background); + *image = (clay_tb_image) { 0 }; +} + +void Clay_Termbox_Initialize(int color_mode, enum border_mode border_mode, + enum border_chars border_chars, enum image_mode image_mode, bool transparency) +{ + int new_color_mode = color_mode; + int new_border_mode = border_mode; + int new_border_chars = border_chars; + int new_image_mode = image_mode; + int new_transparency = transparency; + clay_tb_pixel_dimensions new_pixel_size = clay_tb_cell_size; + + // Check for environment variables that override settings + + const char *env_color_mode = getenv("CLAY_TB_COLOR_MODE"); + if (NULL != env_color_mode) { + if (0 == strcmp("NORMAL", env_color_mode)) { + new_color_mode = TB_OUTPUT_NORMAL; + } else if (0 == strcmp("256", env_color_mode)) { + new_color_mode = TB_OUTPUT_256; + } else if (0 == strcmp("216", env_color_mode)) { + new_color_mode = TB_OUTPUT_216; + } else if (0 == strcmp("GRAYSCALE", env_color_mode)) { + new_color_mode = TB_OUTPUT_GRAYSCALE; + } else if (0 == strcmp("TRUECOLOR", env_color_mode)) { + new_color_mode = TB_OUTPUT_TRUECOLOR; + } else if (0 == strcmp("NOCOLOR", env_color_mode)) { + new_color_mode = CLAY_TB_OUTPUT_NOCOLOR; + } + } + + const char *env_border_chars = getenv("CLAY_TB_BORDER_CHARS"); + if (NULL != env_border_chars) { + if (0 == strcmp("DEFAULT", env_border_chars)) { + new_border_chars = CLAY_TB_BORDER_CHARS_DEFAULT; + } else if (0 == strcmp("ASCII", env_border_chars)) { + new_border_chars = CLAY_TB_BORDER_CHARS_ASCII; + } else if (0 == strcmp("UNICODE", env_border_chars)) { + new_border_chars = CLAY_TB_BORDER_CHARS_UNICODE; + } else if (0 == strcmp("BLANK", env_border_chars)) { + new_border_chars = CLAY_TB_BORDER_CHARS_BLANK; + } else if (0 == strcmp("NONE", env_border_chars)) { + new_border_chars = CLAY_TB_BORDER_CHARS_NONE; + } + } + + const char *env_image_mode = getenv("CLAY_TB_IMAGE_MODE"); + if (NULL != env_image_mode) { + if (0 == strcmp("DEFAULT", env_image_mode)) { + new_image_mode = CLAY_TB_IMAGE_MODE_DEFAULT; + } else if (0 == strcmp("PLACEHOLDER", env_image_mode)) { + new_image_mode = CLAY_TB_IMAGE_MODE_PLACEHOLDER; + } else if (0 == strcmp("BG", env_image_mode)) { + new_image_mode = CLAY_TB_IMAGE_MODE_BG; + } else if (0 == strcmp("ASCII_FG", env_image_mode)) { + new_image_mode = CLAY_TB_IMAGE_MODE_ASCII_FG; + } else if (0 == strcmp("ASCII", env_image_mode)) { + new_image_mode = CLAY_TB_IMAGE_MODE_ASCII; + } else if (0 == strcmp("UNICODE", env_image_mode)) { + new_image_mode = CLAY_TB_IMAGE_MODE_UNICODE; + } else if (0 == strcmp("ASCII_FG_FAST", env_image_mode)) { + new_image_mode = CLAY_TB_IMAGE_MODE_ASCII_FG_FAST; + } else if (0 == strcmp("ASCII_FAST", env_image_mode)) { + new_image_mode = CLAY_TB_IMAGE_MODE_ASCII_FAST; + } else if (0 == strcmp("UNICODE_FAST", env_image_mode)) { + new_image_mode = CLAY_TB_IMAGE_MODE_UNICODE_FAST; + } + } + + const char *env_transparency = getenv("CLAY_TB_TRANSPARENCY"); + if (NULL != env_transparency) { + if (0 == strcmp("1", env_transparency)) { + new_transparency = true; + } else if (0 == strcmp("0", env_transparency)) { + new_transparency = false; + } + } + + const char *env_cell_pixels = getenv("CLAY_TB_CELL_PIXELS"); + if (NULL != env_cell_pixels) { + const char *str_width = env_cell_pixels; + const char *str_height = strstr(env_cell_pixels, "x") + 1; + if (NULL + 1 != str_height) { + bool missing_value = false; + + errno = 0; + float cell_width = strtof(str_width, NULL); + if (0 != errno || 0 > cell_width) { + missing_value = true; + } + float cell_height = strtof(str_height, NULL); + if (0 != errno || 0 >= cell_height) { + missing_value = true; + } + + if (!missing_value) { + new_pixel_size = (clay_tb_pixel_dimensions) { cell_width, cell_height }; + } + } + } + + // NO_COLOR indicates that ANSI colors shouldn't be used: https://no-color.org/ + const char *env_nocolor = getenv("NO_COLOR"); + if (NULL != env_nocolor && '\0' != env_nocolor[0]) { + new_color_mode = CLAY_TB_OUTPUT_NOCOLOR; + } + + tb_init(); + tb_set_input_mode(TB_INPUT_MOUSE); + + // Enable mouse hover support + // - see https://github.com/termbox/termbox2/issues/71#issuecomment-2179581609 + // - 1003 "Any-event tracking" mode + // - 1006 SGR extended coordinates (already enabled with TB_INPUT_MOUSE) + tb_sendf("\x1b[?%d;%dh", 1003, 1006); + + clay_tb_initialized = true; + + Clay_Termbox_Set_Color_Mode(new_color_mode); + Clay_Termbox_Set_Border_Mode(new_border_mode); + Clay_Termbox_Set_Border_Chars(new_border_chars); + Clay_Termbox_Set_Image_Mode(new_image_mode); + Clay_Termbox_Set_Transparency(new_transparency); + Clay_Termbox_Set_Cell_Pixel_Size(new_pixel_size.width, new_pixel_size.height); + + size_t size = (size_t)tb_width() * tb_height(); + clay_tb_color_buffer_clay = tb_malloc(sizeof(Clay_Color) * size); + for (int i = 0; i < size; ++i) { + clay_tb_color_buffer_clay[i] = (Clay_Color) { 0, 0, 0, 0 }; + } +} + +void Clay_Termbox_Close(void) +{ + if (clay_tb_initialized) { + // Disable mouse hover support + tb_sendf("\x1b[?%d;%dl", 1003, 1006); + + tb_free(clay_tb_color_buffer_clay); + tb_shutdown(); + clay_tb_initialized = false; + } +} + +void Clay_Termbox_Render(Clay_RenderCommandArray commands) +{ + clay_tb_assert(clay_tb_initialized, "Clay_Termbox_Initialize must be run first"); + + clay_tb_resize_buffer(); + clay_tb_partial_image_drawn = false; + clay_tb_image_fuel_used = 0; + + for (int32_t i = 0; i < commands.length; ++i) { + const Clay_RenderCommand *command = Clay_RenderCommandArray_Get(&commands, i); + const clay_tb_cell_bounding_box cell_box = cell_snap_bounding_box(command->boundingBox); + + int box_begin_x = CLAY__MAX(cell_box.x, 0); + int box_end_x = CLAY__MIN(cell_box.x + cell_box.width, tb_width()); + int box_begin_y = CLAY__MAX(cell_box.y, 0); + int box_end_y = CLAY__MIN(cell_box.y + cell_box.height, tb_height()); + + if (box_end_x < 0 || box_end_y < 0 || tb_width() < box_begin_x + || tb_height() < box_begin_y) { + continue; + } + + switch (command->commandType) { + default: { + clay_tb_assert(false, "Unhandled command: %d\n", command->commandType); + } + case CLAY_RENDER_COMMAND_TYPE_NONE: { + break; + } + case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { + Clay_RectangleRenderData render_data = command->renderData.rectangle; + Clay_Color color_fg = { 0, 0, 0, 0 }; + Clay_Color color_bg = render_data.backgroundColor; + uintattr_t color_tb_fg = TB_DEFAULT; + uintattr_t color_tb_bg = clay_tb_color_convert(color_bg); + + for (int y = box_begin_y; y < box_end_y; ++y) { + for (int x = box_begin_x; x < box_end_x; ++x) { + clay_tb_color_pair color_bg_new = clay_tb_get_transparency_color( + x, y, (clay_tb_color_pair) { color_bg, color_tb_bg }); + clay_tb_set_cell( + x, y, ' ', color_tb_fg, color_bg_new.termbox, color_bg_new.clay); + } + } + break; + } + case CLAY_RENDER_COMMAND_TYPE_BORDER: { + if (CLAY_TB_BORDER_CHARS_NONE == clay_tb_border_chars) { + break; + } + Clay_BorderRenderData render_data = command->renderData.border; + Clay_Color color_fg = { 0, 0, 0, 1 }; + Clay_Color color_bg = render_data.color; + uintattr_t color_tb_fg = TB_DEFAULT; + uintattr_t color_tb_bg = clay_tb_color_convert(color_bg); + + int border_skip_begin_x = box_begin_x; + int border_skip_end_x = box_end_x; + int border_skip_begin_y = box_begin_y; + int border_skip_end_y = box_end_y; + + switch (clay_tb_border_mode) { + default: { + clay_tb_assert(false, "Invalid or unimplemented border mode (%d)", + clay_tb_border_mode); + break; + } + case CLAY_TB_BORDER_MODE_MINIMUM: { + // Borders will be at least one cell wide if width is nonzero + // and the bounding box is large enough to not be all borders + if (0 < cell_box.width) { + if (0 < render_data.width.left) { + border_skip_begin_x = box_begin_x + + (int)CLAY__MAX( + 1, (render_data.width.left / clay_tb_cell_size.width)); + } + if (0 < render_data.width.right) { + border_skip_end_x = box_end_x + - (int)CLAY__MAX( + 1, (render_data.width.right / clay_tb_cell_size.width)); + } + } + if (0 < cell_box.height) { + if (0 < render_data.width.top) { + border_skip_begin_y = box_begin_y + + (int)CLAY__MAX( + 1, (render_data.width.top / clay_tb_cell_size.width)); + } + if (0 < render_data.width.bottom) { + border_skip_end_y = box_end_y + - (int)CLAY__MAX( + 1, (render_data.width.bottom / clay_tb_cell_size.width)); + } + } + break; + } + case CLAY_TB_BORDER_MODE_ROUND: { + int halfwidth = clay_tb_roundf(clay_tb_cell_size.width / 2); + int halfheight = clay_tb_roundf(clay_tb_cell_size.height / 2); + + if (halfwidth < render_data.width.left) { + border_skip_begin_x = box_begin_x + + (int)CLAY__MAX( + 1, (render_data.width.left / clay_tb_cell_size.width)); + } + if (halfwidth < render_data.width.right) { + border_skip_end_x = box_end_x + - (int)CLAY__MAX( + 1, (render_data.width.right / clay_tb_cell_size.width)); + } + if (halfheight < render_data.width.top) { + border_skip_begin_y = box_begin_y + + (int)CLAY__MAX( + 1, (render_data.width.top / clay_tb_cell_size.width)); + } + if (halfheight < render_data.width.bottom) { + border_skip_end_y = box_end_y + - (int)CLAY__MAX( + 1, (render_data.width.bottom / clay_tb_cell_size.width)); + } + break; + } + } + + // Draw border, skipping over the center of the bounding box + for (int y = box_begin_y; y < box_end_y; ++y) { + for (int x = box_begin_x; x < box_end_x; ++x) { + if ((border_skip_begin_x <= x && x < border_skip_end_x) + && (border_skip_begin_y <= y && y < border_skip_end_y)) { + x = border_skip_end_x - 1; + continue; + } + + uint32_t ch; + switch (clay_tb_border_chars) { + default: { + clay_tb_assert(false, + "Invalid or unimplemented border character mode (%d)", + clay_tb_border_chars); + } + case CLAY_TB_BORDER_CHARS_UNICODE: { + if ((x < border_skip_begin_x) + && (y < border_skip_begin_y)) { // Top left + ch = U'\u250c'; + } else if ((x >= border_skip_end_x) + && (y < border_skip_begin_y)) { // Top right + ch = U'\u2510'; + } else if ((x < border_skip_begin_x) + && (y >= border_skip_end_y)) { // Bottom left + ch = U'\u2514'; + } else if ((x >= border_skip_end_x) + && (y >= border_skip_end_y)) { // Bottom right + ch = U'\u2518'; + } else if (x < border_skip_begin_x || x >= border_skip_end_x) { + ch = U'\u2502'; + } else if (y < border_skip_begin_y || y >= border_skip_end_y) { + ch = U'\u2500'; + } + break; + } + case CLAY_TB_BORDER_CHARS_DEFAULT: + case CLAY_TB_BORDER_CHARS_ASCII: { + if ((x < border_skip_begin_x || x >= border_skip_end_x) + && (y < border_skip_begin_y || y >= border_skip_end_y)) { + ch = '+'; + } else if (x < border_skip_begin_x || x >= border_skip_end_x) { + ch = '|'; + } else if (y < border_skip_begin_y || y >= border_skip_end_y) { + ch = '-'; + } + break; + } + case CLAY_TB_BORDER_CHARS_BLANK: { + ch = ' '; + break; + } + } + + clay_tb_color_pair color_bg_new = clay_tb_get_transparency_color( + x, y, (clay_tb_color_pair) { color_bg, color_tb_bg }); + clay_tb_set_cell( + x, y, ch, color_tb_fg, color_bg_new.termbox, color_bg_new.clay); + } + } + break; + } + case CLAY_RENDER_COMMAND_TYPE_TEXT: { + Clay_TextRenderData render_data = command->renderData.text; + Clay_Color color_fg = render_data.textColor; + uintattr_t color_tb_fg = clay_tb_color_convert(color_fg); + + Clay_StringSlice *text = &render_data.stringContents; + int32_t i = 0; + for (int y = box_begin_y; y < box_end_y; ++y) { + for (int x = box_begin_x; x < box_end_x;) { + uint32_t ch = ' '; + if (i < text->length) { + int codepoint_length = tb_utf8_char_to_unicode(&ch, text->chars + i); + if (0 > codepoint_length) { + clay_tb_assert(false, "Invalid utf8"); + } + i += codepoint_length; + + uintattr_t color_tb_bg = (clay_tb_transparency) + ? TB_DEFAULT + : clay_tb_color_convert(clay_tb_color_buffer_clay_get(x, y)); + Clay_Color color_bg = { 0 }; + clay_tb_color_pair color_bg_new = clay_tb_get_transparency_color( + x, y, (clay_tb_color_pair) { color_bg, color_tb_bg }); + clay_tb_set_cell( + x, y, ch, color_tb_fg, color_bg_new.termbox, color_bg_new.clay); + } + + int codepoint_width = tb_wcwidth(ch); + if (-1 == codepoint_width) { + // Nonprintable character, use REPLACEMENT CHARACTER (U+FFFD) + ch = U'\ufffd'; + codepoint_width = tb_wcwidth(ch); + } + + x += codepoint_width; + } + } + break; + } + case CLAY_RENDER_COMMAND_TYPE_IMAGE: { + Clay_ImageRenderData render_data = command->renderData.image; + Clay_Color color_fg = { 0, 0, 0, 0 }; + Clay_Color color_bg = render_data.backgroundColor; + uintattr_t color_tb_fg = clay_tb_color_convert(color_fg); + uintattr_t color_tb_bg; + + // Only set background to the provided color if it's non-default + bool color_specified + = !(color_bg.r == 0 && color_bg.g == 0 && color_bg.b == 0 && color_bg.a == 0); + if (color_specified) { + color_tb_bg = clay_tb_color_convert(color_bg); + } + + bool use_placeholder = true; + + clay_tb_image *image = (clay_tb_image *)render_data.imageData; + + if (!(CLAY_TB_IMAGE_MODE_PLACEHOLDER == clay_tb_image_mode + || CLAY_TB_OUTPUT_NOCOLOR == clay_tb_color_mode)) { + bool convert_success = (NULL != image) + ? clay_tb_image_convert(image, cell_box.width, cell_box.height) + : false; + if (convert_success) { + use_placeholder = false; + } + } + + if (!use_placeholder) { + // Render image + for (int y = box_begin_y; y < box_end_y; ++y) { + int y_offset = y - cell_box.y; + for (int x = box_begin_x; x < box_end_x; ++x) { + int x_offset = x - cell_box.x; + // Fetch cells from the image's cache + if (!color_specified) { + if (CLAY_TB_IMAGE_MODE_ASCII_FG == clay_tb_image_mode + || CLAY_TB_IMAGE_MODE_ASCII_FG_FAST == clay_tb_image_mode) { + color_bg = (Clay_Color) { 0, 0, 0, 0 }; + color_tb_bg = TB_DEFAULT; + } else { + color_bg + = image->internal + .background[y_offset * cell_box.width + x_offset]; + color_tb_bg = clay_tb_color_convert(color_bg); + } + } + color_tb_fg = clay_tb_color_convert( + image->internal.foreground[y_offset * cell_box.width + x_offset]); + uint32_t ch + = image->internal.characters[y_offset * cell_box.width + x_offset]; + if (CLAY_TB_IMAGE_MODE_BG == clay_tb_image_mode) { + ch = ' '; + } + + clay_tb_set_cell(x, y, ch, color_tb_fg, color_tb_bg, color_bg); + } + } + } else { + // Render a placeholder pattern + const char *placeholder_text = "[Image]"; + + int i = 0; + unsigned long len = strlen(placeholder_text); + for (int y = box_begin_y; y < box_end_y; ++y) { + float percent_y = (float)(y - box_begin_y) / (float)cell_box.height; + + for (int x = box_begin_x; x < box_end_x; ++x) { + char ch = ' '; + if (i < len) { + ch = placeholder_text[i++]; + } + + if (!color_specified) { + // Use a placeholder pattern for the image + float percent_x = (float)(cell_box.width - (x - box_begin_x)) + / (float)cell_box.width; + if (percent_x > percent_y) { + color_bg = (Clay_Color) { 0x94, 0xb4, 0xff, 0xff }; + color_tb_bg = clay_tb_color_convert(color_bg); + } else { + color_bg = (Clay_Color) { 0x3f, 0xcc, 0x45, 0xff }; + color_tb_bg = clay_tb_color_convert(color_bg); + } + } + clay_tb_set_cell(x, y, ch, color_tb_fg, color_tb_bg, color_bg); + } + } + } + break; + } + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { + clay_tb_scissor_box = (clay_tb_cell_bounding_box) { + .x = box_begin_x, + .y = box_begin_y, + .width = box_end_x - box_begin_x, + .height = box_end_y - box_begin_y, + }; + clay_tb_scissor_enabled = true; + break; + } + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { + clay_tb_scissor_enabled = false; + break; + } + case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { + break; + } + } + } +} + +void Clay_Termbox_Waitfor_Event(void) +{ + if (clay_tb_partial_image_drawn) { + return; + } + int termbox_ttyfd, termbox_resizefd; + tb_get_fds(&termbox_ttyfd, &termbox_resizefd); + int nfds = CLAY__MAX(termbox_ttyfd, termbox_resizefd) + 1; + fd_set monitor_set; + FD_ZERO(&monitor_set); + FD_SET(termbox_ttyfd, &monitor_set); + FD_SET(termbox_resizefd, &monitor_set); + select(nfds, &monitor_set, NULL, NULL, NULL); +} diff --git a/renderers/termbox2/image_character_masks.h b/renderers/termbox2/image_character_masks.h new file mode 100644 index 0000000..46258f1 --- /dev/null +++ b/renderers/termbox2/image_character_masks.h @@ -0,0 +1,2863 @@ +#ifndef CLAY_TB_IMAGE_H +#define CLAY_TB_IMAGE_H +#include + +typedef struct { + uint32_t character; + int data[6 * 12]; +} clay_tb_character_mask; + +// Ascii bitmap data from the Terminus 4.49 font (https://terminus-font.sourceforge.net/) +// Licensed under the SIL Open Font License, Version 1.1 (https://scripts.sil.org/OFL) +#define CLAY_TB_IMAGE_SHAPES_ASCII_BEST_COUNT 95 +const clay_tb_character_mask clay_tb_image_shapes_ascii_best[CLAY_TB_IMAGE_SHAPES_ASCII_BEST_COUNT] = { + { + .character = ' ', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '!', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '"', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 1, 0, 1, 0, 0, + 0, 1, 0, 1, 0, 0, + 0, 1, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '#', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 0, 1, 0, 0, + 0, 1, 0, 1, 0, 0, + 1, 1, 1, 1, 1, 0, + 0, 1, 0, 1, 0, 0, + 0, 1, 0, 1, 0, 0, + 1, 1, 1, 1, 1, 0, + 0, 1, 0, 1, 0, 0, + 0, 1, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '$', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '%', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 1, 0, + 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, + 0, 1, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '&', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 0, 1, 0, 0, + 0, 1, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 1, 0, 1, 0, + 1, 0, 0, 1, 0, 0, + 1, 0, 0, 1, 0, 0, + 0, 1, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '\'', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '(', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = ')', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '*', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '+', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = ',', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '-', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '.', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '/', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '0', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 1, 1, 0, + 1, 0, 1, 0, 1, 0, + 1, 1, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '1', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '2', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '3', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '4', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 1, 0, + 0, 0, 1, 0, 1, 0, + 0, 1, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '5', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '6', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '7', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '8', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '9', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = ':', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = ';', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '<', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '=', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '>', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '?', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '@', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 1, 1, 0, + 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, + 1, 0, 0, 1, 1, 0, + 1, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'A', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'B', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'C', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'D', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 0, 0, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 1, 0, 0, + 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'E', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'F', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'G', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'H', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'I', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'J', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 1, 0, 0, 1, 0, 0, + 1, 0, 0, 1, 0, 0, + 0, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'K', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 1, 0, 0, + 1, 0, 1, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 0, 1, 0, 0, 0, + 1, 0, 0, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'L', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'M', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 1, 0, 1, 1, 0, + 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'N', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 1, 0, 0, 1, 0, + 1, 0, 1, 0, 1, 0, + 1, 0, 0, 1, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'O', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'P', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'Q', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 1, 0, 1, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'R', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 1, 1, 1, 0, 0, + 1, 0, 1, 0, 0, 0, + 1, 0, 0, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'S', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'T', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'U', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'V', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 0, 1, 0, 0, + 0, 1, 0, 1, 0, 0, + 0, 1, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'W', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, + 1, 1, 0, 1, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'X', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 0, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'Y', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 0, 1, 0, 0, + 0, 1, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'Z', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '[', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '\\', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = ']', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '^', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 0, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '_', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '`', + .data = + { 0, 1, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'a', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'b', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'c', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'd', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'e', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'f', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'g', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 0, 0 }, + }, + { + .character = 'h', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'i', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'j', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 1, 0, 0, 1, 0, + 0, 0, 1, 1, 0, 0 }, + }, + { + .character = 'k', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 1, 0, 0, 1, 0, + 0, 1, 0, 1, 0, 0, + 0, 1, 1, 0, 0, 0, + 0, 1, 1, 0, 0, 0, + 0, 1, 0, 1, 0, 0, + 0, 1, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'l', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'm', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'n', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'o', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'p', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0 }, + }, + { + .character = 'q', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, 0 }, + }, + { + .character = 'r', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 1, 1, 1, 0, + 1, 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 's', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 0, + 1, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, 0, + 1, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 't', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'u', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'v', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 0, 1, 0, 0, + 0, 1, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'w', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, + 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'x', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 0, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'y', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 0, 0 }, + }, + { + .character = 'z', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '{', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '|', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '}', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 0, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '~', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 1, 0, + 1, 0, 1, 0, 1, 0, + 1, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, +}; + + +#define CLAY_TB_IMAGE_SHAPES_ASCII_FAST_COUNT 15 +const clay_tb_character_mask clay_tb_image_shapes_ascii_fast[CLAY_TB_IMAGE_SHAPES_ASCII_FAST_COUNT] = { + { + .character = ' ', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '"', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 1, 0, 1, 0, 0, + 0, 1, 0, 1, 0, 0, + 0, 1, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '#', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 0, 1, 0, 0, + 0, 1, 0, 1, 0, 0, + 1, 1, 1, 1, 1, 0, + 0, 1, 0, 1, 0, 0, + 0, 1, 0, 1, 0, 0, + 1, 1, 1, 1, 1, 0, + 0, 1, 0, 1, 0, 0, + 0, 1, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '-', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '.', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '@', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 1, 1, 0, + 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, + 1, 0, 0, 1, 1, 0, + 1, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'B', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'F', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'L', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '_', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = '`', + .data = + { 0, 1, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'g', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 0, 0 }, + }, + { + .character = 'r', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 1, 1, 1, 0, + 1, 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = 'y', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 0, 0 }, + }, + { + .character = '~', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 1, 0, + 1, 0, 1, 0, 1, 0, + 1, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, +}; + +#define CLAY_TB_IMAGE_SHAPES_UNICODE_BEST_COUNT 52 +const clay_tb_character_mask clay_tb_image_shapes_unicode_best[CLAY_TB_IMAGE_SHAPES_UNICODE_BEST_COUNT] = { + { + .character = ' ', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = U'\u2580', + .data = + { 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = U'\u2581', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1 }, + }, + { + .character = U'\u2582', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1 }, + }, + { + .character = U'\u2583', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1 }, + }, + { + .character = U'\u2584', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1 }, + }, + { + .character = U'\u2585', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1 }, + }, + { + .character = U'\u2586', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1 }, + }, + { + .character = U'\u2587', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1 }, + }, + { + .character = U'\u2588', + .data = + { 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1 }, + }, + { + .character = U'\u2589', + .data = + { 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0 }, + }, + { + .character = U'\u258a', + .data = + { 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 0, 0 }, + }, + { + .character = U'\u258c', + .data = + { 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0 }, + }, + { + .character = U'\u258e', + .data = + { 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0 }, + }, + { + .character = U'\u258f', + .data = + { 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0 }, + }, + { + .character = U'\u2590', + .data = + { 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1 }, + }, + { + .character = U'\u2594', + .data = + { 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = U'\u2595', + .data = + { 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1 }, + }, + { + .character = U'\u2596', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0 }, + }, + { + .character = U'\u2597', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1 }, + }, + { + .character = U'\u2598', + .data = + { 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = U'\u2599', + .data = + { 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1 }, + }, + { + .character = U'\u259a', + .data = + { 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1 }, + }, + { + .character = U'\u259b', + .data = + { 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0 }, + }, + { + .character = U'\u259c', + .data = + { 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1 }, + }, + { + .character = U'\u259d', + .data = + { 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = U'\u259e', + .data = + { 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0 }, + }, + { + .character = U'\u259f', + .data = + { 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1 }, + }, + // TODO: END HERE + { + .character = U'\u25e2', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 1, 1, 1, 1, + 0, 0, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1 }, + }, + { + .character = U'\u25e3', + .data = + { 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1 }, + }, + { + .character = U'\u25e4', + .data = + { 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = U'\u25e5', + .data = + { 1, 1, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, + 0, 0, 1, 1, 1, 1, + 0, 0, 1, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0 }, + }, + + { + .character = U'\U0001fb3c', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 1, 0, 0, 0 }, + }, + { + .character = U'\U0001fb3d', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 1, 1 }, + }, + { + .character = U'\U0001fb3e', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0 }, + }, + { + .character = U'\U0001fb3f', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1 }, + }, + { + .character = U'\U0001fb40', + .data = + { 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0 }, + }, + { + .character = U'\U0001fb47', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 1, 1, 1 }, + }, + { + .character = U'\U0001fb48', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 1, + 0, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1 }, + }, + { + .character = U'\U0001fb49', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1 }, + }, + { + .character = U'\U0001fb4a', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1 }, + }, + { + .character = U'\U0001fb4b', + .data = + { 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1 }, + }, + { + .character = U'\U0001fb57', + .data = + { 1, 1, 1, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = U'\U0001fb58', + .data = + { 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 0, 0, + 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = U'\U0001fb59', + .data = + { 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = U'\U0001fb5a', + .data = + { 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = U'\U0001fb59', + .data = + { 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0 }, + }, + { + .character = U'\U0001fb62', + .data = + { 0, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = U'\U0001fb63', + .data = + { 1, 1, 1, 1, 1, 1, + 0, 0, 1, 1, 1, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = U'\U0001fb64', + .data = + { 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = U'\U0001fb65', + .data = + { 1, 1, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, + 0, 0, 1, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = U'\U0001fb66', + .data = + { 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1 }, + }, +}; + +#define CLAY_TB_IMAGE_SHAPES_UNICODE_FAST_COUNT 15 +const clay_tb_character_mask clay_tb_image_shapes_unicode_fast[CLAY_TB_IMAGE_SHAPES_UNICODE_FAST_COUNT] = { + { + .character = ' ', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = U'\u2580', + .data = + { 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = U'\u2581', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1 }, + }, + { + .character = U'\u2586', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1 }, + }, + { + .character = U'\u2587', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1 }, + }, + { + .character = U'\u2589', + .data = + { 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0 }, + }, + { + .character = U'\u258c', + .data = + { 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0 }, + }, + { + .character = U'\u258f', + .data = + { 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0 }, + }, + { + .character = U'\u2594', + .data = + { 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = U'\u2596', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0 }, + }, + { + .character = U'\u2597', + .data = + { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1 }, + }, + { + .character = U'\u2598', + .data = + { 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = U'\u259a', + .data = + { 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1 }, + }, + { + .character = U'\u259d', + .data = + { 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }, + }, + { + .character = U'\u259e', + .data = + { 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0 }, + }, +}; + +#endif // CLAY_TB_IMAGE_H