diff --git a/.github/workflows/odin-bindings-update.yml b/.github/workflows/odin-bindings-update.yml
new file mode 100644
index 0000000..11a6f3d
--- /dev/null
+++ b/.github/workflows/odin-bindings-update.yml
@@ -0,0 +1,104 @@
+name: Odin Bindings Update
+on:
+ push:
+ branches: [main]
+jobs:
+ check_changes:
+ runs-on: ubuntu-latest
+ outputs:
+ changed: ${{ steps.check_clay.outputs.changed }}
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 2
+
+ - name: Check if clay.h changed
+ id: check_clay
+ run: |
+ if git diff --name-only HEAD^ HEAD | grep -Fx "clay.h"; then
+ echo "changed=true" >> $GITHUB_OUTPUT
+ else
+ echo "changed=false" >> $GITHUB_OUTPUT
+ fi
+
+ build:
+ needs: check_changes
+ if: needs.check_changes.outputs.changed == 'true'
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-latest]
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v4
+
+ - name: Install clang (Linux)
+ if: runner.os == 'Linux'
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y clang
+
+ - name: Build libs
+ run: |
+ mkdir -p build
+ mkdir -p artifacts
+ cp clay.h clay.c
+
+ COMMON_FLAGS="-DCLAY_IMPLEMENTATION -fno-ident -frandom-seed=clay"
+
+ if [[ "$(uname)" == "Linux" ]]; then
+ mkdir -p artifacts/linux
+ mkdir -p artifacts/windows
+ mkdir -p artifacts/wasm
+
+ echo "Building for Linux..."
+ clang -c $COMMON_FLAGS -fPIC -ffreestanding -static -target x86_64-unknown-linux-gnu clay.c -o build/linux.o
+ ar rD artifacts/linux/clay.a build/linux.o
+
+ echo "Building for Windows..."
+ clang -c $COMMON_FLAGS -ffreestanding -target x86_64-pc-windows-msvc -fuse-ld=llvm-lib clay.c -o artifacts/windows/clay.lib
+
+ echo "Building for WASM..."
+ clang -c $COMMON_FLAGS -fPIC -target wasm32 -nostdlib -static clay.c -o artifacts/wasm/clay.o
+ elif [[ "$(uname)" == "Darwin" ]]; then
+ mkdir -p artifacts/macos
+ mkdir -p artifacts/macos-arm64
+ echo "Building for macOS (x86_64)..."
+ clang -c $COMMON_FLAGS -fPIC -target x86_64-apple-macos clay.c -o build/macos.o
+ libtool -static -o artifacts/macos/clay.a build/macos.o
+
+ echo "Building for macOS (ARM64)..."
+ clang -c $COMMON_FLAGS -fPIC -target arm64-apple-macos clay.c -o build/macos-arm64.o
+ libtool -static -o artifacts/macos-arm64/clay.a build/macos-arm64.o
+ fi
+ rm -f clay.c build/*.o
+
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: artifacts-${{ matrix.os }}
+ path: artifacts/
+
+ commit:
+ needs: build
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v4
+
+ - name: Download artifacts
+ uses: actions/download-artifact@v4
+
+ - name: Move artifacts
+ run: |
+ cp -r artifacts-ubuntu-latest/* bindings/odin/clay-odin/
+ cp -r artifacts-macos-latest/* bindings/odin/clay-odin/
+
+ - name: Commit/Push changes
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git add bindings/odin/clay-odin/
+ git commit -m "[bindings/odin] Update Odin bindings"
+ git push
diff --git a/bindings/odin/clay-odin/clay.odin b/bindings/odin/clay-odin/clay.odin
index ad1e1ee..8862acc 100644
--- a/bindings/odin/clay-odin/clay.odin
+++ b/bindings/odin/clay-odin/clay.odin
@@ -186,7 +186,7 @@ FloatingElementConfig :: struct {
attachment: FloatingAttachPoints,
pointerCaptureMode: PointerCaptureMode,
attachTo: FloatingAttachToElement,
- clipTo: FloatingClipToElement,
+ clipTo: FloatingClipToElement,
}
TextRenderData :: struct {
@@ -342,7 +342,7 @@ ElementDeclaration :: struct {
layout: LayoutConfig,
backgroundColor: Color,
cornerRadius: CornerRadius,
- aspectRatio: AspectRatioElementConfig,
+ aspectRatio: AspectRatioElementConfig,
image: ImageElementConfig,
floating: FloatingElementConfig,
custom: CustomElementConfig,
@@ -413,7 +413,8 @@ foreign Clay {
@(link_prefix = "Clay_", default_calling_convention = "c", private)
foreign Clay {
_ConfigureOpenElement :: proc(config: ElementDeclaration) ---
- _HashString :: proc(key: String, offset: u32, seed: u32) -> ElementId ---
+ _HashString :: proc(key: String, seed: u32) -> ElementId ---
+ _HashStringWithOffset :: proc(key: String, index: u32, seed: u32) -> ElementId ---
_OpenTextElement :: proc(text: String, textConfig: ^TextElementConfig) ---
_StoreTextElementConfig :: proc(config: TextElementConfig) -> ^TextElementConfig ---
_GetParentElementId :: proc() -> u32 ---
@@ -460,11 +461,11 @@ CornerRadiusAll :: proc(radius: f32) -> CornerRadius {
return CornerRadius{radius, radius, radius, radius}
}
-SizingFit :: proc(sizeMinMax: SizingConstraintsMinMax) -> SizingAxis {
+SizingFit :: proc(sizeMinMax: SizingConstraintsMinMax = {}) -> SizingAxis {
return SizingAxis{type = SizingType.Fit, constraints = {sizeMinMax = sizeMinMax}}
}
-SizingGrow :: proc(sizeMinMax: SizingConstraintsMinMax) -> SizingAxis {
+SizingGrow :: proc(sizeMinMax: SizingConstraintsMinMax = {}) -> SizingAxis {
return SizingAxis{type = SizingType.Grow, constraints = {sizeMinMax = sizeMinMax}}
}
@@ -481,9 +482,9 @@ MakeString :: proc(label: string) -> String {
}
ID :: proc(label: string, index: u32 = 0) -> ElementId {
- return _HashString(MakeString(label), index, 0)
+ return _HashString(MakeString(label), 0)
}
ID_LOCAL :: proc(label: string, index: u32 = 0) -> ElementId {
- return _HashString(MakeString(label), index, _GetParentElementId())
-}
\ No newline at end of file
+ return _HashStringWithOffset(MakeString(label), index, _GetParentElementId())
+}
diff --git a/bindings/odin/clay-odin/linux/clay.a b/bindings/odin/clay-odin/linux/clay.a
index 55d74f2..3d348fc 100644
Binary files a/bindings/odin/clay-odin/linux/clay.a and b/bindings/odin/clay-odin/linux/clay.a differ
diff --git a/bindings/odin/clay-odin/macos-arm64/clay.a b/bindings/odin/clay-odin/macos-arm64/clay.a
index 0a57ea2..2e9afdb 100644
Binary files a/bindings/odin/clay-odin/macos-arm64/clay.a and b/bindings/odin/clay-odin/macos-arm64/clay.a differ
diff --git a/bindings/odin/clay-odin/macos/clay.a b/bindings/odin/clay-odin/macos/clay.a
index 55d5687..880c5c2 100644
Binary files a/bindings/odin/clay-odin/macos/clay.a and b/bindings/odin/clay-odin/macos/clay.a differ
diff --git a/bindings/odin/clay-odin/wasm/clay.o b/bindings/odin/clay-odin/wasm/clay.o
index e895733..2ec41c0 100644
Binary files a/bindings/odin/clay-odin/wasm/clay.o and b/bindings/odin/clay-odin/wasm/clay.o differ
diff --git a/bindings/odin/clay-odin/windows/clay.lib b/bindings/odin/clay-odin/windows/clay.lib
index 139ffde..bb75c04 100644
Binary files a/bindings/odin/clay-odin/windows/clay.lib and b/bindings/odin/clay-odin/windows/clay.lib differ
diff --git a/bindings/odin/examples/clay-official-website/clay-official-website.odin b/bindings/odin/examples/clay-official-website/clay-official-website.odin
index 5173d74..4414241 100644
--- a/bindings/odin/examples/clay-official-website/clay-official-website.odin
+++ b/bindings/odin/examples/clay-official-website/clay-official-website.odin
@@ -82,11 +82,11 @@ LandingPageBlob :: proc(index: u32, fontSize: u16, fontId: u16, color: clay.Colo
LandingPageDesktop :: proc() {
if clay.UI()({
id = clay.ID("LandingPage1Desktop"),
- layout = { sizing = { width = clay.SizingGrow({ }), height = clay.SizingFit({ min = cast(f32)windowHeight - 70 }) }, childAlignment = { y = .Center }, padding = { left = 50, right = 50 } },
+ layout = { sizing = { width = clay.SizingGrow(), height = clay.SizingFit({ min = cast(f32)windowHeight - 70 }) }, childAlignment = { y = .Center }, padding = { left = 50, right = 50 } },
}) {
if clay.UI()({
id = clay.ID("LandingPage1"),
- layout = { sizing = { clay.SizingGrow({ }), clay.SizingGrow({ }) }, childAlignment = { y = .Center }, padding = clay.PaddingAll(32), childGap = 32 },
+ layout = { sizing = { clay.SizingGrow(), clay.SizingGrow() }, childAlignment = { y = .Center }, padding = clay.PaddingAll(32), childGap = 32 },
border = { COLOR_RED, { left = 2, right = 2 } },
}) {
if clay.UI()({ id = clay.ID("LeftText"), layout = { sizing = { width = clay.SizingPercent(0.55) }, layoutDirection = .TopToBottom, childGap = 8 } }) {
@@ -119,13 +119,13 @@ LandingPageMobile :: proc() {
id = clay.ID("LandingPage1Mobile"),
layout = {
layoutDirection = .TopToBottom,
- sizing = { width = clay.SizingGrow({ }), height = clay.SizingFit({ min = cast(f32)windowHeight - 70 }) },
+ sizing = { width = clay.SizingGrow(), height = clay.SizingFit({ min = cast(f32)windowHeight - 70 }) },
childAlignment = { x = .Center, y = .Center },
padding = { 16, 16, 32, 32 },
childGap = 32,
},
}) {
- if clay.UI()({ id = clay.ID("LeftText"), layout = { sizing = { width = clay.SizingGrow({ }) }, layoutDirection = .TopToBottom, childGap = 8 } }) {
+ if clay.UI()({ id = clay.ID("LeftText"), layout = { sizing = { width = clay.SizingGrow() }, layoutDirection = .TopToBottom, childGap = 8 } }) {
clay.Text(
"Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance.",
clay.TextConfig({fontSize = 48, fontId = FONT_ID_TITLE_48, textColor = COLOR_RED}),
@@ -138,7 +138,7 @@ LandingPageMobile :: proc() {
}
if clay.UI()({
id = clay.ID("HeroImageOuter"),
- layout = { layoutDirection = .TopToBottom, sizing = { width = clay.SizingGrow({ }) }, childAlignment = { x = .Center }, childGap = 16 },
+ layout = { layoutDirection = .TopToBottom, sizing = { width = clay.SizingGrow() }, childAlignment = { x = .Center }, childGap = 16 },
}) {
LandingPageBlob(1, 24, FONT_ID_BODY_24, COLOR_BLOB_BORDER_5, "High performance", &checkImage5)
LandingPageBlob(2, 24, FONT_ID_BODY_24, COLOR_BLOB_BORDER_4, "Flexbox-style responsive layout", &checkImage4)
@@ -175,7 +175,7 @@ FeatureBlocksDesktop :: proc() {
if clay.UI()({ id = clay.ID("FeatureBlocksOuter"), layout = { sizing = { width = clay.SizingGrow({}) } } }) {
if clay.UI()({
id = clay.ID("FeatureBlocksInner"),
- layout = { sizing = { width = clay.SizingGrow({ }) }, childAlignment = { y = .Center } },
+ layout = { sizing = { width = clay.SizingGrow() }, childAlignment = { y = .Center } },
border = { width = { betweenChildren = 2}, color = COLOR_RED },
}) {
FeatureBlocks(clay.SizingPercent(0.5), 50)
@@ -186,7 +186,7 @@ FeatureBlocksDesktop :: proc() {
FeatureBlocksMobile :: proc() {
if clay.UI()({
id = clay.ID("FeatureBlocksInner"),
- layout = { layoutDirection = .TopToBottom, sizing = { width = clay.SizingGrow({ }) } },
+ layout = { layoutDirection = .TopToBottom, sizing = { width = clay.SizingGrow() } },
border = { width = { betweenChildren = 2}, color = COLOR_RED },
}) {
FeatureBlocks(clay.SizingGrow({}), 16)
@@ -223,11 +223,11 @@ DeclarativeSyntaxPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizi
DeclarativeSyntaxPageDesktop :: proc() {
if clay.UI()({
id = clay.ID("SyntaxPageDesktop"),
- layout = { sizing = { clay.SizingGrow({ }), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) }, childAlignment = { y = .Center }, padding = { left = 50, right = 50 } },
+ layout = { sizing = { clay.SizingGrow(), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) }, childAlignment = { y = .Center }, padding = { left = 50, right = 50 } },
}) {
if clay.UI()({
id = clay.ID("SyntaxPage"),
- layout = { sizing = { clay.SizingGrow({ }), clay.SizingGrow({ }) }, childAlignment = { y = .Center }, padding = clay.PaddingAll(32), childGap = 32 },
+ layout = { sizing = { clay.SizingGrow(), clay.SizingGrow() }, childAlignment = { y = .Center }, padding = clay.PaddingAll(32), childGap = 32 },
border = border2pxRed,
}) {
DeclarativeSyntaxPage({fontSize = 52, fontId = FONT_ID_TITLE_52, textColor = COLOR_RED}, clay.SizingPercent(0.5))
@@ -240,7 +240,7 @@ DeclarativeSyntaxPageMobile :: proc() {
id = clay.ID("SyntaxPageMobile"),
layout = {
layoutDirection = .TopToBottom,
- sizing = { clay.SizingGrow({ }), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) },
+ sizing = { clay.SizingGrow(), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) },
childAlignment = { x = .Center, y = .Center },
padding = { 16, 16, 32, 32 },
childGap = 16,
@@ -276,19 +276,19 @@ HighPerformancePage :: proc(lerpValue: f32, titleTextConfig: clay.TextElementCon
if clay.UI()({ id = clay.ID("PerformanceRightImageOuter"), layout = { sizing = { width = widthSizing }, childAlignment = { x = .Center } } }) {
if clay.UI()({
id = clay.ID("PerformanceRightBorder"),
- layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(400) } },
+ layout = { sizing = { clay.SizingGrow(), clay.SizingFixed(400) } },
border = { COLOR_LIGHT, {2, 2, 2, 2, 2} },
}) {
if clay.UI()({
id = clay.ID("AnimationDemoContainerLeft"),
- layout = { sizing = { clay.SizingPercent(0.35 + 0.3 * lerpValue), clay.SizingGrow({ }) }, childAlignment = { y = .Center }, padding = clay.PaddingAll(16) },
+ layout = { sizing = { clay.SizingPercent(0.35 + 0.3 * lerpValue), clay.SizingGrow() }, childAlignment = { y = .Center }, padding = clay.PaddingAll(16) },
backgroundColor = ColorLerp(COLOR_RED, COLOR_ORANGE, lerpValue),
}) {
clay.Text(LOREM_IPSUM_TEXT, clay.TextConfig({fontSize = 16, fontId = FONT_ID_BODY_16, textColor = COLOR_LIGHT}))
}
if clay.UI()({
id = clay.ID("AnimationDemoContainerRight"),
- layout = { sizing = { clay.SizingGrow({ }), clay.SizingGrow({ }) }, childAlignment = { y = .Center }, padding = clay.PaddingAll(16) },
+ layout = { sizing = { clay.SizingGrow(), clay.SizingGrow() }, childAlignment = { y = .Center }, padding = clay.PaddingAll(16) },
backgroundColor = ColorLerp(COLOR_ORANGE, COLOR_RED, lerpValue),
}) {
clay.Text(LOREM_IPSUM_TEXT, clay.TextConfig({fontSize = 16, fontId = FONT_ID_BODY_16, textColor = COLOR_LIGHT}))
@@ -300,7 +300,7 @@ HighPerformancePage :: proc(lerpValue: f32, titleTextConfig: clay.TextElementCon
HighPerformancePageDesktop :: proc(lerpValue: f32) {
if clay.UI()({
id = clay.ID("PerformanceDesktop"),
- layout = { sizing = { clay.SizingGrow({ }), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) }, childAlignment = { y = .Center }, padding = { 82, 82, 32, 32 }, childGap = 64 },
+ layout = { sizing = { clay.SizingGrow(), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) }, childAlignment = { y = .Center }, padding = { 82, 82, 32, 32 }, childGap = 64 },
backgroundColor = COLOR_RED,
}) {
HighPerformancePage(lerpValue, {fontSize = 52, fontId = FONT_ID_TITLE_52, textColor = COLOR_LIGHT}, clay.SizingPercent(0.5))
@@ -312,7 +312,7 @@ HighPerformancePageMobile :: proc(lerpValue: f32) {
id = clay.ID("PerformanceMobile"),
layout = {
layoutDirection = .TopToBottom,
- sizing = { clay.SizingGrow({ }), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) },
+ sizing = { clay.SizingGrow(), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) },
childAlignment = { x = .Center, y = .Center },
padding = { 16, 16, 32, 32 },
childGap = 32,
@@ -376,11 +376,11 @@ RendererPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizing: clay.
RendererPageDesktop :: proc() {
if clay.UI()({
id = clay.ID("RendererPageDesktop"),
- layout = { sizing = { clay.SizingGrow({ }), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) }, childAlignment = { y = .Center }, padding = { left = 50, right = 50 } },
+ layout = { sizing = { clay.SizingGrow(), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) }, childAlignment = { y = .Center }, padding = { left = 50, right = 50 } },
}) {
if clay.UI()({
id = clay.ID("RendererPage"),
- layout = { sizing = { clay.SizingGrow({ }), clay.SizingGrow({ }) }, childAlignment = { y = .Center }, padding = clay.PaddingAll(32), childGap = 32 },
+ layout = { sizing = { clay.SizingGrow(), clay.SizingGrow() }, childAlignment = { y = .Center }, padding = clay.PaddingAll(32), childGap = 32 },
border = { COLOR_RED, { left = 2, right = 2 } },
}) {
RendererPage({fontSize = 52, fontId = FONT_ID_TITLE_52, textColor = COLOR_RED}, clay.SizingPercent(0.5))
@@ -393,7 +393,7 @@ RendererPageMobile :: proc() {
id = clay.ID("RendererMobile"),
layout = {
layoutDirection = .TopToBottom,
- sizing = { clay.SizingGrow({ }), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) },
+ sizing = { clay.SizingGrow(), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) },
childAlignment = { x = .Center, y = .Center },
padding = { 16, 16, 32, 32 },
childGap = 32,
@@ -418,15 +418,15 @@ createLayout :: proc(lerpValue: f32) -> clay.ClayArray(clay.RenderCommand) {
clay.BeginLayout()
if clay.UI()({
id = clay.ID("OuterContainer"),
- layout = { layoutDirection = .TopToBottom, sizing = { clay.SizingGrow({ }), clay.SizingGrow({ }) } },
+ layout = { layoutDirection = .TopToBottom, sizing = { clay.SizingGrow(), clay.SizingGrow() } },
backgroundColor = COLOR_LIGHT,
}) {
if clay.UI()({
id = clay.ID("Header"),
- layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(50) }, childAlignment = { y = .Center }, childGap = 24, padding = { left = 32, right = 32 } },
+ layout = { sizing = { clay.SizingGrow(), clay.SizingFixed(50) }, childAlignment = { y = .Center }, childGap = 24, padding = { left = 32, right = 32 } },
}) {
clay.Text("Clay", &headerTextConfig)
- if clay.UI()({ layout = { sizing = { width = clay.SizingGrow({ }) } } }) {}
+ if clay.UI()({ layout = { sizing = { width = clay.SizingGrow() } } }) {}
if (!mobileScreen) {
if clay.UI()({ id = clay.ID("LinkExamplesOuter"), backgroundColor = {0, 0, 0, 0} }) {
@@ -446,15 +446,15 @@ createLayout :: proc(lerpValue: f32) -> clay.ClayArray(clay.RenderCommand) {
clay.Text("Github", clay.TextConfig({fontId = FONT_ID_BODY_24, fontSize = 24, textColor = {61, 26, 5, 255}}))
}
}
- if clay.UI()({ id = clay.ID("TopBorder1"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_5 } ) {}
- if clay.UI()({ id = clay.ID("TopBorder2"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_4 } ) {}
- if clay.UI()({ id = clay.ID("TopBorder3"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_3 } ) {}
- if clay.UI()({ id = clay.ID("TopBorder4"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_2 } ) {}
- if clay.UI()({ id = clay.ID("TopBorder5"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_1 } ) {}
+ if clay.UI()({ id = clay.ID("TopBorder1"), layout = { sizing = { clay.SizingGrow(), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_5 } ) {}
+ if clay.UI()({ id = clay.ID("TopBorder2"), layout = { sizing = { clay.SizingGrow(), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_4 } ) {}
+ if clay.UI()({ id = clay.ID("TopBorder3"), layout = { sizing = { clay.SizingGrow(), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_3 } ) {}
+ if clay.UI()({ id = clay.ID("TopBorder4"), layout = { sizing = { clay.SizingGrow(), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_2 } ) {}
+ if clay.UI()({ id = clay.ID("TopBorder5"), layout = { sizing = { clay.SizingGrow(), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_1 } ) {}
if clay.UI()({
id = clay.ID("ScrollContainerBackgroundRectangle"),
clip = { vertical = true, childOffset = clay.GetScrollOffset() },
- layout = { sizing = { clay.SizingGrow({ }), clay.SizingGrow({ }) }, layoutDirection = clay.LayoutDirection.TopToBottom },
+ layout = { sizing = { clay.SizingGrow(), clay.SizingGrow() }, layoutDirection = clay.LayoutDirection.TopToBottom },
backgroundColor = COLOR_LIGHT,
border = { COLOR_RED, { betweenChildren = 2} },
}) {
diff --git a/clay.h b/clay.h
index edc400c..00c90bc 100644
--- a/clay.h
+++ b/clay.h
@@ -73,24 +73,24 @@
#define CLAY_SIZING_PERCENT(percentOfParent) (CLAY__INIT(Clay_SizingAxis) { .size = { .percent = (percentOfParent) }, .type = CLAY__SIZING_TYPE_PERCENT })
// Note: If a compile error led you here, you might be trying to use CLAY_ID with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SID instead.
-#define CLAY_ID(label) CLAY_IDI(label, 0)
+#define CLAY_ID(label) CLAY_SID(CLAY_STRING(label))
-#define CLAY_SID(label) CLAY_SIDI(label, 0)
+#define CLAY_SID(label) Clay__HashString(label, 0)
// Note: If a compile error led you here, you might be trying to use CLAY_IDI with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SIDI instead.
#define CLAY_IDI(label, index) CLAY_SIDI(CLAY_STRING(label), index)
-#define CLAY_SIDI(label, index) Clay__HashString(label, index, 0)
+#define CLAY_SIDI(label, index) Clay__HashStringWithOffset(label, index, 0)
// Note: If a compile error led you here, you might be trying to use CLAY_ID_LOCAL with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SID_LOCAL instead.
-#define CLAY_ID_LOCAL(label) CLAY_IDI_LOCAL(label, 0)
+#define CLAY_ID_LOCAL(label) CLAY_SID_LOCAL(CLAY_STRING(label))
-#define CLAY_SID_LOCAL(label) CLAY_SIDI_LOCAL(label, 0)
+#define CLAY_SID_LOCAL(label, index) Clay__HashString(label, Clay__GetParentElementId())
// Note: If a compile error led you here, you might be trying to use CLAY_IDI_LOCAL with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SIDI_LOCAL instead.
#define CLAY_IDI_LOCAL(label, index) CLAY_SIDI_LOCAL(CLAY_STRING(label), index)
-#define CLAY_SIDI_LOCAL(label, index) Clay__HashString(label, index, Clay__GetParentElementId())
+#define CLAY_SIDI_LOCAL(label, index) Clay__HashStringWithOffset(label, index, Clay__GetParentElementId())
#define CLAY__STRING_LENGTH(s) ((sizeof(s) / sizeof((s)[0])) - sizeof((s)[0]))
@@ -920,7 +920,8 @@ CLAY_DLL_EXPORT void Clay__OpenElement(void);
CLAY_DLL_EXPORT void Clay__ConfigureOpenElement(const Clay_ElementDeclaration config);
CLAY_DLL_EXPORT void Clay__ConfigureOpenElementPtr(const Clay_ElementDeclaration *config);
CLAY_DLL_EXPORT void Clay__CloseElement(void);
-CLAY_DLL_EXPORT Clay_ElementId Clay__HashString(Clay_String key, uint32_t offset, uint32_t seed);
+CLAY_DLL_EXPORT Clay_ElementId Clay__HashString(Clay_String key, uint32_t seed);
+CLAY_DLL_EXPORT Clay_ElementId Clay__HashStringWithOffset(Clay_String key, uint32_t offset, uint32_t seed);
CLAY_DLL_EXPORT void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig *textConfig);
CLAY_DLL_EXPORT Clay_TextElementConfig *Clay__StoreTextElementConfig(Clay_TextElementConfig config);
CLAY_DLL_EXPORT uint32_t Clay__GetParentElementId(void);
@@ -1361,7 +1362,22 @@ Clay_ElementId Clay__HashNumber(const uint32_t offset, const uint32_t seed) {
return CLAY__INIT(Clay_ElementId) { .id = hash + 1, .offset = offset, .baseId = seed, .stringId = CLAY__STRING_DEFAULT }; // Reserve the hash result of zero as "null id"
}
-Clay_ElementId Clay__HashString(Clay_String key, const uint32_t offset, const uint32_t seed) {
+Clay_ElementId Clay__HashString(Clay_String key, const uint32_t seed) {
+ uint32_t hash = seed;
+
+ for (int32_t i = 0; i < key.length; i++) {
+ hash += key.chars[i];
+ hash += (hash << 10);
+ hash ^= (hash >> 6);
+ }
+
+ hash += (hash << 3);
+ hash ^= (hash >> 11);
+ hash += (hash << 15);
+ return CLAY__INIT(Clay_ElementId) { .id = hash + 1, .offset = 0, .baseId = hash + 1, .stringId = key }; // Reserve the hash result of zero as "null id"
+}
+
+Clay_ElementId Clay__HashStringWithOffset(Clay_String key, const uint32_t offset, const uint32_t seed) {
uint32_t hash = 0;
uint32_t base = seed;
@@ -1644,7 +1660,10 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
char current = text->chars[end];
if (current == ' ' || current == '\n') {
int32_t length = end - start;
- Clay_Dimensions dimensions = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) { .length = length, .chars = &text->chars[start], .baseChars = text->chars }, config, context->measureTextUserData);
+ Clay_Dimensions dimensions = {};
+ if (length > 0) {
+ dimensions = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) {.length = length, .chars = &text->chars[start], .baseChars = text->chars}, config, context->measureTextUserData);
+ }
measured->minWidth = CLAY__MAX(dimensions.width, measured->minWidth);
measuredHeight = CLAY__MAX(measuredHeight, dimensions.height);
if (current == ' ') {
@@ -2091,10 +2110,10 @@ void Clay__ConfigureOpenElementPtr(const Clay_ElementDeclaration *declaration) {
clipElementId = Clay__int32_tArray_GetValue(&context->layoutElementClipElementIds, (int32_t)(parentItem->layoutElement - context->layoutElements.internalArray));
}
} else if (declaration->floating.attachTo == CLAY_ATTACH_TO_ROOT) {
- floatingConfig.parentId = Clay__HashString(CLAY_STRING("Clay__RootContainer"), 0, 0).id;
+ floatingConfig.parentId = Clay__HashString(CLAY_STRING("Clay__RootContainer"), 0).id;
}
if (!openLayoutElementId.id) {
- openLayoutElementId = Clay__HashString(CLAY_STRING("Clay__FloatingContainer"), context->layoutElementTreeRoots.length, 0);
+ openLayoutElementId = Clay__HashStringWithOffset(CLAY_STRING("Clay__FloatingContainer"), context->layoutElementTreeRoots.length, 0);
}
if (declaration->floating.clipTo == CLAY_CLIP_TO_NONE) {
clipElementId = 0;
@@ -2230,17 +2249,37 @@ void Clay__SizeContainersAlongAxis(bool xAxis) {
Clay_LayoutElementHashMapItem *parentItem = Clay__GetHashMapItem(floatingElementConfig->parentId);
if (parentItem && parentItem != &Clay_LayoutElementHashMapItem_DEFAULT) {
Clay_LayoutElement *parentLayoutElement = parentItem->layoutElement;
- if (rootElement->layoutConfig->sizing.width.type == CLAY__SIZING_TYPE_GROW) {
- rootElement->dimensions.width = parentLayoutElement->dimensions.width;
+ switch (rootElement->layoutConfig->sizing.width.type) {
+ case CLAY__SIZING_TYPE_GROW: {
+ rootElement->dimensions.width = parentLayoutElement->dimensions.width;
+ break;
+ }
+ case CLAY__SIZING_TYPE_PERCENT: {
+ rootElement->dimensions.width = parentLayoutElement->dimensions.width * rootElement->layoutConfig->sizing.width.size.percent;
+ break;
+ }
+ default: break;
}
- if (rootElement->layoutConfig->sizing.height.type == CLAY__SIZING_TYPE_GROW) {
- rootElement->dimensions.height = parentLayoutElement->dimensions.height;
+ switch (rootElement->layoutConfig->sizing.height.type) {
+ case CLAY__SIZING_TYPE_GROW: {
+ rootElement->dimensions.height = parentLayoutElement->dimensions.height;
+ break;
+ }
+ case CLAY__SIZING_TYPE_PERCENT: {
+ rootElement->dimensions.height = parentLayoutElement->dimensions.height * rootElement->layoutConfig->sizing.height.size.percent;
+ break;
+ }
+ default: break;
}
}
}
- rootElement->dimensions.width = CLAY__MIN(CLAY__MAX(rootElement->dimensions.width, rootElement->layoutConfig->sizing.width.size.minMax.min), rootElement->layoutConfig->sizing.width.size.minMax.max);
- rootElement->dimensions.height = CLAY__MIN(CLAY__MAX(rootElement->dimensions.height, rootElement->layoutConfig->sizing.height.size.minMax.min), rootElement->layoutConfig->sizing.height.size.minMax.max);
+ if (rootElement->layoutConfig->sizing.width.type != CLAY__SIZING_TYPE_PERCENT) {
+ rootElement->dimensions.width = CLAY__MIN(CLAY__MAX(rootElement->dimensions.width, rootElement->layoutConfig->sizing.width.size.minMax.min), rootElement->layoutConfig->sizing.width.size.minMax.max);
+ }
+ if (rootElement->layoutConfig->sizing.height.type != CLAY__SIZING_TYPE_PERCENT) {
+ rootElement->dimensions.height = CLAY__MIN(CLAY__MAX(rootElement->dimensions.height, rootElement->layoutConfig->sizing.height.size.minMax.min), rootElement->layoutConfig->sizing.height.size.minMax.max);
+ }
for (int32_t i = 0; i < bfsBuffer.length; ++i) {
int32_t parentIndex = Clay__int32_tArray_GetValue(&bfsBuffer, i);
@@ -2929,6 +2968,7 @@ void Clay__CalculateFinalLayout(void) {
default: break;
}
currentElementTreeNode->nextChildOffset.x += extraSpace;
+ extraSpace = CLAY__MAX(0, extraSpace);
} else {
for (int32_t i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) {
Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[i]);
@@ -2942,6 +2982,7 @@ void Clay__CalculateFinalLayout(void) {
case CLAY_ALIGN_Y_CENTER: extraSpace /= 2; break;
default: break;
}
+ extraSpace = CLAY__MAX(0, extraSpace);
currentElementTreeNode->nextChildOffset.y += extraSpace;
}
@@ -3276,7 +3317,7 @@ Clay__RenderDebugLayoutData Clay__RenderDebugLayoutElementsList(int32_t initialR
}
if (context->pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) {
- Clay_ElementId collapseButtonId = Clay__HashString(CLAY_STRING("Clay__DebugView_CollapseElement"), 0, 0);
+ Clay_ElementId collapseButtonId = Clay__HashString(CLAY_STRING("Clay__DebugView_CollapseElement"), 0);
for (int32_t i = (int)context->pointerOverIds.length - 1; i >= 0; i--) {
Clay_ElementId *elementId = Clay_ElementIdArray_Get(&context->pointerOverIds, i);
if (elementId->baseId == collapseButtonId.baseId) {
@@ -3379,7 +3420,7 @@ void HandleDebugViewCloseButtonInteraction(Clay_ElementId elementId, Clay_Pointe
void Clay__RenderDebugView(void) {
Clay_Context* context = Clay_GetCurrentContext();
- Clay_ElementId closeButtonId = Clay__HashString(CLAY_STRING("Clay__DebugViewTopHeaderCloseButtonOuter"), 0, 0);
+ Clay_ElementId closeButtonId = Clay__HashString(CLAY_STRING("Clay__DebugViewTopHeaderCloseButtonOuter"), 0);
if (context->pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) {
for (int32_t i = 0; i < context->pointerOverIds.length; ++i) {
Clay_ElementId *elementId = Clay_ElementIdArray_Get(&context->pointerOverIds, i);
@@ -3394,7 +3435,7 @@ void Clay__RenderDebugView(void) {
uint32_t initialElementsLength = context->layoutElements.length;
Clay_TextElementConfig *infoTextConfig = CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE });
Clay_TextElementConfig *infoTitleConfig = CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE });
- Clay_ElementId scrollId = Clay__HashString(CLAY_STRING("Clay__DebugViewOuterScrollPane"), 0, 0);
+ Clay_ElementId scrollId = Clay__HashString(CLAY_STRING("Clay__DebugViewOuterScrollPane"), 0);
float scrollYOffset = 0;
bool pointerInDebugView = context->pointerInfo.position.y < context->layoutDimensions.height - 300;
for (int32_t i = 0; i < context->scrollContainerDatas.length; ++i) {
@@ -3437,7 +3478,7 @@ void Clay__RenderDebugView(void) {
CLAY({ .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(1)} }, .backgroundColor = CLAY__DEBUGVIEW_COLOR_3 } ) {}
CLAY({ .id = scrollId, .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)} }, .clip = { .horizontal = true, .vertical = true, .childOffset = Clay_GetScrollOffset() } }) {
CLAY({ .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, .layoutDirection = CLAY_TOP_TO_BOTTOM }, .backgroundColor = ((initialElementsLength + initialRootsLength) & 1) == 0 ? CLAY__DEBUGVIEW_COLOR_2 : CLAY__DEBUGVIEW_COLOR_1 }) {
- Clay_ElementId panelContentsId = Clay__HashString(CLAY_STRING("Clay__DebugViewPaneOuter"), 0, 0);
+ Clay_ElementId panelContentsId = Clay__HashString(CLAY_STRING("Clay__DebugViewPaneOuter"), 0);
// Element list
CLAY({ .id = panelContentsId, .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)} }, .floating = { .zIndex = 32766, .pointerCaptureMode = CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH, .attachTo = CLAY_ATTACH_TO_PARENT, .clipTo = CLAY_CLIP_TO_ATTACHED_PARENT } }) {
CLAY({ .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, .padding = { CLAY__DEBUGVIEW_OUTER_PADDING, CLAY__DEBUGVIEW_OUTER_PADDING, 0, 0 }, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) {
@@ -3609,7 +3650,16 @@ void Clay__RenderDebugView(void) {
CLAY({ .id = CLAY_ID("Clay__DebugViewElementInfoAspectRatioBody"), .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) {
CLAY_TEXT(CLAY_STRING("Aspect Ratio"), infoTitleConfig);
// Aspect Ratio
- CLAY_TEXT(Clay__IntToString(aspectRatioConfig->aspectRatio), infoTextConfig);
+ CLAY({ .id = CLAY_ID("Clay__DebugViewElementInfoAspectRatio") }) {
+ CLAY_TEXT(Clay__IntToString(aspectRatioConfig->aspectRatio), infoTextConfig);
+ CLAY_TEXT(CLAY_STRING("."), infoTextConfig);
+ float frac = aspectRatioConfig->aspectRatio - (int)(aspectRatioConfig->aspectRatio);
+ frac *= 100;
+ if ((int)frac < 10) {
+ CLAY_TEXT(CLAY_STRING("0"), infoTextConfig);
+ }
+ CLAY_TEXT(Clay__IntToString(frac), infoTextConfig);
+ }
}
break;
}
@@ -3940,7 +3990,7 @@ void Clay_SetPointerState(Clay_Vector2 position, bool isPointerDown) {
Clay_BoundingBox elementBox = mapItem->boundingBox;
elementBox.x -= root->pointerOffset.x;
elementBox.y -= root->pointerOffset.y;
- if ((Clay__PointIsInsideRect(position, elementBox)) && (clipElementId == 0 || (Clay__PointIsInsideRect(position, clipItem->boundingBox)))) {
+ if ((Clay__PointIsInsideRect(position, elementBox)) && (clipElementId == 0 || (Clay__PointIsInsideRect(position, clipItem->boundingBox)) || context->externalScrollHandlingEnabled)) {
if (mapItem->onHoverFunction) {
mapItem->onHoverFunction(mapItem->elementId, context->pointerInfo, mapItem->hoverFunctionUserData);
}
@@ -4212,12 +4262,12 @@ Clay_RenderCommandArray Clay_EndLayout(void) {
CLAY_WASM_EXPORT("Clay_GetElementId")
Clay_ElementId Clay_GetElementId(Clay_String idString) {
- return Clay__HashString(idString, 0, 0);
+ return Clay__HashString(idString, 0);
}
CLAY_WASM_EXPORT("Clay_GetElementIdWithIndex")
Clay_ElementId Clay_GetElementIdWithIndex(Clay_String idString, uint32_t index) {
- return Clay__HashString(idString, index, 0);
+ return Clay__HashStringWithOffset(idString, index, 0);
}
bool Clay_Hovered(void) {
diff --git a/examples/SDL3-simple-demo/main.c b/examples/SDL3-simple-demo/main.c
index de4aa3d..ee9f605 100644
--- a/examples/SDL3-simple-demo/main.c
+++ b/examples/SDL3-simple-demo/main.c
@@ -32,6 +32,7 @@ static inline Clay_Dimensions SDL_MeasureText(Clay_StringSlice text, Clay_TextEl
TTF_Font *font = fonts[config->fontId];
int width, height;
+ TTF_SetFontSize(font, config->fontSize);
if (!TTF_GetStringSize(font, text.chars, text.length, &width, &height)) {
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to measure text: %s", SDL_GetError());
}
diff --git a/examples/cairo-pdf-rendering/main.c b/examples/cairo-pdf-rendering/main.c
index b1d1306..75dbbd6 100644
--- a/examples/cairo-pdf-rendering/main.c
+++ b/examples/cairo-pdf-rendering/main.c
@@ -88,7 +88,7 @@ void Layout() {
.childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER }},
.border = { .color = PRIMARY, .width = 2, 2, 2, 2 }, .cornerRadius = 10
}) {
- CLAY({ .layout = { .sizing = { CLAY_SIZING_FIXED(32), CLAY_SIZING_FIXED(32) } }, .image = { .sourceDimensions = { 32, 32 }, .imageData = "resources/check.png" }});
+ CLAY({ .layout = { .sizing = { CLAY_SIZING_FIXED(32), CLAY_SIZING_FIXED(32) } }, .image = { .imageData = "resources/check.png" }});
}
}
}
diff --git a/examples/clay-official-website/build/clay/index.wasm b/examples/clay-official-website/build/clay/index.wasm
index fc9be6f..8b113a8 100755
Binary files a/examples/clay-official-website/build/clay/index.wasm and b/examples/clay-official-website/build/clay/index.wasm differ
diff --git a/examples/raylib-sidebar-scrolling-container/main.c b/examples/raylib-sidebar-scrolling-container/main.c
index 97c7042..67d1abc 100644
--- a/examples/raylib-sidebar-scrolling-container/main.c
+++ b/examples/raylib-sidebar-scrolling-container/main.c
@@ -69,7 +69,7 @@ Clay_RenderCommandArray CreateLayout(void) {
})
{
CLAY({ .id = CLAY_ID("FloatingContainer"),
- .layout = { .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_FIXED(300) }, .padding = { 16, 16, 16, 16 }},
+ .layout = { .sizing = { .width = CLAY_SIZING_PERCENT(0.5), .height = CLAY_SIZING_FIXED(300) }, .padding = { 16, 16, 16, 16 }},
.backgroundColor = { 140, 80, 200, 200 },
.floating = { .attachTo = CLAY_ATTACH_TO_PARENT, .zIndex = 1, .attachPoints = { CLAY_ATTACH_POINT_CENTER_TOP, CLAY_ATTACH_POINT_CENTER_TOP }, .offset = {0, 0} },
.border = { .width = CLAY_BORDER_OUTSIDE(2), .color = {80, 80, 80, 255} },
@@ -108,8 +108,8 @@ Clay_RenderCommandArray CreateLayout(void) {
CLAY({ .id = CLAY_ID("Blob4Floating2"), .floating = { .attachTo = CLAY_ATTACH_TO_ELEMENT_WITH_ID, .zIndex = 1, .parentId = Clay_GetElementId(CLAY_STRING("SidebarBlob4")).id } }) {
CLAY({ .id = CLAY_ID("ScrollContainer"), .layout = { .sizing = { .height = CLAY_SIZING_FIXED(200) }, .childGap = 2 }, .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() } }) {
- CLAY({ .id = CLAY_ID("FloatingContainer2"), .floating = { .attachTo = CLAY_ATTACH_TO_PARENT, .zIndex = 1 } }) {
- CLAY({ .id = CLAY_ID("FloatingContainerInner"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_FIXED(300) }, .padding = {16, 16, 16, 16} }, .backgroundColor = {140,80, 200, 200} }) {
+ CLAY({ .id = CLAY_ID("FloatingContainer2"), .layout.sizing.height = CLAY_SIZING_GROW(), .floating = { .attachTo = CLAY_ATTACH_TO_PARENT, .zIndex = 1 } }) {
+ CLAY({ .id = CLAY_ID("FloatingContainerInner"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_GROW() }, .padding = {16, 16, 16, 16} }, .backgroundColor = {140,80, 200, 200} }) {
CLAY_TEXT(CLAY_STRING("I'm an inline floating container."), CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {255,255,255,255} }));
}
}
@@ -133,7 +133,7 @@ Clay_RenderCommandArray CreateLayout(void) {
}) {
CLAY({ .id = CLAY_ID("ScrollBarButton"),
.layout = { .sizing = {CLAY_SIZING_FIXED(12), CLAY_SIZING_FIXED((scrollData.scrollContainerDimensions.height / scrollData.contentDimensions.height) * scrollData.scrollContainerDimensions.height) }},
- .backgroundColor = Clay_PointerOver(Clay__HashString(CLAY_STRING("ScrollBar"), 0, 0)) ? (Clay_Color){100, 100, 140, 150} : (Clay_Color){120, 120, 160, 150} ,
+ .backgroundColor = Clay_PointerOver(Clay_GetElementId(CLAY_STRING("ScrollBar"))) ? (Clay_Color){100, 100, 140, 150} : (Clay_Color){120, 120, 160, 150} ,
.cornerRadius = CLAY_CORNER_RADIUS(6)
}) {}
}
@@ -172,13 +172,13 @@ void UpdateDrawFrame(Font* fonts)
scrollbarData.mouseDown = false;
}
- if (IsMouseButtonDown(0) && !scrollbarData.mouseDown && Clay_PointerOver(Clay__HashString(CLAY_STRING("ScrollBar"), 0, 0))) {
- Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay__HashString(CLAY_STRING("MainContent"), 0, 0));
+ if (IsMouseButtonDown(0) && !scrollbarData.mouseDown && Clay_PointerOver(Clay_GetElementId(CLAY_STRING("ScrollBar")))) {
+ Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("MainContent")));
scrollbarData.clickOrigin = mousePosition;
scrollbarData.positionOrigin = *scrollContainerData.scrollPosition;
scrollbarData.mouseDown = true;
} else if (scrollbarData.mouseDown) {
- Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay__HashString(CLAY_STRING("MainContent"), 0, 0));
+ Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("MainContent")));
if (scrollContainerData.contentDimensions.height > 0) {
Clay_Vector2 ratio = (Clay_Vector2) {
scrollContainerData.contentDimensions.width / scrollContainerData.scrollContainerDimensions.width,
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/SDL2/clay_renderer_SDL2.c b/renderers/SDL2/clay_renderer_SDL2.c
index 6eccb01..6372ba7 100644
--- a/renderers/SDL2/clay_renderer_SDL2.c
+++ b/renderers/SDL2/clay_renderer_SDL2.c
@@ -23,6 +23,7 @@ static Clay_Dimensions SDL2_MeasureText(Clay_StringSlice text, Clay_TextElementC
SDL2_Font *fonts = (SDL2_Font*)userData;
TTF_Font *font = fonts[config->fontId].font;
+ TTF_SetFontSize(font, config->fontSize);
char *chars = (char *)calloc(text.length + 1, 1);
memcpy(chars, text.chars, text.length);
int width = 0;
@@ -294,6 +295,7 @@ static void Clay_SDL2_Render(SDL_Renderer *renderer, Clay_RenderCommandArray ren
char *cloned = (char *)calloc(config->stringContents.length + 1, 1);
memcpy(cloned, config->stringContents.chars, config->stringContents.length);
TTF_Font* font = fonts[config->fontId].font;
+ TTF_SetFontSize(font, config->fontSize);
SDL_Surface *surface = TTF_RenderUTF8_Blended(font, cloned, (SDL_Color) {
.r = (Uint8)config->textColor.r,
.g = (Uint8)config->textColor.g,
diff --git a/renderers/SDL3/clay_renderer_SDL3.c b/renderers/SDL3/clay_renderer_SDL3.c
index b918c97..fc71290 100644
--- a/renderers/SDL3/clay_renderer_SDL3.c
+++ b/renderers/SDL3/clay_renderer_SDL3.c
@@ -164,6 +164,7 @@ static void SDL_Clay_RenderClayCommands(Clay_SDL3RendererData *rendererData, Cla
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
Clay_TextRenderData *config = &rcmd->renderData.text;
TTF_Font *font = rendererData->fonts[config->fontId];
+ TTF_SetFontSize(font, config->fontSize);
TTF_Text *text = TTF_CreateText(rendererData->textEngine, font, config->stringContents.chars, config->stringContents.length);
TTF_SetTextColor(text, config->textColor.r, config->textColor.g, config->textColor.b, config->textColor.a);
TTF_DrawRendererText(text, rect.x, rect.y);
@@ -184,11 +185,11 @@ static void SDL_Clay_RenderClayCommands(Clay_SDL3RendererData *rendererData, Cla
if (config->width.left > 0) {
const float starting_y = rect.y + clampedRadii.topLeft;
const float length = rect.h - clampedRadii.topLeft - clampedRadii.bottomLeft;
- SDL_FRect line = { rect.x, starting_y, config->width.left, length };
+ SDL_FRect line = { rect.x - 1, starting_y, config->width.left, length };
SDL_RenderFillRect(rendererData->renderer, &line);
}
if (config->width.right > 0) {
- const float starting_x = rect.x + rect.w - (float)config->width.right;
+ const float starting_x = rect.x + rect.w - (float)config->width.right + 1;
const float starting_y = rect.y + clampedRadii.topRight;
const float length = rect.h - clampedRadii.topRight - clampedRadii.bottomRight;
SDL_FRect line = { starting_x, starting_y, config->width.right, length };
@@ -197,12 +198,12 @@ static void SDL_Clay_RenderClayCommands(Clay_SDL3RendererData *rendererData, Cla
if (config->width.top > 0) {
const float starting_x = rect.x + clampedRadii.topLeft;
const float length = rect.w - clampedRadii.topLeft - clampedRadii.topRight;
- SDL_FRect line = { starting_x, rect.y, length, config->width.top };
+ SDL_FRect line = { starting_x, rect.y - 1, length, config->width.top };
SDL_RenderFillRect(rendererData->renderer, &line);
}
if (config->width.bottom > 0) {
const float starting_x = rect.x + clampedRadii.bottomLeft;
- const float starting_y = rect.y + rect.h - (float)config->width.bottom;
+ const float starting_y = rect.y + rect.h - (float)config->width.bottom + 1;
const float length = rect.w - clampedRadii.bottomLeft - clampedRadii.bottomRight;
SDL_FRect line = { starting_x, starting_y, length, config->width.bottom };
SDL_SetRenderDrawColor(rendererData->renderer, config->color.r, config->color.g, config->color.b, config->color.a);
@@ -211,25 +212,25 @@ static void SDL_Clay_RenderClayCommands(Clay_SDL3RendererData *rendererData, Cla
//corners
if (config->cornerRadius.topLeft > 0) {
const float centerX = rect.x + clampedRadii.topLeft -1;
- const float centerY = rect.y + clampedRadii.topLeft;
+ const float centerY = rect.y + clampedRadii.topLeft - 1;
SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.topLeft,
180.0f, 270.0f, config->width.top, config->color);
}
if (config->cornerRadius.topRight > 0) {
- const float centerX = rect.x + rect.w - clampedRadii.topRight -1;
- const float centerY = rect.y + clampedRadii.topRight;
+ const float centerX = rect.x + rect.w - clampedRadii.topRight;
+ const float centerY = rect.y + clampedRadii.topRight - 1;
SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.topRight,
270.0f, 360.0f, config->width.top, config->color);
}
if (config->cornerRadius.bottomLeft > 0) {
const float centerX = rect.x + clampedRadii.bottomLeft -1;
- const float centerY = rect.y + rect.h - clampedRadii.bottomLeft -1;
+ const float centerY = rect.y + rect.h - clampedRadii.bottomLeft;
SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.bottomLeft,
90.0f, 180.0f, config->width.bottom, config->color);
}
if (config->cornerRadius.bottomRight > 0) {
- const float centerX = rect.x + rect.w - clampedRadii.bottomRight -1; //TODO: why need to -1 in all calculations???
- const float centerY = rect.y + rect.h - clampedRadii.bottomRight -1;
+ const float centerX = rect.x + rect.w - clampedRadii.bottomRight;
+ const float centerY = rect.y + rect.h - clampedRadii.bottomRight;
SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.bottomRight,
0.0f, 90.0f, config->width.bottom, config->color);
}
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