feat: rudimentary userspace desktop environment

This commit is contained in:
2026-02-20 00:48:51 +01:00
parent 3753ebd4f4
commit 6eebc64863
27 changed files with 5027 additions and 9 deletions
+194 -2
View File
@@ -17,6 +17,7 @@
#include <Libraries/Memory.hpp> #include <Libraries/Memory.hpp>
#include <Libraries/String.hpp> #include <Libraries/String.hpp>
#include <Drivers/PS2/Keyboard.hpp> #include <Drivers/PS2/Keyboard.hpp>
#include <Drivers/PS2/Mouse.hpp>
#include <Net/Icmp.hpp> #include <Net/Icmp.hpp>
#include <Net/Dns.hpp> #include <Net/Dns.hpp>
#include <Net/Socket.hpp> #include <Net/Socket.hpp>
@@ -53,11 +54,53 @@ namespace Zenith {
return Sched::GetCurrentPid(); return Sched::GetCurrentPid();
} }
static void RingWrite(uint8_t* buf, uint32_t& head, uint32_t /*tail*/, uint32_t size, uint8_t byte) {
buf[head] = byte;
head = (head + 1) % size;
}
static int RingRead(uint8_t* buf, uint32_t& head, uint32_t& tail, uint32_t size, uint8_t* out, int maxLen) {
int count = 0;
while (tail != head && count < maxLen) {
out[count++] = buf[tail];
tail = (tail + 1) % size;
}
return count;
}
// Find the process that owns the I/O ring buffers for a redirected process.
// If proc owns buffers itself (spawned via spawn_redir), returns proc.
// If proc inherited redirection (spawned via spawn from a redirected parent),
// follows parentPid to find the buffer owner.
static Sched::Process* GetRedirTarget(Sched::Process* proc) {
if (!proc || !proc->redirected) return nullptr;
if (proc->outBuf) return proc; // owns buffers
return Sched::GetProcessByPid(proc->parentPid);
}
static void Sys_Print(const char* text) { static void Sys_Print(const char* text) {
auto* proc = Sched::GetCurrentProcessPtr();
if (proc && proc->redirected) {
auto* target = GetRedirTarget(proc);
if (target && target->outBuf) {
for (int i = 0; text[i]; i++) {
RingWrite(target->outBuf, target->outHead, target->outTail, Sched::Process::IoBufSize, (uint8_t)text[i]);
}
return;
}
}
Kt::Print(text); Kt::Print(text);
} }
static void Sys_Putchar(char c) { static void Sys_Putchar(char c) {
auto* proc = Sched::GetCurrentProcessPtr();
if (proc && proc->redirected) {
auto* target = GetRedirTarget(proc);
if (target && target->outBuf) {
RingWrite(target->outBuf, target->outHead, target->outTail, Sched::Process::IoBufSize, (uint8_t)c);
return;
}
}
Kt::Putchar(c); Kt::Putchar(c);
} }
@@ -162,11 +205,29 @@ namespace Zenith {
} }
static bool Sys_IsKeyAvailable() { static bool Sys_IsKeyAvailable() {
auto* proc = Sched::GetCurrentProcessPtr();
if (proc && proc->redirected) {
auto* target = GetRedirTarget(proc);
if (target) return target->keyHead != target->keyTail;
}
return Drivers::PS2::Keyboard::IsKeyAvailable(); return Drivers::PS2::Keyboard::IsKeyAvailable();
} }
static void Sys_GetKey(KeyEvent* outEvent) { static void Sys_GetKey(KeyEvent* outEvent) {
if (outEvent == nullptr) return; if (outEvent == nullptr) return;
auto* proc = Sched::GetCurrentProcessPtr();
if (proc && proc->redirected) {
auto* target = GetRedirTarget(proc);
if (target) {
// Wait for key in target's keyBuf ring
while (target->keyHead == target->keyTail) {
Sched::Schedule();
}
*outEvent = target->keyBuf[target->keyTail];
target->keyTail = (target->keyTail + 1) % 64;
return;
}
}
auto k = Drivers::PS2::Keyboard::GetKey(); auto k = Drivers::PS2::Keyboard::GetKey();
outEvent->scancode = k.Scancode; outEvent->scancode = k.Scancode;
outEvent->ascii = k.Ascii; outEvent->ascii = k.Ascii;
@@ -177,6 +238,19 @@ namespace Zenith {
} }
static char Sys_GetChar() { static char Sys_GetChar() {
auto* proc = Sched::GetCurrentProcessPtr();
if (proc && proc->redirected) {
auto* target = GetRedirTarget(proc);
if (target && target->inBuf) {
// Wait for data in target's inBuf ring
while (target->inTail == target->inHead) {
Sched::Schedule(); // yield until parent writes
}
uint8_t c = target->inBuf[target->inTail];
target->inTail = (target->inTail + 1) % Sched::Process::IoBufSize;
return (char)c;
}
}
return Drivers::PS2::Keyboard::GetChar(); return Drivers::PS2::Keyboard::GetChar();
} }
@@ -249,7 +323,26 @@ namespace Zenith {
} }
static int Sys_Spawn(const char* path, const char* args) { static int Sys_Spawn(const char* path, const char* args) {
return Sched::Spawn(path, args); auto* parent = Sched::GetCurrentProcessPtr();
int childPid = Sched::Spawn(path, args);
if (childPid < 0) return childPid;
// Inherit I/O redirection: if the parent is redirected, the child
// is marked redirected too. It stores a parentPid pointing to the
// process that owns the actual ring buffers (the one spawned via
// spawn_redir). The child does NOT get its own buffers — Sys_Print
// et al. look up the buffer owner at write time.
if (parent && parent->redirected) {
auto* child = Sched::GetProcessByPid(childPid);
if (child) {
child->redirected = true;
// Point to the buffer owner: if parent owns buffers, target parent;
// if parent itself inherited, follow the chain.
child->parentPid = parent->outBuf ? parent->pid : parent->parentPid;
}
}
return childPid;
} }
static int Sys_GetArgs(char* buf, uint64_t maxLen) { static int Sys_GetArgs(char* buf, uint64_t maxLen) {
@@ -264,6 +357,14 @@ namespace Zenith {
} }
static uint64_t Sys_TermSize() { static uint64_t Sys_TermSize() {
// If the process is redirected to a GUI terminal, return those dimensions
auto* proc = Sched::GetCurrentProcessPtr();
if (proc && proc->redirected) {
auto* target = GetRedirTarget(proc);
if (target && target->termCols > 0 && target->termRows > 0) {
return ((uint64_t)target->termRows << 32) | ((uint64_t)target->termCols & 0xFFFFFFFF);
}
}
size_t cols = 0, rows = 0; size_t cols = 0, rows = 0;
flanterm_get_dimensions(Kt::ctx, &cols, &rows); flanterm_get_dimensions(Kt::ctx, &cols, &rows);
return (rows << 32) | (cols & 0xFFFFFFFF); return (rows << 32) | (cols & 0xFFFFFFFF);
@@ -429,6 +530,81 @@ namespace Zenith {
return (int64_t)len; return (int64_t)len;
} }
// ---- Mouse syscalls ----
static void Sys_MouseState(MouseState* out) {
if (out == nullptr) return;
auto state = Drivers::PS2::Mouse::GetMouseState();
out->x = state.X;
out->y = state.Y;
out->scrollDelta = state.ScrollDelta;
out->buttons = state.Buttons;
}
static void Sys_SetMouseBounds(int32_t maxX, int32_t maxY) {
Drivers::PS2::Mouse::SetBounds(maxX, maxY);
}
// ---- I/O redirection syscalls ----
static int Sys_SpawnRedir(const char* path, const char* args) {
int childPid = Sched::Spawn(path, args);
if (childPid < 0) return -1;
auto* child = Sched::GetProcessByPid(childPid);
if (child == nullptr) return -1;
// Allocate ring buffers
void* outPage = Memory::g_pfa->AllocateZeroed();
void* inPage = Memory::g_pfa->AllocateZeroed();
if (!outPage || !inPage) return -1;
child->outBuf = (uint8_t*)outPage;
child->inBuf = (uint8_t*)inPage;
child->outHead = 0;
child->outTail = 0;
child->inHead = 0;
child->inTail = 0;
child->keyHead = 0;
child->keyTail = 0;
child->redirected = true;
child->parentPid = Sched::GetCurrentPid();
return childPid;
}
static int Sys_ChildIoRead(int childPid, char* buf, int maxLen) {
auto* child = Sched::GetProcessByPid(childPid);
if (child == nullptr || !child->redirected || !child->outBuf) return -1;
return RingRead(child->outBuf, child->outHead, child->outTail, Sched::Process::IoBufSize, (uint8_t*)buf, maxLen);
}
static int Sys_ChildIoWrite(int childPid, const char* data, int len) {
auto* child = Sched::GetProcessByPid(childPid);
if (child == nullptr || !child->redirected || !child->inBuf) return -1;
for (int i = 0; i < len; i++) {
RingWrite(child->inBuf, child->inHead, child->inTail, Sched::Process::IoBufSize, (uint8_t)data[i]);
}
return len;
}
static int Sys_ChildIoWriteKey(int childPid, const KeyEvent* key) {
if (key == nullptr) return -1;
auto* child = Sched::GetProcessByPid(childPid);
if (child == nullptr || !child->redirected) return -1;
child->keyBuf[child->keyHead] = *key;
child->keyHead = (child->keyHead + 1) % 64;
return 0;
}
static int Sys_ChildIoSetTermsz(int childPid, int cols, int rows) {
auto* child = Sched::GetProcessByPid(childPid);
if (child == nullptr || !child->redirected) return -1;
child->termCols = cols;
child->termRows = rows;
return 0;
}
// ---- Dispatch ---- // ---- Dispatch ----
extern "C" int64_t SyscallDispatch(SyscallFrame* frame) { extern "C" int64_t SyscallDispatch(SyscallFrame* frame) {
@@ -551,6 +727,22 @@ namespace Zenith {
return Sys_GetRandom((uint8_t*)frame->arg1, frame->arg2); return Sys_GetRandom((uint8_t*)frame->arg1, frame->arg2);
case SYS_KLOG: case SYS_KLOG:
return Kt::ReadKernelLog((char*)frame->arg1, frame->arg2); return Kt::ReadKernelLog((char*)frame->arg1, frame->arg2);
case SYS_MOUSESTATE:
Sys_MouseState((MouseState*)frame->arg1);
return 0;
case SYS_SETMOUSEBOUNDS:
Sys_SetMouseBounds((int32_t)frame->arg1, (int32_t)frame->arg2);
return 0;
case SYS_SPAWN_REDIR:
return (int64_t)Sys_SpawnRedir((const char*)frame->arg1, (const char*)frame->arg2);
case SYS_CHILDIO_READ:
return (int64_t)Sys_ChildIoRead((int)frame->arg1, (char*)frame->arg2, (int)frame->arg3);
case SYS_CHILDIO_WRITE:
return (int64_t)Sys_ChildIoWrite((int)frame->arg1, (const char*)frame->arg2, (int)frame->arg3);
case SYS_CHILDIO_WRITEKEY:
return (int64_t)Sys_ChildIoWriteKey((int)frame->arg1, (const KeyEvent*)frame->arg2);
case SYS_CHILDIO_SETTERMSZ:
return (int64_t)Sys_ChildIoSetTermsz((int)frame->arg1, (int)frame->arg2, (int)frame->arg3);
default: default:
return -1; return -1;
} }
@@ -577,7 +769,7 @@ namespace Zenith {
Hal::WriteMSR(Hal::IA32_FMASK, 0x200); Hal::WriteMSR(Hal::IA32_FMASK, 0x200);
Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR=" Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR="
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 47 syscalls)"; << kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 53 syscalls)";
} }
} }
+14
View File
@@ -58,6 +58,13 @@ namespace Zenith {
static constexpr uint64_t SYS_RESOLVE = 44; static constexpr uint64_t SYS_RESOLVE = 44;
static constexpr uint64_t SYS_GETRANDOM = 45; static constexpr uint64_t SYS_GETRANDOM = 45;
static constexpr uint64_t SYS_KLOG = 46; static constexpr uint64_t SYS_KLOG = 46;
static constexpr uint64_t SYS_MOUSESTATE = 47;
static constexpr uint64_t SYS_SETMOUSEBOUNDS = 48;
static constexpr uint64_t SYS_SPAWN_REDIR = 49;
static constexpr uint64_t SYS_CHILDIO_READ = 50;
static constexpr uint64_t SYS_CHILDIO_WRITE = 51;
static constexpr uint64_t SYS_CHILDIO_WRITEKEY = 52;
static constexpr uint64_t SYS_CHILDIO_SETTERMSZ = 53;
static constexpr int SOCK_TCP = 1; static constexpr int SOCK_TCP = 1;
static constexpr int SOCK_UDP = 2; static constexpr int SOCK_UDP = 2;
@@ -104,6 +111,13 @@ namespace Zenith {
bool alt; bool alt;
}; };
struct MouseState {
int32_t x;
int32_t y;
int32_t scrollDelta;
uint8_t buttons;
};
// Stack frame pushed by SyscallEntry.asm // Stack frame pushed by SyscallEntry.asm
struct SyscallFrame { struct SyscallFrame {
uint64_t r15, r14, r13, r12, rbp, rbx; // callee-saved uint64_t r15, r14, r13, r12, rbp, rbx; // callee-saved
+36
View File
@@ -76,6 +76,18 @@ namespace Sched {
processTable[i].userStackTop = 0; processTable[i].userStackTop = 0;
processTable[i].heapNext = 0; processTable[i].heapNext = 0;
processTable[i].args[0] = '\0'; processTable[i].args[0] = '\0';
processTable[i].redirected = false;
processTable[i].parentPid = -1;
processTable[i].outBuf = nullptr;
processTable[i].outHead = 0;
processTable[i].outTail = 0;
processTable[i].inBuf = nullptr;
processTable[i].inHead = 0;
processTable[i].inTail = 0;
processTable[i].keyHead = 0;
processTable[i].keyTail = 0;
processTable[i].termCols = 0;
processTable[i].termRows = 0;
} }
currentPid = -1; currentPid = -1;
@@ -199,6 +211,19 @@ namespace Sched {
proc.args[i] = '\0'; proc.args[i] = '\0';
} }
proc.redirected = false;
proc.parentPid = -1;
proc.outBuf = nullptr;
proc.outHead = 0;
proc.outTail = 0;
proc.inBuf = nullptr;
proc.inHead = 0;
proc.inTail = 0;
proc.keyHead = 0;
proc.keyTail = 0;
proc.termCols = 0;
proc.termRows = 0;
return proc.pid; return proc.pid;
} }
@@ -326,4 +351,15 @@ namespace Sched {
return false; return false;
} }
Process* GetProcessByPid(int pid) {
for (int i = 0; i < MaxProcesses; i++) {
if (processTable[i].pid == pid &&
(processTable[i].state == ProcessState::Ready ||
processTable[i].state == ProcessState::Running)) {
return &processTable[i];
}
}
return nullptr;
}
} }
+22
View File
@@ -6,6 +6,7 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <Api/Syscall.hpp>
namespace Sched { namespace Sched {
@@ -39,6 +40,24 @@ namespace Sched {
uint64_t userStackTop; // User-space stack top uint64_t userStackTop; // User-space stack top
uint64_t heapNext; // Simple bump allocator for user heap uint64_t heapNext; // Simple bump allocator for user heap
char args[256]; // Command-line arguments (set by parent via Spawn) char args[256]; // Command-line arguments (set by parent via Spawn)
// I/O redirection for GUI terminal
bool redirected = false;
int parentPid = -1;
uint8_t* outBuf = nullptr; // 4KB ring: child writes (print/putchar), parent reads
uint32_t outHead = 0;
uint32_t outTail = 0;
uint8_t* inBuf = nullptr; // 4KB ring: parent writes, child reads (getchar)
uint32_t inHead = 0;
uint32_t inTail = 0;
Zenith::KeyEvent keyBuf[64]; // parent injects, child reads (getkey/iskeyavailable)
uint32_t keyHead = 0;
uint32_t keyTail = 0;
static constexpr uint32_t IoBufSize = 4096;
// GUI terminal dimensions (set by desktop, read by SYS_TERMSIZE)
int termCols = 0;
int termRows = 0;
}; };
void Initialize(); void Initialize();
@@ -60,4 +79,7 @@ namespace Sched {
// Check if a process is still alive (Ready or Running) // Check if a process is still alive (Ready or Running)
bool IsAlive(int pid); bool IsAlive(int pid);
// Find a process by PID (returns nullptr if not found or not alive)
Process* GetProcessByPid(int pid);
} }
+5 -1
View File
@@ -149,7 +149,11 @@ namespace Timekeeping {
void Sleep(uint64_t ms) { void Sleep(uint64_t ms) {
uint64_t target = g_tickCount + ms; uint64_t target = g_tickCount + ms;
while (g_tickCount < target) { while (g_tickCount < target) {
asm volatile("hlt"); // Yield to other processes instead of hlt. Using hlt here causes
// a deadlock: if the timer ISR preempts us during hlt and context-
// switches away (via Tick → Schedule), EOI is never sent, so no
// more timer interrupts fire and any process doing hlt freezes.
Sched::Schedule();
} }
} }
}; };
+12 -3
View File
@@ -57,7 +57,7 @@ BINDIR := bin
PROGRAMS := $(notdir $(wildcard src/*)) PROGRAMS := $(notdir $(wildcard src/*))
# Programs with custom Makefiles (built separately). # Programs with custom Makefiles (built separately).
CUSTOM_BUILDS := doom fetch wiki CUSTOM_BUILDS := doom fetch wiki desktop
SYSTEM_PROGRAMS := $(filter-out $(CUSTOM_BUILDS),$(PROGRAMS)) SYSTEM_PROGRAMS := $(filter-out $(CUSTOM_BUILDS),$(PROGRAMS))
# Build targets: system programs go to bin/os/, games are handled separately. # Build targets: system programs go to bin/os/, games are handled separately.
@@ -82,9 +82,9 @@ CA_CERTS := $(BINDIR)/etc/ca-certificates.crt
# Home directory placeholder. # Home directory placeholder.
HOMEKEEP := $(BINDIR)/home/.keep HOMEKEEP := $(BINDIR)/home/.keep
.PHONY: all clean doom fetch wiki bearssl libc .PHONY: all clean doom fetch wiki desktop icons bearssl libc
all: bearssl libc $(TARGETS) fetch wiki doom $(GAME_DATA) $(MANDST) $(WWWDST) $(CA_CERTS) $(HOMEKEEP) all: bearssl libc $(TARGETS) fetch wiki doom desktop icons $(GAME_DATA) $(MANDST) $(WWWDST) $(CA_CERTS) $(HOMEKEEP)
# Build BearSSL static library. # Build BearSSL static library.
bearssl: bearssl:
@@ -102,6 +102,14 @@ fetch: bearssl libc
wiki: bearssl libc wiki: bearssl libc
$(MAKE) -C src/wiki $(MAKE) -C src/wiki
# Build desktop via its own Makefile.
desktop:
$(MAKE) -C src/desktop
# Copy SVG icons for the desktop into bin/icons/.
icons:
../scripts/copy_icons.sh
# Build doom via its own Makefile. # Build doom via its own Makefile.
doom: doom:
$(MAKE) -C src/doom $(MAKE) -C src/doom
@@ -143,3 +151,4 @@ clean:
$(MAKE) -C lib/libc clean $(MAKE) -C lib/libc clean
$(MAKE) -C src/fetch clean $(MAKE) -C src/fetch clean
$(MAKE) -C src/doom clean $(MAKE) -C src/doom clean
$(MAKE) -C src/desktop clean
+15
View File
@@ -57,6 +57,14 @@ namespace Zenith {
static constexpr uint64_t SYS_TERMSCALE = 43; static constexpr uint64_t SYS_TERMSCALE = 43;
static constexpr uint64_t SYS_RESOLVE = 44; static constexpr uint64_t SYS_RESOLVE = 44;
static constexpr uint64_t SYS_GETRANDOM = 45; static constexpr uint64_t SYS_GETRANDOM = 45;
static constexpr uint64_t SYS_KLOG = 46;
static constexpr uint64_t SYS_MOUSESTATE = 47;
static constexpr uint64_t SYS_SETMOUSEBOUNDS = 48;
static constexpr uint64_t SYS_SPAWN_REDIR = 49;
static constexpr uint64_t SYS_CHILDIO_READ = 50;
static constexpr uint64_t SYS_CHILDIO_WRITE = 51;
static constexpr uint64_t SYS_CHILDIO_WRITEKEY = 52;
static constexpr uint64_t SYS_CHILDIO_SETTERMSZ = 53;
static constexpr int SOCK_TCP = 1; static constexpr int SOCK_TCP = 1;
static constexpr int SOCK_UDP = 2; static constexpr int SOCK_UDP = 2;
@@ -103,4 +111,11 @@ namespace Zenith {
bool alt; bool alt;
}; };
struct MouseState {
int32_t x;
int32_t y;
int32_t scrollDelta;
uint8_t buttons;
};
} }
+55
View File
@@ -0,0 +1,55 @@
/*
* desktop.hpp
* ZenithOS desktop state and compositor declarations
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include "gui/gui.hpp"
#include "gui/framebuffer.hpp"
#include "gui/svg.hpp"
#include "gui/window.hpp"
#include "gui/widgets.hpp"
#include "gui/terminal.hpp"
#include <Api/Syscall.hpp>
namespace gui {
static constexpr int MAX_WINDOWS = 8;
static constexpr int PANEL_HEIGHT = 32;
struct DesktopState {
Framebuffer fb;
Window windows[MAX_WINDOWS];
int window_count;
int focused_window;
Zenith::MouseState mouse;
uint8_t prev_buttons;
bool app_menu_open;
SvgIcon icon_terminal;
SvgIcon icon_filemanager;
SvgIcon icon_sysinfo;
SvgIcon icon_appmenu;
SvgIcon icon_folder;
SvgIcon icon_file;
SvgIcon icon_computer;
int screen_w, screen_h;
};
// Forward declarations - implemented in main.cpp
void desktop_init(DesktopState* ds);
void desktop_run(DesktopState* ds);
void desktop_compose(DesktopState* ds);
int desktop_create_window(DesktopState* ds, const char* title, int x, int y, int w, int h);
void desktop_close_window(DesktopState* ds, int idx);
void desktop_raise_window(DesktopState* ds, int idx);
void desktop_draw_panel(DesktopState* ds);
void desktop_draw_window(DesktopState* ds, int idx);
void desktop_handle_mouse(DesktopState* ds);
void desktop_handle_keyboard(DesktopState* ds, const Zenith::KeyEvent& key);
} // namespace gui
+239
View File
@@ -0,0 +1,239 @@
/*
* draw.hpp
* ZenithOS drawing primitives (lines, circles, rounded rects, cursor)
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include "gui/gui.hpp"
#include "gui/framebuffer.hpp"
namespace gui {
// Fast horizontal line
inline void draw_hline(Framebuffer& fb, int x, int y, int w, Color c) {
for (int i = 0; i < w; i++) fb.put_pixel(x + i, y, c);
}
// Fast vertical line
inline void draw_vline(Framebuffer& fb, int x, int y, int h, Color c) {
for (int i = 0; i < h; i++) fb.put_pixel(x, y + i, c);
}
// Rectangle outline
inline void draw_rect(Framebuffer& fb, int x, int y, int w, int h, Color c) {
draw_hline(fb, x, y, w, c);
draw_hline(fb, x, y + h - 1, w, c);
draw_vline(fb, x, y, h, c);
draw_vline(fb, x + w - 1, y, h, c);
}
// Filled rounded rectangle using corner circles
inline void fill_rounded_rect(Framebuffer& fb, int x, int y, int w, int h, int radius, Color c) {
if (radius <= 0) {
fb.fill_rect(x, y, w, h, c);
return;
}
// Clamp radius to half the smaller dimension
int max_r = gui_min(w / 2, h / 2);
if (radius > max_r) radius = max_r;
// Fill the center rectangle
fb.fill_rect(x + radius, y, w - 2 * radius, h, c);
// Fill the left and right strips (excluding corners)
fb.fill_rect(x, y + radius, radius, h - 2 * radius, c);
fb.fill_rect(x + w - radius, y + radius, radius, h - 2 * radius, c);
// Draw the four rounded corners using midpoint circle
int cx_tl = x + radius;
int cy_tl = y + radius;
int cx_tr = x + w - radius - 1;
int cy_tr = y + radius;
int cx_bl = x + radius;
int cy_bl = y + h - radius - 1;
int cx_br = x + w - radius - 1;
int cy_br = y + h - radius - 1;
int px = 0;
int py = radius;
int d = 1 - radius;
while (px <= py) {
// Top-left corner
draw_hline(fb, cx_tl - py, cy_tl - px, py - radius + radius, c);
draw_hline(fb, cx_tl - px, cy_tl - py, px, c);
// Top-right corner
draw_hline(fb, cx_tr + 1, cy_tr - px, py, c);
draw_hline(fb, cx_tr + 1, cy_tr - py, px, c);
// Bottom-left corner
draw_hline(fb, cx_bl - py, cy_bl + px, py, c);
draw_hline(fb, cx_bl - px, cy_bl + py, px, c);
// Bottom-right corner
draw_hline(fb, cx_br + 1, cy_br + px, py, c);
draw_hline(fb, cx_br + 1, cy_br + py, px, c);
if (d < 0) {
d += 2 * px + 3;
} else {
d += 2 * (px - py) + 5;
py--;
}
px++;
}
}
// Filled circle (midpoint algorithm)
inline void fill_circle(Framebuffer& fb, int cx, int cy, int r, Color c) {
if (r <= 0) return;
int x = 0;
int y = r;
int d = 1 - r;
// Draw initial horizontal lines
draw_hline(fb, cx - r, cy, 2 * r + 1, c);
while (x < y) {
if (d < 0) {
d += 2 * x + 3;
} else {
d += 2 * (x - y) + 5;
y--;
draw_hline(fb, cx - x, cy + y + 1, 2 * x + 1, c);
draw_hline(fb, cx - x, cy - y - 1, 2 * x + 1, c);
}
x++;
draw_hline(fb, cx - y, cy + x, 2 * y + 1, c);
draw_hline(fb, cx - y, cy - x, 2 * y + 1, c);
}
}
// Circle outline (midpoint algorithm)
inline void draw_circle(Framebuffer& fb, int cx, int cy, int r, Color c) {
if (r <= 0) {
fb.put_pixel(cx, cy, c);
return;
}
int x = 0;
int y = r;
int d = 1 - r;
while (x <= y) {
fb.put_pixel(cx + x, cy + y, c);
fb.put_pixel(cx - x, cy + y, c);
fb.put_pixel(cx + x, cy - y, c);
fb.put_pixel(cx - x, cy - y, c);
fb.put_pixel(cx + y, cy + x, c);
fb.put_pixel(cx - y, cy + x, c);
fb.put_pixel(cx + y, cy - x, c);
fb.put_pixel(cx - y, cy - x, c);
if (d < 0) {
d += 2 * x + 3;
} else {
d += 2 * (x - y) + 5;
y--;
}
x++;
}
}
// Bresenham line drawing
inline void draw_line(Framebuffer& fb, int x0, int y0, int x1, int y1, Color c) {
int dx = gui_abs(x1 - x0);
int dy = gui_abs(y1 - y0);
int sx = x0 < x1 ? 1 : -1;
int sy = y0 < y1 ? 1 : -1;
int err = dx - dy;
for (;;) {
fb.put_pixel(x0, y0, c);
if (x0 == x1 && y0 == y1) break;
int e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
}
}
// Drop shadow: offset darker rectangles below/right
inline void draw_shadow(Framebuffer& fb, int x, int y, int w, int h, int offset, Color shadow_color) {
// Bottom shadow strip
fb.fill_rect_alpha(x + offset, y + h, w, offset, shadow_color);
// Right shadow strip
fb.fill_rect_alpha(x + w, y + offset, offset, h, shadow_color);
// Corner
fb.fill_rect_alpha(x + w, y + h, offset, offset, shadow_color);
}
// 16x16 mouse cursor bitmaps
// Outline (black, where design has '1')
static constexpr uint16_t cursor_outline[16] = {
0x8000, // 1000000000000000
0xC000, // 1100000000000000
0xA000, // 1010000000000000
0x9000, // 1001000000000000
0x8800, // 1000100000000000
0x8400, // 1000010000000000
0x8200, // 1000001000000000
0x8100, // 1000000100000000
0x8080, // 1000000010000000
0x8040, // 1000000001000000
0x8780, // 1000011110000000 (changed: row 10 = 1 2 2 2 2 2 1 1 1 1)
0x9200, // 1001001000000000 (row 11 = 1 2 2 1 2 2 1)
0xA900, // 1010100100000000 (row 12 = 1 2 1 0 1 2 2 1)
0xC900, // 1100100100000000 (row 13 = 1 1 0 0 1 2 2 1)
0x8480, // 1000010010000000 (row 14 = 1 0 0 0 0 1 2 2 1)
0x0700, // 0000011100000000 (row 15 = 0 0 0 0 0 1 1 1)
};
// Fill (white, where design has '2')
static constexpr uint16_t cursor_fill[16] = {
0x0000, // row 0: no fill
0x0000, // row 1: no fill
0x4000, // row 2: 0100000000000000
0x6000, // row 3: 0110000000000000
0x7000, // row 4: 0111000000000000
0x7800, // row 5: 0111100000000000
0x7C00, // row 6: 0111110000000000
0x7E00, // row 7: 0111111000000000
0x7F00, // row 8: 0111111100000000
0x7F80, // row 9: 0111111110000000
0x7800, // row 10: 0111100000000000 (fill only in positions 1-5)
0x6C00, // row 11: 0110110000000000 (fill at positions 1,2 and 4,5)
0x4600, // row 12: 0100011000000000 (fill at position 1 and 5,6)
0x0600, // row 13: 0000011000000000 (fill at positions 5,6)
0x0300, // row 14: 0000001100000000 (fill at positions 6,7)
0x0000, // row 15: no fill
};
// Draw the mouse cursor at (x, y)
inline void draw_cursor(Framebuffer& fb, int x, int y) {
Color black = colors::BLACK;
Color white = colors::WHITE;
for (int row = 0; row < 16; row++) {
uint16_t outline = cursor_outline[row];
uint16_t fill = cursor_fill[row];
for (int col = 0; col < 16; col++) {
uint16_t mask = (uint16_t)(0x8000 >> col);
if (outline & mask) {
fb.put_pixel(x + col, y + row, black);
} else if (fill & mask) {
fb.put_pixel(x + col, y + row, white);
}
}
}
}
} // namespace gui
+63
View File
@@ -0,0 +1,63 @@
/*
* font.hpp
* ZenithOS 8x16 VGA bitmap font rendering
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include "gui/gui.hpp"
#include "gui/framebuffer.hpp"
namespace gui {
static constexpr int FONT_WIDTH = 8;
static constexpr int FONT_HEIGHT = 16;
// Defined in font_data.cpp
extern const uint8_t font_data[256 * 16];
inline void draw_char(Framebuffer& fb, int x, int y, char c, Color fg) {
const uint8_t* glyph = &font_data[(unsigned char)c * FONT_HEIGHT];
for (int row = 0; row < FONT_HEIGHT; row++) {
uint8_t bits = glyph[row];
for (int col = 0; col < FONT_WIDTH; col++) {
if (bits & (0x80 >> col)) {
fb.put_pixel(x + col, y + row, fg);
}
}
}
}
inline void draw_char_bg(Framebuffer& fb, int x, int y, char c, Color fg, Color bg) {
const uint8_t* glyph = &font_data[(unsigned char)c * FONT_HEIGHT];
for (int row = 0; row < FONT_HEIGHT; row++) {
uint8_t bits = glyph[row];
for (int col = 0; col < FONT_WIDTH; col++) {
if (bits & (0x80 >> col)) {
fb.put_pixel(x + col, y + row, fg);
} else {
fb.put_pixel(x + col, y + row, bg);
}
}
}
}
inline void draw_text(Framebuffer& fb, int x, int y, const char* text, Color fg) {
for (int i = 0; text[i]; i++) {
draw_char(fb, x + i * FONT_WIDTH, y, text[i], fg);
}
}
inline void draw_text_bg(Framebuffer& fb, int x, int y, const char* text, Color fg, Color bg) {
for (int i = 0; text[i]; i++) {
draw_char_bg(fb, x + i * FONT_WIDTH, y, text[i], fg, bg);
}
}
inline int text_width(const char* text) {
int len = 0;
while (text[len]) len++;
return len * FONT_WIDTH;
}
} // namespace gui
+196
View File
@@ -0,0 +1,196 @@
/*
* framebuffer.hpp
* ZenithOS double-buffered framebuffer abstraction
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <cstdint>
#include <zenith/syscall.h>
#include "gui/gui.hpp"
namespace gui {
class Framebuffer {
uint32_t* hw_fb;
uint32_t* back_buf;
int fb_width;
int fb_height;
int fb_pitch; // in bytes
public:
Framebuffer() : hw_fb(nullptr), back_buf(nullptr), fb_width(0), fb_height(0), fb_pitch(0) {
Zenith::FbInfo info;
zenith::fb_info(&info);
fb_width = (int)info.width;
fb_height = (int)info.height;
fb_pitch = (int)info.pitch;
hw_fb = (uint32_t*)zenith::fb_map();
back_buf = (uint32_t*)zenith::alloc((uint64_t)fb_height * fb_pitch);
}
int width() const { return fb_width; }
int height() const { return fb_height; }
int pitch() const { return fb_pitch; }
uint32_t* buffer() { return back_buf; }
inline void put_pixel(int x, int y, Color c) {
if (x < 0 || x >= fb_width || y < 0 || y >= fb_height) return;
uint32_t* row = (uint32_t*)((uint8_t*)back_buf + y * fb_pitch);
row[x] = c.to_pixel();
}
inline void put_pixel_alpha(int x, int y, Color c) {
if (x < 0 || x >= fb_width || y < 0 || y >= fb_height) return;
if (c.a == 0) return;
if (c.a == 255) {
put_pixel(x, y, c);
return;
}
uint32_t* row = (uint32_t*)((uint8_t*)back_buf + y * fb_pitch);
uint32_t dst = row[x];
uint8_t dr = (dst >> 16) & 0xFF;
uint8_t dg = (dst >> 8) & 0xFF;
uint8_t db = dst & 0xFF;
uint32_t a = c.a;
uint32_t inv_a = 255 - a;
// Fast alpha blend: out = (src * alpha + dst * (255 - alpha) + 128) / 255
// Approximation: (x + 1 + (x >> 8)) >> 8 for division by 255
uint32_t rr = a * c.r + inv_a * dr;
uint32_t gg = a * c.g + inv_a * dg;
uint32_t bb = a * c.b + inv_a * db;
rr = (rr + 1 + (rr >> 8)) >> 8;
gg = (gg + 1 + (gg >> 8)) >> 8;
bb = (bb + 1 + (bb >> 8)) >> 8;
row[x] = (0xFF000000) | (rr << 16) | (gg << 8) | bb;
}
inline void fill_rect(int x, int y, int w, int h, Color c) {
// Clip to screen bounds
int x0 = x < 0 ? 0 : x;
int y0 = y < 0 ? 0 : y;
int x1 = (x + w) > fb_width ? fb_width : (x + w);
int y1 = (y + h) > fb_height ? fb_height : (y + h);
if (x0 >= x1 || y0 >= y1) return;
uint32_t pixel = c.to_pixel();
int clipped_w = x1 - x0;
for (int row = y0; row < y1; row++) {
uint32_t* dst = (uint32_t*)((uint8_t*)back_buf + row * fb_pitch) + x0;
for (int col = 0; col < clipped_w; col++) {
dst[col] = pixel;
}
}
}
inline void fill_rect_alpha(int x, int y, int w, int h, Color c) {
if (c.a == 0) return;
if (c.a == 255) {
fill_rect(x, y, w, h, c);
return;
}
int x0 = x < 0 ? 0 : x;
int y0 = y < 0 ? 0 : y;
int x1 = (x + w) > fb_width ? fb_width : (x + w);
int y1 = (y + h) > fb_height ? fb_height : (y + h);
if (x0 >= x1 || y0 >= y1) return;
uint32_t a = c.a;
uint32_t inv_a = 255 - a;
uint32_t src_r = a * c.r;
uint32_t src_g = a * c.g;
uint32_t src_b = a * c.b;
for (int row = y0; row < y1; row++) {
uint32_t* dst = (uint32_t*)((uint8_t*)back_buf + row * fb_pitch) + x0;
for (int col = 0; col < x1 - x0; col++) {
uint32_t d = dst[col];
uint32_t dr = (d >> 16) & 0xFF;
uint32_t dg = (d >> 8) & 0xFF;
uint32_t db = d & 0xFF;
uint32_t rr = src_r + inv_a * dr;
uint32_t gg = src_g + inv_a * dg;
uint32_t bb = src_b + inv_a * db;
rr = (rr + 1 + (rr >> 8)) >> 8;
gg = (gg + 1 + (gg >> 8)) >> 8;
bb = (bb + 1 + (bb >> 8)) >> 8;
dst[col] = (0xFF000000) | (rr << 16) | (gg << 8) | bb;
}
}
}
inline void blit(int x, int y, int w, int h, const uint32_t* pixels) {
for (int row = 0; row < h; row++) {
int dy = y + row;
if (dy < 0 || dy >= fb_height) continue;
for (int col = 0; col < w; col++) {
int dx = x + col;
if (dx < 0 || dx >= fb_width) continue;
uint32_t* dst_row = (uint32_t*)((uint8_t*)back_buf + dy * fb_pitch);
dst_row[dx] = pixels[row * w + col];
}
}
}
inline void blit_alpha(int x, int y, int w, int h, const uint32_t* pixels) {
for (int row = 0; row < h; row++) {
int dy = y + row;
if (dy < 0 || dy >= fb_height) continue;
for (int col = 0; col < w; col++) {
int dx = x + col;
if (dx < 0 || dx >= fb_width) continue;
uint32_t src = pixels[row * w + col];
uint8_t sa = (src >> 24) & 0xFF;
if (sa == 0) continue;
uint8_t sr = (src >> 16) & 0xFF;
uint8_t sg = (src >> 8) & 0xFF;
uint8_t sb = src & 0xFF;
if (sa == 255) {
uint32_t* dst_row = (uint32_t*)((uint8_t*)back_buf + dy * fb_pitch);
dst_row[dx] = src;
continue;
}
Color sc = {sr, sg, sb, sa};
put_pixel_alpha(dx, dy, sc);
}
}
}
inline void clear(Color c) {
fill_rect(0, 0, fb_width, fb_height, c);
}
inline void flip() {
// Copy back buffer to hardware framebuffer, row by row (pitch may differ)
int row_pixels = fb_width;
for (int y = 0; y < fb_height; y++) {
uint32_t* src = (uint32_t*)((uint8_t*)back_buf + y * fb_pitch);
uint32_t* dst = (uint32_t*)((uint8_t*)hw_fb + y * fb_pitch);
for (int x = 0; x < row_pixels; x++) {
dst[x] = src[x];
}
}
}
};
} // namespace gui
+92
View File
@@ -0,0 +1,92 @@
/*
* gui.hpp
* ZenithOS core GUI types and utilities
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <cstdint>
namespace gui {
// 16.16 fixed-point type
using fixed_t = int32_t;
static constexpr int FIXED_SHIFT = 16;
inline fixed_t int_to_fixed(int v) { return v << FIXED_SHIFT; }
inline int fixed_to_int(fixed_t v) { return v >> FIXED_SHIFT; }
inline fixed_t fixed_mul(fixed_t a, fixed_t b) { return (int32_t)(((int64_t)a * b) >> FIXED_SHIFT); }
inline fixed_t fixed_div(fixed_t a, fixed_t b) { return (int32_t)(((int64_t)a << FIXED_SHIFT) / b); }
inline fixed_t fixed_from_parts(int whole, int frac_num, int frac_den) {
return int_to_fixed(whole) + (int32_t)(((int64_t)frac_num << FIXED_SHIFT) / frac_den);
}
struct Color {
uint8_t r, g, b, a;
static Color from_rgb(uint8_t r, uint8_t g, uint8_t b) { return {r, g, b, 255}; }
static Color from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { return {r, g, b, a}; }
static Color from_hex(uint32_t hex) {
return {(uint8_t)((hex >> 16) & 0xFF), (uint8_t)((hex >> 8) & 0xFF), (uint8_t)(hex & 0xFF), 255};
}
uint32_t to_pixel() const { return ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; }
};
// Named colors for the desktop theme
namespace colors {
static constexpr Color DESKTOP_BG = {0xE0, 0xE0, 0xE0, 0xFF};
static constexpr Color PANEL_BG = {0x2B, 0x3E, 0x50, 0xFF};
static constexpr Color TITLEBAR_BG = {0xF5, 0xF5, 0xF5, 0xFF};
static constexpr Color WINDOW_BG = {0xFF, 0xFF, 0xFF, 0xFF};
static constexpr Color BORDER = {0xCC, 0xCC, 0xCC, 0xFF};
static constexpr Color TEXT_COLOR = {0x33, 0x33, 0x33, 0xFF};
static constexpr Color PANEL_TEXT = {0xFF, 0xFF, 0xFF, 0xFF};
static constexpr Color ACCENT = {0x36, 0x7B, 0xF0, 0xFF};
static constexpr Color CLOSE_BTN = {0xFF, 0x5F, 0x57, 0xFF};
static constexpr Color MAX_BTN = {0x28, 0xCA, 0x42, 0xFF};
static constexpr Color MIN_BTN = {0xFF, 0xBD, 0x2E, 0xFF};
static constexpr Color SHADOW = {0x00, 0x00, 0x00, 0x40};
static constexpr Color TRANSPARENT = {0x00, 0x00, 0x00, 0x00};
static constexpr Color BLACK = {0x00, 0x00, 0x00, 0xFF};
static constexpr Color WHITE = {0xFF, 0xFF, 0xFF, 0xFF};
static constexpr Color ICON_COLOR = {0x5C, 0x61, 0x6C, 0xFF};
static constexpr Color SCROLLBAR_BG = {0xF0, 0xF0, 0xF0, 0xFF};
static constexpr Color SCROLLBAR_FG = {0xC0, 0xC0, 0xC0, 0xFF};
static constexpr Color MENU_BG = {0xFF, 0xFF, 0xFF, 0xFF};
static constexpr Color MENU_HOVER = {0xE8, 0xF0, 0xFE, 0xFF};
static constexpr Color TERM_BG = {0x2D, 0x2D, 0x2D, 0xFF};
static constexpr Color TERM_FG = {0xCC, 0xCC, 0xCC, 0xFF};
}
struct Point {
int x, y;
};
struct Rect {
int x, y, w, h;
bool contains(int px, int py) const {
return px >= x && px < x + w && py >= y && py < y + h;
}
Rect intersect(const Rect& other) const {
int rx = x > other.x ? x : other.x;
int ry = y > other.y ? y : other.y;
int rx2 = (x + w) < (other.x + other.w) ? (x + w) : (other.x + other.w);
int ry2 = (y + h) < (other.y + other.h) ? (y + h) : (other.y + other.h);
if (rx2 <= rx || ry2 <= ry) return {0, 0, 0, 0};
return {rx, ry, rx2 - rx, ry2 - ry};
}
bool empty() const { return w <= 0 || h <= 0; }
};
// Simple inline utility functions
inline int gui_min(int a, int b) { return a < b ? a : b; }
inline int gui_max(int a, int b) { return a > b ? a : b; }
inline int gui_abs(int a) { return a < 0 ? -a : a; }
inline int gui_clamp(int v, int lo, int hi) { return v < lo ? lo : (v > hi ? hi : v); }
} // namespace gui
+972
View File
@@ -0,0 +1,972 @@
/*
* svg.hpp
* ZenithOS SVG icon parser and scanline rasterizer
* Handles the Flat-Remix symbolic icon subset (path, circle, rect)
* All math uses 16.16 fixed-point -- NO floating point.
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include "gui/gui.hpp"
#include <zenith/syscall.h>
namespace gui {
// ---------------------------------------------------------------------------
// SVG icon result
// ---------------------------------------------------------------------------
struct SvgIcon {
uint32_t* pixels; // ARGB pixel data (heap-allocated)
int width;
int height;
};
// ---------------------------------------------------------------------------
// Edge used by the scanline rasterizer
// ---------------------------------------------------------------------------
struct SvgEdge {
fixed_t x0, y0, x1, y1;
};
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
static constexpr int SVG_MAX_EDGES = 4096;
static constexpr int SVG_MAX_PATH_LEN = 4096;
static constexpr int SVG_MAX_FILE_SIZE = 32768;
static constexpr int SVG_BEZIER_STEPS = 8;
// ---------------------------------------------------------------------------
// Fixed-point number parser (NO floating point)
// Parses strings like "3.25", "-0.5", ".1115", "16"
// Returns the number of characters consumed.
// ---------------------------------------------------------------------------
inline int svg_parse_fixed(const char* s, fixed_t* out) {
const char* p = s;
bool neg = false;
if (*p == '-') { neg = true; ++p; }
else if (*p == '+') { ++p; }
// Integer part
int32_t integer = 0;
while (*p >= '0' && *p <= '9') {
integer = integer * 10 + (*p - '0');
++p;
}
// Fractional part
int32_t frac = 0;
int32_t frac_div = 1;
if (*p == '.') {
++p;
while (*p >= '0' && *p <= '9') {
if (frac_div < 100000) { // prevent overflow
frac = frac * 10 + (*p - '0');
frac_div *= 10;
}
++p;
}
}
fixed_t val = int_to_fixed(integer);
if (frac_div > 1) {
val += (int32_t)(((int64_t)frac << 16) / frac_div);
}
if (neg) val = -val;
*out = val;
return (int)(p - s);
}
// ---------------------------------------------------------------------------
// String helpers (no stdlib)
// ---------------------------------------------------------------------------
inline bool svg_char_is_ws(char c) {
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}
inline bool svg_char_is_sep(char c) {
return svg_char_is_ws(c) || c == ',';
}
inline bool svg_char_is_num_start(char c) {
return (c >= '0' && c <= '9') || c == '-' || c == '+' || c == '.';
}
inline bool svg_char_is_cmd(char c) {
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}
inline int svg_strlen(const char* s) {
int n = 0;
while (s[n]) ++n;
return n;
}
inline bool svg_strncmp(const char* a, const char* b, int n) {
for (int i = 0; i < n; ++i)
if (a[i] != b[i]) return false;
return true;
}
inline void svg_memset(void* dst, uint8_t val, int n) {
auto* d = (uint8_t*)dst;
for (int i = 0; i < n; ++i) d[i] = val;
}
inline void svg_memcpy(void* dst, const void* src, int n) {
auto* d = (uint8_t*)dst;
auto* s = (const uint8_t*)src;
for (int i = 0; i < n; ++i) d[i] = s[i];
}
// ---------------------------------------------------------------------------
// Mini XML attribute extraction
// ---------------------------------------------------------------------------
// Find the next occurrence of needle in haystack (haystack has length hLen).
// Returns pointer to start of match, or nullptr.
inline const char* svg_strstr(const char* haystack, int hLen, const char* needle) {
int nLen = svg_strlen(needle);
if (nLen == 0 || nLen > hLen) return nullptr;
for (int i = 0; i <= hLen - nLen; ++i) {
if (svg_strncmp(haystack + i, needle, nLen))
return haystack + i;
}
return nullptr;
}
// Check if a character can be part of an XML attribute name
inline bool svg_char_is_attrname(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || c == '-' || c == '_' || c == ':';
}
// Extract the value of an XML attribute: attr="value"
// The attr string should include a leading space (e.g., " cx") to distinguish
// from substrings of other attribute names.
// Writes into buf (up to maxLen-1 chars), returns length or -1 if not found.
inline int svg_get_attr(const char* tag, int tagLen, const char* attr, char* buf, int maxLen) {
int attrLen = svg_strlen(attr);
const char* search_start = tag;
int search_len = tagLen;
while (search_len > 0) {
const char* p = svg_strstr(search_start, search_len, attr);
if (!p) return -1;
// Ensure this is the exact attribute name, not a prefix of another
// (e.g., " r" should not match " rx" or " ry")
const char* after_name = p + attrLen;
if (after_name < tag + tagLen && svg_char_is_attrname(*after_name)) {
// This is a prefix of a longer attribute name, skip and search again
search_start = after_name;
search_len = tagLen - (int)(search_start - tag);
continue;
}
p = after_name;
// skip whitespace around '='
while (p < tag + tagLen && svg_char_is_ws(*p)) ++p;
if (p >= tag + tagLen || *p != '=') return -1;
++p;
while (p < tag + tagLen && svg_char_is_ws(*p)) ++p;
if (p >= tag + tagLen) return -1;
char quote = *p;
if (quote != '"' && quote != '\'') return -1;
++p;
int len = 0;
while (p < tag + tagLen && *p != quote && len < maxLen - 1) {
buf[len++] = *p++;
}
buf[len] = '\0';
return len;
}
return -1;
}
// Parse an integer from a string (no sign, simple decimal).
inline int svg_parse_int(const char* s) {
int v = 0;
while (*s >= '0' && *s <= '9') {
v = v * 10 + (*s - '0');
++s;
}
return v;
}
// Parse a hex color like "#5c616c" into a Color.
inline Color svg_parse_hex_color(const char* s) {
if (*s == '#') ++s;
auto hexval = [](char c) -> uint8_t {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return 10 + (c - 'a');
if (c >= 'A' && c <= 'F') return 10 + (c - 'A');
return 0;
};
uint8_t r = (hexval(s[0]) << 4) | hexval(s[1]);
uint8_t g = (hexval(s[2]) << 4) | hexval(s[3]);
uint8_t b = (hexval(s[4]) << 4) | hexval(s[5]);
return Color::from_rgb(r, g, b);
}
// ---------------------------------------------------------------------------
// Edge list builder
// ---------------------------------------------------------------------------
struct SvgEdgeList {
SvgEdge* edges;
int count;
int capacity;
void init(int cap) {
edges = (SvgEdge*)zenith::alloc(cap * sizeof(SvgEdge));
count = 0;
capacity = cap;
}
void add(fixed_t x0, fixed_t y0, fixed_t x1, fixed_t y1) {
if (count >= capacity) return;
// skip horizontal edges (they don't contribute to scanline crossings)
if (y0 == y1) return;
edges[count++] = {x0, y0, x1, y1};
}
};
// ---------------------------------------------------------------------------
// Bezier flattening (fixed-point)
// ---------------------------------------------------------------------------
// Cubic bezier: add line segments approximating B(t) for t in [0,1]
inline void svg_flatten_cubic(SvgEdgeList& el,
fixed_t x0, fixed_t y0,
fixed_t x1, fixed_t y1,
fixed_t x2, fixed_t y2,
fixed_t x3, fixed_t y3) {
constexpr int N = SVG_BEZIER_STEPS;
fixed_t px = x0, py = y0;
for (int i = 1; i <= N; ++i) {
// t = i/N in 16.16: (i << 16) / N
fixed_t t = (int32_t)(((int64_t)i << 16) / N);
fixed_t omt = int_to_fixed(1) - t; // 1 - t
// (1-t)^2 and t^2
fixed_t omt2 = fixed_mul(omt, omt);
fixed_t t2 = fixed_mul(t, t);
// (1-t)^3 and t^3
fixed_t omt3 = fixed_mul(omt2, omt);
fixed_t t3 = fixed_mul(t2, t);
// 3*(1-t)^2*t and 3*(1-t)*t^2
fixed_t c1 = fixed_mul(omt2, t) * 3;
fixed_t c2 = fixed_mul(omt, t2) * 3;
fixed_t nx = fixed_mul(omt3, x0) + fixed_mul(c1, x1) + fixed_mul(c2, x2) + fixed_mul(t3, x3);
fixed_t ny = fixed_mul(omt3, y0) + fixed_mul(c1, y1) + fixed_mul(c2, y2) + fixed_mul(t3, y3);
el.add(px, py, nx, ny);
px = nx;
py = ny;
}
}
// Quadratic bezier: add line segments approximating B(t) for t in [0,1]
inline void svg_flatten_quad(SvgEdgeList& el,
fixed_t x0, fixed_t y0,
fixed_t x1, fixed_t y1,
fixed_t x2, fixed_t y2) {
constexpr int N = SVG_BEZIER_STEPS;
fixed_t px = x0, py = y0;
for (int i = 1; i <= N; ++i) {
fixed_t t = (int32_t)(((int64_t)i << 16) / N);
fixed_t omt = int_to_fixed(1) - t;
// (1-t)^2*P0 + 2*(1-t)*t*P1 + t^2*P2
fixed_t omt2 = fixed_mul(omt, omt);
fixed_t t2 = fixed_mul(t, t);
fixed_t c1 = fixed_mul(omt, t) * 2;
fixed_t nx = fixed_mul(omt2, x0) + fixed_mul(c1, x1) + fixed_mul(t2, x2);
fixed_t ny = fixed_mul(omt2, y0) + fixed_mul(c1, y1) + fixed_mul(t2, y2);
el.add(px, py, nx, ny);
px = nx;
py = ny;
}
}
// ---------------------------------------------------------------------------
// Circle to edges: approximate a circle as N line segments
// ---------------------------------------------------------------------------
inline void svg_circle_edges(SvgEdgeList& el, fixed_t cx, fixed_t cy, fixed_t r) {
// Approximate circle with 16 segments using a precomputed sin/cos table
// for angles 0, 22.5, 45, ... 337.5 degrees.
// sin/cos in 16.16 fixed-point for 16 evenly-spaced angles:
static const fixed_t cos16[16] = {
65536, 60547, 46341, 25080, 0, -25080, -46341, -60547,
-65536, -60547, -46341, -25080, 0, 25080, 46341, 60547
};
static const fixed_t sin16[16] = {
0, 25080, 46341, 60547, 65536, 60547, 46341, 25080,
0, -25080, -46341, -60547, -65536, -60547, -46341, -25080
};
fixed_t px = cx + fixed_mul(r, cos16[0]);
fixed_t py = cy + fixed_mul(r, sin16[0]);
for (int i = 1; i <= 16; ++i) {
int idx = i & 15;
fixed_t nx = cx + fixed_mul(r, cos16[idx]);
fixed_t ny = cy + fixed_mul(r, sin16[idx]);
el.add(px, py, nx, ny);
px = nx;
py = ny;
}
}
// ---------------------------------------------------------------------------
// Rounded rect to edges
// ---------------------------------------------------------------------------
inline void svg_rect_edges(SvgEdgeList& el, fixed_t x, fixed_t y, fixed_t w, fixed_t h,
fixed_t rx, fixed_t ry) {
if (rx <= 0 && ry <= 0) {
// Simple rectangle: 4 edges
fixed_t x2 = x + w;
fixed_t y2 = y + h;
el.add(x, y, x2, y); // top
el.add(x2, y, x2, y2); // right
el.add(x2, y2, x, y2); // bottom
el.add(x, y2, x, y); // left
return;
}
// Clamp radii
fixed_t half_w = w >> 1;
fixed_t half_h = h >> 1;
if (rx > half_w) rx = half_w;
if (ry > half_h) ry = half_h;
// Quarter-circle corner with 4 segments per corner.
// cos/sin for 0, 22.5, 45, 67.5, 90 degrees (5 points, 4 segments):
static const fixed_t qcos[5] = { 65536, 60547, 46341, 25080, 0 };
static const fixed_t qsin[5] = { 0, 25080, 46341, 60547, 65536 };
// Corners: top-right, bottom-right, bottom-left, top-left
// Each corner has a center and a quadrant direction for cos/sin application
struct Corner { fixed_t cx, cy; int sx, sy; };
Corner corners[4] = {
{ x + w - rx, y + ry, 1, -1 }, // top-right
{ x + w - rx, y + h - ry, 1, 1 }, // bottom-right
{ x + rx, y + h - ry, -1, 1 }, // bottom-left
{ x + rx, y + ry, -1, -1 }, // top-left
};
for (int c = 0; c < 4; ++c) {
Corner& cn = corners[c];
fixed_t px = cn.cx + fixed_mul(rx, qcos[0]) * cn.sx;
fixed_t py = cn.cy + fixed_mul(ry, qsin[0]) * cn.sy;
for (int i = 1; i <= 4; ++i) {
fixed_t nx = cn.cx + fixed_mul(rx, qcos[i]) * cn.sx;
fixed_t ny = cn.cy + fixed_mul(ry, qsin[i]) * cn.sy;
el.add(px, py, nx, ny);
px = nx;
py = ny;
}
}
// Straight edges between corners
// Top edge: top-left corner end -> top-right corner start
el.add(x + rx, y, x + w - rx, y);
// Right edge: top-right corner end -> bottom-right corner start
el.add(x + w, y + ry, x + w, y + h - ry);
// Bottom edge: bottom-right corner end -> bottom-left corner start
el.add(x + w - rx, y + h, x + rx, y + h);
// Left edge: bottom-left corner end -> top-left corner start
el.add(x, y + h - ry, x, y + ry);
}
// ---------------------------------------------------------------------------
// Path command parser: tokenize the 'd' attribute
// ---------------------------------------------------------------------------
struct SvgPathParser {
const char* data;
int len;
int pos;
void init(const char* d, int l) { data = d; len = l; pos = 0; }
void skip_separators() {
while (pos < len && svg_char_is_sep(data[pos])) ++pos;
}
bool has_more() const { return pos < len; }
// Peek at what's next: command letter, number, or end
bool next_is_number() {
skip_separators();
if (pos >= len) return false;
return svg_char_is_num_start(data[pos]);
}
char read_command() {
skip_separators();
if (pos >= len) return '\0';
if (svg_char_is_cmd(data[pos]) && data[pos] != 'e' && data[pos] != 'E') {
return data[pos++];
}
return '\0';
}
fixed_t read_number() {
skip_separators();
if (pos >= len) return 0;
fixed_t val = 0;
int consumed = svg_parse_fixed(data + pos, &val);
pos += consumed;
return val;
}
};
// ---------------------------------------------------------------------------
// Process an SVG path 'd' attribute into edges
// ---------------------------------------------------------------------------
inline void svg_path_to_edges(SvgEdgeList& el, const char* d, int dLen,
fixed_t scale_x, fixed_t scale_y,
fixed_t off_x, fixed_t off_y) {
SvgPathParser pp;
pp.init(d, dLen);
fixed_t cur_x = 0, cur_y = 0; // current point
fixed_t start_x = 0, start_y = 0; // subpath start
fixed_t last_cx = 0, last_cy = 0; // last control point (for S/T)
char last_cmd = '\0';
auto scale_pt = [&](fixed_t x, fixed_t y, fixed_t* ox, fixed_t* oy) {
*ox = fixed_mul(x - off_x, scale_x);
*oy = fixed_mul(y - off_y, scale_y);
};
while (pp.has_more()) {
char cmd = '\0';
// Try reading a command letter
pp.skip_separators();
if (pp.pos < pp.len && svg_char_is_cmd(pp.data[pp.pos]) &&
pp.data[pp.pos] != 'e' && pp.data[pp.pos] != 'E') {
cmd = pp.data[pp.pos++];
} else if (pp.next_is_number()) {
// Implicit repeat of last command
// After M, implicit repeat is L; after m, implicit repeat is l
if (last_cmd == 'M') cmd = 'L';
else if (last_cmd == 'm') cmd = 'l';
else cmd = last_cmd;
} else {
// Skip unknown character
if (pp.pos < pp.len) pp.pos++;
continue;
}
if (cmd == '\0') break;
switch (cmd) {
case 'M': {
fixed_t x = pp.read_number();
fixed_t y = pp.read_number();
cur_x = x; cur_y = y;
start_x = x; start_y = y;
last_cmd = 'M';
break;
}
case 'm': {
fixed_t dx = pp.read_number();
fixed_t dy = pp.read_number();
cur_x += dx; cur_y += dy;
start_x = cur_x; start_y = cur_y;
last_cmd = 'm';
break;
}
case 'L': {
fixed_t x = pp.read_number();
fixed_t y = pp.read_number();
fixed_t sx0, sy0, sx1, sy1;
scale_pt(cur_x, cur_y, &sx0, &sy0);
scale_pt(x, y, &sx1, &sy1);
el.add(sx0, sy0, sx1, sy1);
cur_x = x; cur_y = y;
last_cmd = 'L';
break;
}
case 'l': {
fixed_t dx = pp.read_number();
fixed_t dy = pp.read_number();
fixed_t nx = cur_x + dx, ny = cur_y + dy;
fixed_t sx0, sy0, sx1, sy1;
scale_pt(cur_x, cur_y, &sx0, &sy0);
scale_pt(nx, ny, &sx1, &sy1);
el.add(sx0, sy0, sx1, sy1);
cur_x = nx; cur_y = ny;
last_cmd = 'l';
break;
}
case 'H': {
fixed_t x = pp.read_number();
fixed_t sx0, sy0, sx1, sy1;
scale_pt(cur_x, cur_y, &sx0, &sy0);
scale_pt(x, cur_y, &sx1, &sy1);
el.add(sx0, sy0, sx1, sy1);
cur_x = x;
last_cmd = 'H';
break;
}
case 'h': {
fixed_t dx = pp.read_number();
fixed_t nx = cur_x + dx;
fixed_t sx0, sy0, sx1, sy1;
scale_pt(cur_x, cur_y, &sx0, &sy0);
scale_pt(nx, cur_y, &sx1, &sy1);
el.add(sx0, sy0, sx1, sy1);
cur_x = nx;
last_cmd = 'h';
break;
}
case 'V': {
fixed_t y = pp.read_number();
fixed_t sx0, sy0, sx1, sy1;
scale_pt(cur_x, cur_y, &sx0, &sy0);
scale_pt(cur_x, y, &sx1, &sy1);
el.add(sx0, sy0, sx1, sy1);
cur_y = y;
last_cmd = 'V';
break;
}
case 'v': {
fixed_t dy = pp.read_number();
fixed_t ny = cur_y + dy;
fixed_t sx0, sy0, sx1, sy1;
scale_pt(cur_x, cur_y, &sx0, &sy0);
scale_pt(cur_x, ny, &sx1, &sy1);
el.add(sx0, sy0, sx1, sy1);
cur_y = ny;
last_cmd = 'v';
break;
}
case 'C': {
fixed_t x1 = pp.read_number(), y1 = pp.read_number();
fixed_t x2 = pp.read_number(), y2 = pp.read_number();
fixed_t x3 = pp.read_number(), y3 = pp.read_number();
fixed_t sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3;
scale_pt(cur_x, cur_y, &sx0, &sy0);
scale_pt(x1, y1, &sx1, &sy1);
scale_pt(x2, y2, &sx2, &sy2);
scale_pt(x3, y3, &sx3, &sy3);
svg_flatten_cubic(el, sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3);
last_cx = x2; last_cy = y2;
cur_x = x3; cur_y = y3;
last_cmd = 'C';
break;
}
case 'c': {
fixed_t dx1 = pp.read_number(), dy1 = pp.read_number();
fixed_t dx2 = pp.read_number(), dy2 = pp.read_number();
fixed_t dx3 = pp.read_number(), dy3 = pp.read_number();
fixed_t x1 = cur_x + dx1, y1 = cur_y + dy1;
fixed_t x2 = cur_x + dx2, y2 = cur_y + dy2;
fixed_t x3 = cur_x + dx3, y3 = cur_y + dy3;
fixed_t sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3;
scale_pt(cur_x, cur_y, &sx0, &sy0);
scale_pt(x1, y1, &sx1, &sy1);
scale_pt(x2, y2, &sx2, &sy2);
scale_pt(x3, y3, &sx3, &sy3);
svg_flatten_cubic(el, sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3);
last_cx = x2; last_cy = y2;
cur_x = x3; cur_y = y3;
last_cmd = 'c';
break;
}
case 'S': {
// Smooth cubic: reflect last control point
fixed_t rcx = cur_x * 2 - last_cx;
fixed_t rcy = cur_y * 2 - last_cy;
if (last_cmd != 'C' && last_cmd != 'c' && last_cmd != 'S' && last_cmd != 's') {
rcx = cur_x; rcy = cur_y;
}
fixed_t x2 = pp.read_number(), y2 = pp.read_number();
fixed_t x3 = pp.read_number(), y3 = pp.read_number();
fixed_t sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3;
scale_pt(cur_x, cur_y, &sx0, &sy0);
scale_pt(rcx, rcy, &sx1, &sy1);
scale_pt(x2, y2, &sx2, &sy2);
scale_pt(x3, y3, &sx3, &sy3);
svg_flatten_cubic(el, sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3);
last_cx = x2; last_cy = y2;
cur_x = x3; cur_y = y3;
last_cmd = 'S';
break;
}
case 's': {
fixed_t rcx = cur_x * 2 - last_cx;
fixed_t rcy = cur_y * 2 - last_cy;
if (last_cmd != 'C' && last_cmd != 'c' && last_cmd != 'S' && last_cmd != 's') {
rcx = cur_x; rcy = cur_y;
}
fixed_t dx2 = pp.read_number(), dy2 = pp.read_number();
fixed_t dx3 = pp.read_number(), dy3 = pp.read_number();
fixed_t x2 = cur_x + dx2, y2 = cur_y + dy2;
fixed_t x3 = cur_x + dx3, y3 = cur_y + dy3;
fixed_t sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3;
scale_pt(cur_x, cur_y, &sx0, &sy0);
scale_pt(rcx, rcy, &sx1, &sy1);
scale_pt(x2, y2, &sx2, &sy2);
scale_pt(x3, y3, &sx3, &sy3);
svg_flatten_cubic(el, sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3);
last_cx = x2; last_cy = y2;
cur_x = x3; cur_y = y3;
last_cmd = 's';
break;
}
case 'Q': {
fixed_t x1 = pp.read_number(), y1 = pp.read_number();
fixed_t x2 = pp.read_number(), y2 = pp.read_number();
fixed_t sx0, sy0, sx1, sy1, sx2, sy2;
scale_pt(cur_x, cur_y, &sx0, &sy0);
scale_pt(x1, y1, &sx1, &sy1);
scale_pt(x2, y2, &sx2, &sy2);
svg_flatten_quad(el, sx0, sy0, sx1, sy1, sx2, sy2);
last_cx = x1; last_cy = y1;
cur_x = x2; cur_y = y2;
last_cmd = 'Q';
break;
}
case 'q': {
fixed_t dx1 = pp.read_number(), dy1 = pp.read_number();
fixed_t dx2 = pp.read_number(), dy2 = pp.read_number();
fixed_t x1 = cur_x + dx1, y1 = cur_y + dy1;
fixed_t x2 = cur_x + dx2, y2 = cur_y + dy2;
fixed_t sx0, sy0, sx1, sy1, sx2, sy2;
scale_pt(cur_x, cur_y, &sx0, &sy0);
scale_pt(x1, y1, &sx1, &sy1);
scale_pt(x2, y2, &sx2, &sy2);
svg_flatten_quad(el, sx0, sy0, sx1, sy1, sx2, sy2);
last_cx = x1; last_cy = y1;
cur_x = x2; cur_y = y2;
last_cmd = 'q';
break;
}
case 'A': case 'a': {
// Arc command: consume parameters but approximate as a line
// (arcs are rare in these icons)
fixed_t rx = pp.read_number();
fixed_t ry = pp.read_number();
pp.read_number(); // x-rotation
pp.read_number(); // large-arc-flag
pp.read_number(); // sweep-flag
fixed_t x = pp.read_number();
fixed_t y = pp.read_number();
if (cmd == 'a') { x += cur_x; y += cur_y; }
fixed_t sx0, sy0, sx1, sy1;
scale_pt(cur_x, cur_y, &sx0, &sy0);
scale_pt(x, y, &sx1, &sy1);
el.add(sx0, sy0, sx1, sy1);
cur_x = x; cur_y = y;
last_cmd = cmd;
(void)rx; (void)ry;
break;
}
case 'Z': case 'z': {
if (cur_x != start_x || cur_y != start_y) {
fixed_t sx0, sy0, sx1, sy1;
scale_pt(cur_x, cur_y, &sx0, &sy0);
scale_pt(start_x, start_y, &sx1, &sy1);
el.add(sx0, sy0, sx1, sy1);
}
cur_x = start_x; cur_y = start_y;
last_cmd = 'Z';
break;
}
default:
// Unknown command, skip
break;
}
}
}
// ---------------------------------------------------------------------------
// Scanline rasterizer (even-odd fill rule)
// ---------------------------------------------------------------------------
inline void svg_rasterize(const SvgEdgeList& el, uint32_t* pixels, int w, int h, uint32_t fill) {
// Temporary array for x-intersections on each scanline
// Allocate enough for all edges (each edge can intersect at most once per scanline)
int maxIsect = el.count + 16;
fixed_t* isect = (fixed_t*)zenith::alloc(maxIsect * sizeof(fixed_t));
for (int y = 0; y < h; ++y) {
// Scanline center in fixed-point
fixed_t scanY = int_to_fixed(y) + (1 << 15); // y + 0.5
int isectCount = 0;
// Find intersections with all edges
for (int i = 0; i < el.count; ++i) {
const SvgEdge& e = el.edges[i];
fixed_t ey0 = e.y0, ey1 = e.y1;
// Ensure ey0 <= ey1 for the range check
fixed_t emin = ey0 < ey1 ? ey0 : ey1;
fixed_t emax = ey0 > ey1 ? ey0 : ey1;
// Does this edge cross scanY?
if (scanY < emin || scanY >= emax) continue;
// Compute x at intersection: x = x0 + (scanY - y0) * (x1 - x0) / (y1 - y0)
fixed_t dy = ey1 - ey0;
if (dy == 0) continue; // horizontal, skip
fixed_t dx = e.x1 - e.x0;
fixed_t t_num = scanY - ey0;
// x_intersect = x0 + dx * t_num / dy
fixed_t x_int = e.x0 + (int32_t)(((int64_t)dx * t_num) / dy);
if (isectCount < maxIsect)
isect[isectCount++] = x_int;
}
// Sort intersections (simple insertion sort -- usually very few)
for (int i = 1; i < isectCount; ++i) {
fixed_t key = isect[i];
int j = i - 1;
while (j >= 0 && isect[j] > key) {
isect[j + 1] = isect[j];
--j;
}
isect[j + 1] = key;
}
// Fill between pairs (even-odd rule)
for (int i = 0; i + 1 < isectCount; i += 2) {
int x0 = fixed_to_int(isect[i]);
int x1 = fixed_to_int(isect[i + 1]);
// Clamp to pixel bounds
if (x0 < 0) x0 = 0;
if (x1 > w) x1 = w;
for (int x = x0; x < x1; ++x) {
pixels[y * w + x] = fill;
}
}
}
zenith::free(isect);
}
// ---------------------------------------------------------------------------
// SVG document parser: extract paths, circles, rects and rasterize
// ---------------------------------------------------------------------------
inline SvgIcon svg_render(const char* svg_data, int svg_len, int target_w, int target_h, Color fill_color) {
SvgIcon icon;
icon.width = target_w;
icon.height = target_h;
icon.pixels = (uint32_t*)zenith::alloc(target_w * target_h * sizeof(uint32_t));
// Clear to transparent
svg_memset(icon.pixels, 0, target_w * target_h * sizeof(uint32_t));
uint32_t fill_px = fill_color.to_pixel();
// Parse SVG dimensions: width and height
int svg_w = 16, svg_h = 16;
fixed_t vb_x = 0, vb_y = 0, vb_w = 0, vb_h = 0;
bool has_viewbox = false;
// Find <svg tag
const char* svg_tag = svg_strstr(svg_data, svg_len, "<svg");
if (svg_tag) {
// Find the end of the <svg ...> tag
int tag_offset = (int)(svg_tag - svg_data);
int tag_end = tag_offset;
while (tag_end < svg_len && svg_data[tag_end] != '>') ++tag_end;
int tag_len = tag_end - tag_offset + 1;
char attr_buf[64];
// width
if (svg_get_attr(svg_tag, tag_len, " width", attr_buf, sizeof(attr_buf)) > 0) {
svg_w = svg_parse_int(attr_buf);
}
// height
if (svg_get_attr(svg_tag, tag_len, " height", attr_buf, sizeof(attr_buf)) > 0) {
svg_h = svg_parse_int(attr_buf);
}
// viewBox
if (svg_get_attr(svg_tag, tag_len, " viewBox", attr_buf, sizeof(attr_buf)) > 0) {
has_viewbox = true;
const char* vp = attr_buf;
int c;
c = svg_parse_fixed(vp, &vb_x); vp += c; while (*vp && svg_char_is_sep(*vp)) ++vp;
c = svg_parse_fixed(vp, &vb_y); vp += c; while (*vp && svg_char_is_sep(*vp)) ++vp;
c = svg_parse_fixed(vp, &vb_w); vp += c; while (*vp && svg_char_is_sep(*vp)) ++vp;
svg_parse_fixed(vp, &vb_h);
}
}
if (!has_viewbox) {
vb_x = 0;
vb_y = 0;
vb_w = int_to_fixed(svg_w);
vb_h = int_to_fixed(svg_h);
}
// Compute scale: pixel = (svg_coord - vb_origin) * target_size / vb_size
fixed_t scale_x = vb_w > 0 ? fixed_div(int_to_fixed(target_w), vb_w) : int_to_fixed(1);
fixed_t scale_y = vb_h > 0 ? fixed_div(int_to_fixed(target_h), vb_h) : int_to_fixed(1);
// Edge list
SvgEdgeList el;
el.init(SVG_MAX_EDGES);
// Scan for <path, <circle, <rect elements
const char* p = svg_data;
const char* end = svg_data + svg_len;
while (p < end) {
// Find next '<'
while (p < end && *p != '<') ++p;
if (p >= end) break;
int remaining = (int)(end - p);
// Check for <path
if (remaining > 5 && svg_strncmp(p, "<path", 5) && (svg_char_is_ws(p[5]) || p[5] == '/')) {
// Find end of this element
const char* elem_start = p;
const char* elem_end = p;
while (elem_end < end && *elem_end != '>') ++elem_end;
if (elem_end < end) ++elem_end; // include '>'
int elem_len = (int)(elem_end - elem_start);
// Extract d attribute
char d_buf[SVG_MAX_PATH_LEN];
int d_len = svg_get_attr(elem_start, elem_len, " d", d_buf, SVG_MAX_PATH_LEN);
if (d_len > 0) {
svg_path_to_edges(el, d_buf, d_len, scale_x, scale_y, vb_x, vb_y);
}
p = elem_end;
continue;
}
// Check for <circle
if (remaining > 7 && svg_strncmp(p, "<circle", 7) && (svg_char_is_ws(p[7]) || p[7] == '/')) {
const char* elem_start = p;
const char* elem_end = p;
while (elem_end < end && *elem_end != '>') ++elem_end;
if (elem_end < end) ++elem_end;
int elem_len = (int)(elem_end - elem_start);
char attr_buf[32];
fixed_t cx = 0, cy = 0, r = 0;
if (svg_get_attr(elem_start, elem_len, " cx", attr_buf, sizeof(attr_buf)) > 0)
svg_parse_fixed(attr_buf, &cx);
if (svg_get_attr(elem_start, elem_len, " cy", attr_buf, sizeof(attr_buf)) > 0)
svg_parse_fixed(attr_buf, &cy);
if (svg_get_attr(elem_start, elem_len, " r", attr_buf, sizeof(attr_buf)) > 0)
svg_parse_fixed(attr_buf, &r);
// Scale to target coordinates
fixed_t scx = fixed_mul(cx - vb_x, scale_x);
fixed_t scy = fixed_mul(cy - vb_y, scale_y);
fixed_t srx = fixed_mul(r, scale_x);
fixed_t sry = fixed_mul(r, scale_y);
// Use average of scaled radii
fixed_t sr = (srx + sry) >> 1;
svg_circle_edges(el, scx, scy, sr);
p = elem_end;
continue;
}
// Check for <rect
if (remaining > 5 && svg_strncmp(p, "<rect", 5) && (svg_char_is_ws(p[5]) || p[5] == '/')) {
const char* elem_start = p;
const char* elem_end = p;
while (elem_end < end && *elem_end != '>') ++elem_end;
if (elem_end < end) ++elem_end;
int elem_len = (int)(elem_end - elem_start);
char attr_buf[32];
fixed_t rx_val = 0, ry_val = 0, rw = 0, rh = 0, rrx = 0, rry = 0;
if (svg_get_attr(elem_start, elem_len, " x", attr_buf, sizeof(attr_buf)) > 0)
svg_parse_fixed(attr_buf, &rx_val);
if (svg_get_attr(elem_start, elem_len, " y", attr_buf, sizeof(attr_buf)) > 0)
svg_parse_fixed(attr_buf, &ry_val);
if (svg_get_attr(elem_start, elem_len, " width", attr_buf, sizeof(attr_buf)) > 0)
svg_parse_fixed(attr_buf, &rw);
if (svg_get_attr(elem_start, elem_len, " height", attr_buf, sizeof(attr_buf)) > 0)
svg_parse_fixed(attr_buf, &rh);
if (svg_get_attr(elem_start, elem_len, " rx", attr_buf, sizeof(attr_buf)) > 0)
svg_parse_fixed(attr_buf, &rrx);
if (svg_get_attr(elem_start, elem_len, " ry", attr_buf, sizeof(attr_buf)) > 0)
svg_parse_fixed(attr_buf, &rry);
// Scale all to target pixel space
fixed_t sx = fixed_mul(rx_val - vb_x, scale_x);
fixed_t sy = fixed_mul(ry_val - vb_y, scale_y);
fixed_t sw = fixed_mul(rw, scale_x);
fixed_t sh = fixed_mul(rh, scale_y);
fixed_t srx = fixed_mul(rrx, scale_x);
fixed_t sry = fixed_mul(rry, scale_y);
svg_rect_edges(el, sx, sy, sw, sh, srx, sry);
p = elem_end;
continue;
}
++p;
}
// Rasterize all accumulated edges
if (el.count > 0) {
svg_rasterize(el, icon.pixels, target_w, target_h, fill_px);
}
zenith::free(el.edges);
return icon;
}
// ---------------------------------------------------------------------------
// Load SVG from VFS and render
// ---------------------------------------------------------------------------
inline SvgIcon svg_load(const char* vfs_path, int target_w, int target_h, Color fill_color) {
int fd = zenith::open(vfs_path);
if (fd < 0) {
return {nullptr, 0, 0};
}
uint64_t size = zenith::getsize(fd);
if (size == 0 || size > SVG_MAX_FILE_SIZE) {
zenith::close(fd);
return {nullptr, 0, 0};
}
char* buf = (char*)zenith::alloc(size + 1);
zenith::read(fd, (uint8_t*)buf, 0, size);
zenith::close(fd);
buf[size] = '\0';
SvgIcon icon = svg_render(buf, (int)size, target_w, target_h, fill_color);
zenith::free(buf);
return icon;
}
// ---------------------------------------------------------------------------
// Free icon pixel data
// ---------------------------------------------------------------------------
inline void svg_free(SvgIcon& icon) {
if (icon.pixels) zenith::free(icon.pixels);
icon.pixels = nullptr;
icon.width = 0;
icon.height = 0;
}
} // namespace gui
+526
View File
@@ -0,0 +1,526 @@
/*
* terminal.hpp
* ZenithOS terminal emulator with ANSI escape sequence support
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include "gui/gui.hpp"
#include "gui/font.hpp"
#include <zenith/syscall.h>
#include <zenith/string.h>
#include <Api/Syscall.hpp>
namespace gui {
struct TermCell {
char ch;
Color fg;
Color bg;
};
static constexpr int TERM_MAX_SCROLLBACK = 500;
struct TerminalState {
TermCell* cells;
TermCell* alt_cells; // alternate screen buffer
int cols, rows;
int cursor_x, cursor_y;
int saved_cursor_x, saved_cursor_y; // saved cursor for alternate screen
int scroll_top;
int total_rows;
int child_pid;
Color current_fg;
Color current_bg;
bool cursor_visible;
bool alt_screen_active;
bool reverse_video;
enum { STATE_NORMAL, STATE_ESC, STATE_CSI } parse_state;
bool csi_private; // true if '?' was seen after CSI
int csi_params[8];
int csi_param_count;
int csi_current_param;
};
// Standard ANSI color palette as ARGB pixels
static inline Color term_ansi_color(int idx) {
switch (idx) {
case 0: return Color::from_hex(0x000000);
case 1: return Color::from_hex(0xCC0000);
case 2: return Color::from_hex(0x4E9A06);
case 3: return Color::from_hex(0xC4A000);
case 4: return Color::from_hex(0x3465A4);
case 5: return Color::from_hex(0x75507B);
case 6: return Color::from_hex(0x06989A);
case 7: return Color::from_hex(0xD3D7CF);
case 8: return Color::from_hex(0x555753);
case 9: return Color::from_hex(0xEF2929);
case 10: return Color::from_hex(0x8AE234);
case 11: return Color::from_hex(0xFCE94F);
case 12: return Color::from_hex(0x729FCF);
case 13: return Color::from_hex(0xAD7FA8);
case 14: return Color::from_hex(0x34E2E2);
case 15: return Color::from_hex(0xEEEEEC);
default: return colors::TERM_FG;
}
}
static inline void terminal_scroll_up(TerminalState* t) {
// Move all rows up by one
for (int r = 0; r < t->rows - 1; r++) {
for (int c = 0; c < t->cols; c++) {
t->cells[r * t->cols + c] = t->cells[(r + 1) * t->cols + c];
}
}
// Clear last row
int last = t->rows - 1;
for (int c = 0; c < t->cols; c++) {
t->cells[last * t->cols + c] = {' ', t->current_fg, colors::TERM_BG};
}
}
static inline void terminal_init(TerminalState* t, int cols, int rows) {
t->cols = cols;
t->rows = rows;
t->cursor_x = 0;
t->cursor_y = 0;
t->saved_cursor_x = 0;
t->saved_cursor_y = 0;
t->scroll_top = 0;
t->total_rows = rows;
t->current_fg = colors::TERM_FG;
t->current_bg = colors::TERM_BG;
t->cursor_visible = true;
t->alt_screen_active = false;
t->reverse_video = false;
t->parse_state = TerminalState::STATE_NORMAL;
t->csi_private = false;
t->csi_param_count = 0;
t->csi_current_param = 0;
int total_cells = cols * rows;
t->cells = (TermCell*)zenith::alloc(total_cells * sizeof(TermCell));
t->alt_cells = (TermCell*)zenith::alloc(total_cells * sizeof(TermCell));
for (int i = 0; i < total_cells; i++) {
t->cells[i] = {' ', colors::TERM_FG, colors::TERM_BG};
t->alt_cells[i] = {' ', colors::TERM_FG, colors::TERM_BG};
}
t->child_pid = zenith::spawn_redir("0:/os/shell.elf");
zenith::childio_settermsz(t->child_pid, cols, rows);
}
static inline void terminal_put_char(TerminalState* t, char ch) {
if (t->cursor_x >= t->cols) {
t->cursor_x = 0;
t->cursor_y++;
}
if (t->cursor_y >= t->rows) {
terminal_scroll_up(t);
t->cursor_y = t->rows - 1;
}
int idx = t->cursor_y * t->cols + t->cursor_x;
t->cells[idx].ch = ch;
t->cells[idx].fg = t->current_fg;
t->cells[idx].bg = t->current_bg;
t->cursor_x++;
}
static inline void terminal_enter_alt_screen(TerminalState* t) {
if (t->alt_screen_active) return;
t->alt_screen_active = true;
// Save cursor
t->saved_cursor_x = t->cursor_x;
t->saved_cursor_y = t->cursor_y;
// Swap buffers: save main screen to alt_cells, clear main
int total = t->cols * t->rows;
for (int i = 0; i < total; i++) {
t->alt_cells[i] = t->cells[i];
t->cells[i] = {' ', colors::TERM_FG, colors::TERM_BG};
}
t->cursor_x = 0;
t->cursor_y = 0;
}
static inline void terminal_exit_alt_screen(TerminalState* t) {
if (!t->alt_screen_active) return;
t->alt_screen_active = false;
// Restore main screen from alt_cells
int total = t->cols * t->rows;
for (int i = 0; i < total; i++) {
t->cells[i] = t->alt_cells[i];
}
// Restore cursor
t->cursor_x = t->saved_cursor_x;
t->cursor_y = t->saved_cursor_y;
}
static inline void terminal_process_private_mode(TerminalState* t, char cmd) {
int p0 = t->csi_param_count > 0 ? t->csi_params[0] : 0;
if (cmd == 'h') {
// Set private mode
if (p0 == 25) {
t->cursor_visible = true;
} else if (p0 == 1049) {
terminal_enter_alt_screen(t);
}
} else if (cmd == 'l') {
// Reset private mode
if (p0 == 25) {
t->cursor_visible = false;
} else if (p0 == 1049) {
terminal_exit_alt_screen(t);
}
}
}
static inline void terminal_process_csi(TerminalState* t, char cmd) {
// Finalize current param
if (t->csi_param_count < 8) {
t->csi_params[t->csi_param_count] = t->csi_current_param;
t->csi_param_count++;
}
// Handle private mode sequences (ESC[?...)
if (t->csi_private) {
terminal_process_private_mode(t, cmd);
return;
}
int p0 = t->csi_param_count > 0 ? t->csi_params[0] : 0;
int p1 = t->csi_param_count > 1 ? t->csi_params[1] : 0;
switch (cmd) {
case 'H': case 'f': {
// Cursor position: ESC[row;colH (1-based)
int row = (p0 > 0 ? p0 : 1) - 1;
int col = (p1 > 0 ? p1 : 1) - 1;
if (row < 0) row = 0;
if (row >= t->rows) row = t->rows - 1;
if (col < 0) col = 0;
if (col >= t->cols) col = t->cols - 1;
t->cursor_y = row;
t->cursor_x = col;
break;
}
case 'A': {
// Cursor up
int n = p0 > 0 ? p0 : 1;
t->cursor_y -= n;
if (t->cursor_y < 0) t->cursor_y = 0;
break;
}
case 'B': {
// Cursor down
int n = p0 > 0 ? p0 : 1;
t->cursor_y += n;
if (t->cursor_y >= t->rows) t->cursor_y = t->rows - 1;
break;
}
case 'C': {
// Cursor forward
int n = p0 > 0 ? p0 : 1;
t->cursor_x += n;
if (t->cursor_x >= t->cols) t->cursor_x = t->cols - 1;
break;
}
case 'D': {
// Cursor backward
int n = p0 > 0 ? p0 : 1;
t->cursor_x -= n;
if (t->cursor_x < 0) t->cursor_x = 0;
break;
}
case 'J': {
// Erase in display
if (p0 == 0) {
// Clear from cursor to end
for (int x = t->cursor_x; x < t->cols; x++)
t->cells[t->cursor_y * t->cols + x] = {' ', t->current_fg, colors::TERM_BG};
for (int r = t->cursor_y + 1; r < t->rows; r++)
for (int c = 0; c < t->cols; c++)
t->cells[r * t->cols + c] = {' ', t->current_fg, colors::TERM_BG};
} else if (p0 == 1) {
// Clear from start to cursor
for (int r = 0; r < t->cursor_y; r++)
for (int c = 0; c < t->cols; c++)
t->cells[r * t->cols + c] = {' ', t->current_fg, colors::TERM_BG};
for (int x = 0; x <= t->cursor_x; x++)
t->cells[t->cursor_y * t->cols + x] = {' ', t->current_fg, colors::TERM_BG};
} else if (p0 == 2) {
// Clear entire screen
for (int i = 0; i < t->rows * t->cols; i++)
t->cells[i] = {' ', t->current_fg, colors::TERM_BG};
t->cursor_x = 0;
t->cursor_y = 0;
}
break;
}
case 'K': {
// Erase in line
int start = 0, end = t->cols;
if (p0 == 0) { start = t->cursor_x; end = t->cols; }
else if (p0 == 1) { start = 0; end = t->cursor_x + 1; }
else if (p0 == 2) { start = 0; end = t->cols; }
for (int x = start; x < end; x++)
t->cells[t->cursor_y * t->cols + x] = {' ', t->current_fg, colors::TERM_BG};
break;
}
case 'm': {
// SGR - Set Graphics Rendition
for (int i = 0; i < t->csi_param_count; i++) {
int code = t->csi_params[i];
if (code == 0) {
t->current_fg = colors::TERM_FG;
t->current_bg = colors::TERM_BG;
t->reverse_video = false;
} else if (code == 1) {
// Bold: map to bright version of current color
uint8_t r = t->current_fg.r;
uint8_t g = t->current_fg.g;
uint8_t b = t->current_fg.b;
int add = 50;
r = (r + add > 255) ? 255 : r + add;
g = (g + add > 255) ? 255 : g + add;
b = (b + add > 255) ? 255 : b + add;
t->current_fg = Color::from_rgb(r, g, b);
} else if (code == 2) {
// Dim: darken current fg color
t->current_fg.r = t->current_fg.r / 2;
t->current_fg.g = t->current_fg.g / 2;
t->current_fg.b = t->current_fg.b / 2;
} else if (code == 7) {
// Reverse video
if (!t->reverse_video) {
t->reverse_video = true;
Color tmp = t->current_fg;
t->current_fg = t->current_bg;
t->current_bg = tmp;
}
} else if (code == 27) {
// Reverse off
if (t->reverse_video) {
t->reverse_video = false;
Color tmp = t->current_fg;
t->current_fg = t->current_bg;
t->current_bg = tmp;
}
} else if (code >= 30 && code <= 37) {
t->current_fg = term_ansi_color(code - 30);
if (t->reverse_video) {
// In reverse mode, fg is displayed as bg
Color tmp = t->current_fg;
t->current_fg = t->current_bg;
t->current_bg = tmp;
}
} else if (code >= 40 && code <= 47) {
t->current_bg = term_ansi_color(code - 40);
} else if (code >= 90 && code <= 97) {
t->current_fg = term_ansi_color(code - 90 + 8);
} else if (code >= 100 && code <= 107) {
t->current_bg = term_ansi_color(code - 100 + 8);
} else if (code == 39) {
t->current_fg = colors::TERM_FG;
} else if (code == 49) {
t->current_bg = colors::TERM_BG;
}
}
if (t->csi_param_count == 0) {
// ESC[m with no params = reset
t->current_fg = colors::TERM_FG;
t->current_bg = colors::TERM_BG;
t->reverse_video = false;
}
break;
}
default:
break;
}
}
static inline void terminal_feed(TerminalState* t, const char* data, int len) {
for (int i = 0; i < len; i++) {
char ch = data[i];
switch (t->parse_state) {
case TerminalState::STATE_NORMAL:
if (ch == '\033') {
t->parse_state = TerminalState::STATE_ESC;
} else if (ch == '\n') {
t->cursor_x = 0; // CR+LF: shell sends \n without \r
t->cursor_y++;
if (t->cursor_y >= t->rows) {
terminal_scroll_up(t);
t->cursor_y = t->rows - 1;
}
} else if (ch == '\r') {
t->cursor_x = 0;
} else if (ch == '\b') {
if (t->cursor_x > 0) t->cursor_x--;
} else if (ch == '\t') {
int next = (t->cursor_x + 8) & ~7;
if (next > t->cols) next = t->cols;
while (t->cursor_x < next) {
terminal_put_char(t, ' ');
}
} else if (ch >= 32 || ch < 0) {
// Printable character (also treat high-bit chars as printable)
terminal_put_char(t, ch);
}
break;
case TerminalState::STATE_ESC:
if (ch == '[') {
t->parse_state = TerminalState::STATE_CSI;
t->csi_private = false;
t->csi_param_count = 0;
t->csi_current_param = 0;
for (int j = 0; j < 8; j++) t->csi_params[j] = 0;
} else if (ch == 'c') {
// Reset terminal
t->current_fg = colors::TERM_FG;
t->current_bg = colors::TERM_BG;
t->cursor_x = 0;
t->cursor_y = 0;
t->parse_state = TerminalState::STATE_NORMAL;
} else {
// Unknown ESC sequence, ignore
t->parse_state = TerminalState::STATE_NORMAL;
}
break;
case TerminalState::STATE_CSI:
if (ch >= '0' && ch <= '9') {
t->csi_current_param = t->csi_current_param * 10 + (ch - '0');
} else if (ch == ';') {
if (t->csi_param_count < 8) {
t->csi_params[t->csi_param_count] = t->csi_current_param;
t->csi_param_count++;
}
t->csi_current_param = 0;
} else if (ch == '?') {
t->csi_private = true;
} else if (ch >= 0x40 && ch <= 0x7E) {
// Final byte - execute command
terminal_process_csi(t, ch);
t->parse_state = TerminalState::STATE_NORMAL;
} else {
// Unknown, abort CSI
t->parse_state = TerminalState::STATE_NORMAL;
}
break;
}
}
}
static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, int ph) {
// Fill background
uint32_t bg_px = colors::TERM_BG.to_pixel();
int total = pw * ph;
for (int i = 0; i < total; i++) {
pixels[i] = bg_px;
}
// Render each visible cell
int visible_rows = ph / FONT_HEIGHT;
int visible_cols = pw / FONT_WIDTH;
if (visible_rows > t->rows) visible_rows = t->rows;
if (visible_cols > t->cols) visible_cols = t->cols;
for (int r = 0; r < visible_rows; r++) {
for (int c = 0; c < visible_cols; c++) {
int idx = r * t->cols + c;
TermCell& cell = t->cells[idx];
int px = c * FONT_WIDTH;
int py = r * FONT_HEIGHT;
uint32_t cell_bg = cell.bg.to_pixel();
uint32_t cell_fg = cell.fg.to_pixel();
// Draw cell background
for (int fy = 0; fy < FONT_HEIGHT; fy++) {
int dy = py + fy;
if (dy >= ph) break;
for (int fx = 0; fx < FONT_WIDTH; fx++) {
int dx = px + fx;
if (dx >= pw) break;
pixels[dy * pw + dx] = cell_bg;
}
}
// Draw character glyph
if (cell.ch > 32 || cell.ch < 0) {
const uint8_t* glyph = &font_data[(unsigned char)cell.ch * FONT_HEIGHT];
for (int fy = 0; fy < FONT_HEIGHT; fy++) {
int dy = py + fy;
if (dy >= ph) break;
uint8_t bits = glyph[fy];
for (int fx = 0; fx < FONT_WIDTH; fx++) {
if (bits & (0x80 >> fx)) {
int dx = px + fx;
if (dx >= pw) break;
pixels[dy * pw + dx] = cell_fg;
}
}
}
}
}
}
// Draw cursor
if (t->cursor_visible && t->cursor_x < visible_cols && t->cursor_y < visible_rows) {
int cx = t->cursor_x * FONT_WIDTH;
int cy = t->cursor_y * FONT_HEIGHT;
uint32_t cursor_px = colors::WHITE.to_pixel();
for (int fy = 0; fy < FONT_HEIGHT; fy++) {
int dy = cy + fy;
if (dy >= ph) break;
for (int fx = 0; fx < FONT_WIDTH; fx++) {
int dx = cx + fx;
if (dx >= pw) break;
pixels[dy * pw + dx] = cursor_px;
}
}
// Draw character on top of cursor in black
if (t->cursor_y < t->rows && t->cursor_x < t->cols) {
int idx = t->cursor_y * t->cols + t->cursor_x;
char ch = t->cells[idx].ch;
if (ch > 32 || ch < 0) {
const uint8_t* glyph = &font_data[(unsigned char)ch * FONT_HEIGHT];
uint32_t black_px = colors::BLACK.to_pixel();
for (int fy = 0; fy < FONT_HEIGHT; fy++) {
int dy = cy + fy;
if (dy >= ph) break;
uint8_t bits = glyph[fy];
for (int fx = 0; fx < FONT_WIDTH; fx++) {
if (bits & (0x80 >> fx)) {
int dx = cx + fx;
if (dx >= pw) break;
pixels[dy * pw + dx] = black_px;
}
}
}
}
}
}
}
static inline void terminal_handle_key(TerminalState* t, const Zenith::KeyEvent& key) {
if (t->child_pid > 0) {
zenith::childio_writekey(t->child_pid, &key);
}
}
static inline void terminal_poll(TerminalState* t) {
if (t->child_pid <= 0) return;
char buf[512];
int n = zenith::childio_read(t->child_pid, buf, sizeof(buf));
if (n > 0) {
terminal_feed(t, buf, n);
}
}
} // namespace gui
+354
View File
@@ -0,0 +1,354 @@
/*
* widgets.hpp
* ZenithOS GUI widget toolkit (Label, Button, TextBox, Scrollbar)
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include "gui/gui.hpp"
#include "gui/framebuffer.hpp"
#include "gui/font.hpp"
#include "gui/draw.hpp"
namespace gui {
// ---- Mouse event ----
struct MouseEvent {
int x, y;
uint8_t buttons;
uint8_t prev_buttons;
int32_t scroll;
bool left_held() const { return buttons & 0x01; }
bool right_held() const { return buttons & 0x02; }
bool middle_held() const { return buttons & 0x04; }
bool left_pressed() const { return (buttons & 0x01) && !(prev_buttons & 0x01); }
bool left_released() const { return !(buttons & 0x01) && (prev_buttons & 0x01); }
bool right_pressed() const { return (buttons & 0x02) && !(prev_buttons & 0x02); }
bool right_released() const { return !(buttons & 0x02) && (prev_buttons & 0x02); }
};
// ---- Callback types ----
using ClickCallback = void (*)(void* userdata);
// ---- Label ----
struct Label {
int x, y;
const char* text;
Color color;
void draw(Framebuffer& fb) const {
if (text) {
draw_text(fb, x, y, text, color);
}
}
};
// ---- Button ----
struct Button {
Rect bounds;
const char* text;
Color bg;
Color fg;
Color hover_bg;
bool hovered;
bool pressed;
ClickCallback on_click;
void* userdata;
void init(int x, int y, int w, int h, const char* label) {
bounds = {x, y, w, h};
text = label;
bg = colors::ACCENT;
fg = colors::WHITE;
hover_bg = Color::from_rgb(0x2B, 0x6B, 0xE0);
hovered = false;
pressed = false;
on_click = nullptr;
userdata = nullptr;
}
void draw(Framebuffer& fb) const {
Color bg_color = hovered ? hover_bg : bg;
fill_rounded_rect(fb, bounds.x, bounds.y, bounds.w, bounds.h, 4, bg_color);
// Center text
int tw = text_width(text);
int tx = bounds.x + (bounds.w - tw) / 2;
int ty = bounds.y + (bounds.h - FONT_HEIGHT) / 2;
draw_text(fb, tx, ty, text, fg);
}
bool handle_mouse(const MouseEvent& ev) {
hovered = bounds.contains(ev.x, ev.y);
if (hovered && ev.left_pressed()) {
pressed = true;
}
if (pressed && ev.left_released()) {
pressed = false;
if (hovered && on_click) {
on_click(userdata);
return true;
}
}
if (!ev.left_held()) {
pressed = false;
}
return false;
}
};
// ---- IconButton (for panel/menu items with SVG icon + optional text) ----
struct IconButton {
Rect bounds;
const char* text; // optional label (can be nullptr)
uint32_t* icon_pixels; // icon pixel data (ARGB)
int icon_w, icon_h;
Color bg;
Color hover_bg;
Color text_color;
bool hovered;
bool pressed;
ClickCallback on_click;
void* userdata;
void init(int x, int y, int w, int h) {
bounds = {x, y, w, h};
text = nullptr;
icon_pixels = nullptr;
icon_w = 0;
icon_h = 0;
bg = {0, 0, 0, 0}; // transparent by default
hover_bg = colors::MENU_HOVER;
text_color = colors::TEXT_COLOR;
hovered = false;
pressed = false;
on_click = nullptr;
userdata = nullptr;
}
void draw(Framebuffer& fb) const {
if (hovered) {
fill_rounded_rect(fb, bounds.x, bounds.y, bounds.w, bounds.h, 3, hover_bg);
} else if (bg.a > 0) {
fill_rounded_rect(fb, bounds.x, bounds.y, bounds.w, bounds.h, 3, bg);
}
int content_x = bounds.x + 6;
// Draw icon
if (icon_pixels && icon_w > 0 && icon_h > 0) {
int iy = bounds.y + (bounds.h - icon_h) / 2;
fb.blit_alpha(content_x, iy, icon_w, icon_h, icon_pixels);
content_x += icon_w + 6;
}
// Draw text
if (text) {
int ty = bounds.y + (bounds.h - FONT_HEIGHT) / 2;
draw_text(fb, content_x, ty, text, text_color);
}
}
bool handle_mouse(const MouseEvent& ev) {
hovered = bounds.contains(ev.x, ev.y);
if (hovered && ev.left_pressed()) {
pressed = true;
}
if (pressed && ev.left_released()) {
pressed = false;
if (hovered && on_click) {
on_click(userdata);
return true;
}
}
if (!ev.left_held()) {
pressed = false;
}
return false;
}
};
// ---- TextBox ----
struct TextBox {
Rect bounds;
char text[256];
int cursor;
int text_len;
bool focused;
Color bg;
Color fg;
Color border_color;
Color cursor_color;
void init(int x, int y, int w, int h) {
bounds = {x, y, w, h};
text[0] = '\0';
cursor = 0;
text_len = 0;
focused = false;
bg = colors::WHITE;
fg = colors::TEXT_COLOR;
border_color = colors::BORDER;
cursor_color = colors::ACCENT;
}
void draw(Framebuffer& fb) const {
fb.fill_rect(bounds.x, bounds.y, bounds.w, bounds.h, bg);
draw_rect(fb, bounds.x, bounds.y, bounds.w, bounds.h,
focused ? cursor_color : border_color);
// Draw text with 4px padding
int tx = bounds.x + 4;
int ty = bounds.y + (bounds.h - FONT_HEIGHT) / 2;
draw_text(fb, tx, ty, text, fg);
// Draw cursor if focused
if (focused) {
int cx = tx + cursor * FONT_WIDTH;
draw_vline(fb, cx, ty, FONT_HEIGHT, cursor_color);
}
}
void handle_mouse(const MouseEvent& ev) {
if (ev.left_pressed()) {
focused = bounds.contains(ev.x, ev.y);
}
}
void handle_key(const Zenith::KeyEvent& key) {
if (!focused || !key.pressed) return;
if (key.ascii == '\b' || key.scancode == 0x0E) {
// Backspace
if (cursor > 0) {
for (int i = cursor - 1; i < text_len - 1; i++) {
text[i] = text[i + 1];
}
text_len--;
cursor--;
text[text_len] = '\0';
}
} else if (key.ascii >= 32 && key.ascii < 127) {
// Printable character
if (text_len < 254) {
for (int i = text_len; i > cursor; i--) {
text[i] = text[i - 1];
}
text[cursor] = key.ascii;
cursor++;
text_len++;
text[text_len] = '\0';
}
} else if (key.scancode == 0x4B) {
// Left arrow
if (cursor > 0) cursor--;
} else if (key.scancode == 0x4D) {
// Right arrow
if (cursor < text_len) cursor++;
}
}
};
// ---- Scrollbar ----
struct Scrollbar {
Rect bounds;
int content_height;
int view_height;
int scroll_offset;
bool dragging;
int drag_start_y;
int drag_start_offset;
Color bg;
Color fg;
Color hover_fg;
bool hovered;
void init(int x, int y, int w, int h) {
bounds = {x, y, w, h};
content_height = 0;
view_height = h;
scroll_offset = 0;
dragging = false;
drag_start_y = 0;
drag_start_offset = 0;
bg = colors::SCROLLBAR_BG;
fg = colors::SCROLLBAR_FG;
hover_fg = Color::from_rgb(0xA0, 0xA0, 0xA0);
hovered = false;
}
int thumb_height() const {
if (content_height <= view_height) return bounds.h;
int th = (view_height * bounds.h) / content_height;
return th < 20 ? 20 : th;
}
int thumb_y() const {
if (content_height <= view_height) return bounds.y;
int range = bounds.h - thumb_height();
int max_scroll = content_height - view_height;
if (max_scroll <= 0) return bounds.y;
return bounds.y + (scroll_offset * range) / max_scroll;
}
int max_scroll() const {
int ms = content_height - view_height;
return ms > 0 ? ms : 0;
}
void draw(Framebuffer& fb) const {
if (content_height <= view_height) return; // no scrollbar needed
fb.fill_rect(bounds.x, bounds.y, bounds.w, bounds.h, bg);
int th = thumb_height();
int ty = thumb_y();
Color thumb_color = (hovered || dragging) ? hover_fg : fg;
fill_rounded_rect(fb, bounds.x + 1, ty, bounds.w - 2, th, 3, thumb_color);
}
void handle_mouse(const MouseEvent& ev) {
if (content_height <= view_height) return;
Rect thumb_rect = {bounds.x, thumb_y(), bounds.w, thumb_height()};
hovered = thumb_rect.contains(ev.x, ev.y);
if (hovered && ev.left_pressed()) {
dragging = true;
drag_start_y = ev.y;
drag_start_offset = scroll_offset;
}
if (dragging && ev.left_held()) {
int dy = ev.y - drag_start_y;
int range = bounds.h - thumb_height();
if (range > 0) {
int ms = max_scroll();
scroll_offset = drag_start_offset + (dy * ms) / range;
if (scroll_offset < 0) scroll_offset = 0;
if (scroll_offset > ms) scroll_offset = ms;
}
}
if (!ev.left_held()) {
dragging = false;
}
// Handle scroll wheel
if (bounds.contains(ev.x, ev.y) && ev.scroll != 0) {
scroll_offset += ev.scroll * 20;
int ms = max_scroll();
if (scroll_offset < 0) scroll_offset = 0;
if (scroll_offset > ms) scroll_offset = ms;
}
}
};
} // namespace gui
+78
View File
@@ -0,0 +1,78 @@
/*
* window.hpp
* ZenithOS window management types
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include "gui/gui.hpp"
#include "gui/framebuffer.hpp"
#include "gui/widgets.hpp"
#include <Api/Syscall.hpp>
namespace gui {
enum WindowState { WIN_NORMAL, WIN_MINIMIZED, WIN_MAXIMIZED, WIN_CLOSED };
static constexpr int TITLEBAR_HEIGHT = 30;
static constexpr int BORDER_WIDTH = 1;
static constexpr int SHADOW_SIZE = 3;
static constexpr int BTN_RADIUS = 6;
static constexpr int MAX_TITLE_LEN = 64;
struct Window;
using WindowDrawCallback = void (*)(Window* win, Framebuffer& fb);
using WindowMouseCallback = void (*)(Window* win, MouseEvent& ev);
using WindowKeyCallback = void (*)(Window* win, const Zenith::KeyEvent& key);
using WindowCloseCallback = void (*)(Window* win);
struct Window {
char title[MAX_TITLE_LEN];
Rect frame;
WindowState state;
int z_order;
bool focused;
bool dirty;
uint32_t* content;
int content_w, content_h;
bool dragging;
int drag_offset_x, drag_offset_y;
bool resizing;
Rect saved_frame;
WindowDrawCallback on_draw;
WindowMouseCallback on_mouse;
WindowKeyCallback on_key;
WindowCloseCallback on_close;
void* app_data;
Rect titlebar_rect() const {
return {frame.x, frame.y, frame.w, TITLEBAR_HEIGHT};
}
Rect content_rect() const {
return {frame.x + BORDER_WIDTH, frame.y + TITLEBAR_HEIGHT,
frame.w - 2 * BORDER_WIDTH, frame.h - TITLEBAR_HEIGHT - BORDER_WIDTH};
}
Rect close_btn_rect() const {
int by = frame.y + (TITLEBAR_HEIGHT - BTN_RADIUS * 2) / 2;
return {frame.x + 12, by, BTN_RADIUS * 2, BTN_RADIUS * 2};
}
Rect min_btn_rect() const {
int by = frame.y + (TITLEBAR_HEIGHT - BTN_RADIUS * 2) / 2;
return {frame.x + 12 + 22, by, BTN_RADIUS * 2, BTN_RADIUS * 2};
}
Rect max_btn_rect() const {
int by = frame.y + (TITLEBAR_HEIGHT - BTN_RADIUS * 2) / 2;
return {frame.x + 12 + 44, by, BTN_RADIUS * 2, BTN_RADIUS * 2};
}
};
} // namespace gui
+23
View File
@@ -256,4 +256,27 @@ namespace zenith {
__builtin_unreachable(); __builtin_unreachable();
} }
// Mouse
inline void mouse_state(Zenith::MouseState* out) { syscall1(Zenith::SYS_MOUSESTATE, (uint64_t)out); }
inline void set_mouse_bounds(int32_t maxX, int32_t maxY) {
syscall2(Zenith::SYS_SETMOUSEBOUNDS, (uint64_t)maxX, (uint64_t)maxY);
}
// I/O redirection
inline int spawn_redir(const char* path, const char* args = nullptr) {
return (int)syscall2(Zenith::SYS_SPAWN_REDIR, (uint64_t)path, (uint64_t)args);
}
inline int childio_read(int childPid, char* buf, int maxLen) {
return (int)syscall3(Zenith::SYS_CHILDIO_READ, (uint64_t)childPid, (uint64_t)buf, (uint64_t)maxLen);
}
inline int childio_write(int childPid, const char* data, int len) {
return (int)syscall3(Zenith::SYS_CHILDIO_WRITE, (uint64_t)childPid, (uint64_t)data, (uint64_t)len);
}
inline int childio_writekey(int childPid, const Zenith::KeyEvent* key) {
return (int)syscall2(Zenith::SYS_CHILDIO_WRITEKEY, (uint64_t)childPid, (uint64_t)key);
}
inline int childio_settermsz(int childPid, int cols, int rows) {
return (int)syscall3(Zenith::SYS_CHILDIO_SETTERMSZ, (uint64_t)childPid, (uint64_t)cols, (uint64_t)rows);
}
} }
Binary file not shown.
Binary file not shown.
+86
View File
@@ -0,0 +1,86 @@
# Makefile for desktop (GUI desktop environment) on ZenithOS
# Copyright (c) 2025 Daniel Hammer
MAKEFLAGS += -rR
.SUFFIXES:
# ---- Toolchain ----
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-elf-
ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
CXX := $(TOOLCHAIN_PREFIX)g++
else
CXX := g++
endif
# ---- Paths ----
PROG_INC := ../../include
LINK_LD := ../../link.ld
BINDIR := ../../bin
OBJDIR := obj
# ---- Compiler flags ----
CXXFLAGS := \
-std=gnu++20 \
-g -O2 -pipe \
-Wall \
-Wextra \
-Wno-unused-parameter \
-Wno-unused-function \
-nostdinc \
-ffreestanding \
-fno-stack-protector \
-fno-stack-check \
-fno-PIC \
-fno-rtti \
-fno-exceptions \
-ffunction-sections \
-fdata-sections \
-m64 \
-march=x86-64 \
-mno-80387 \
-mno-mmx \
-mno-sse \
-mno-sse2 \
-mno-red-zone \
-mcmodel=small \
-I $(PROG_INC) \
-isystem ../../../kernel/freestnd-c-hdrs/x86_64/include \
-isystem ../../../kernel/freestnd-cxx-hdrs/x86_64/include
# ---- Linker flags ----
LDFLAGS := \
-nostdlib \
-static \
-Wl,--build-id=none \
-Wl,--gc-sections \
-Wl,-m,elf_x86_64 \
-z max-page-size=0x1000 \
-T $(LINK_LD)
# ---- Source files ----
SRCS := main.cpp font_data.cpp
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
# ---- Target ----
TARGET := $(BINDIR)/os/desktop.elf
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJS) $(LINK_LD) Makefile
mkdir -p $(BINDIR)/os
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) -o $@
$(OBJDIR)/%.o: %.cpp Makefile
mkdir -p $(OBJDIR)
$(CXX) $(CXXFLAGS) -c $< -o $@
clean:
rm -rf $(OBJDIR) $(TARGET)
+783
View File
@@ -0,0 +1,783 @@
/*
* font_data.cpp
* Standard VGA 8x16 bitmap font (Code Page 437)
* 256 characters, 16 bytes each (8 pixels wide, 1 bit per pixel, MSB left)
* Copyright (c) 2025 Daniel Hammer
*/
#include "gui/font.hpp"
namespace gui {
const uint8_t font_data[256 * 16] = {
// Character 0 (NUL)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 1 (smiley face)
0x00, 0x00, 0x7E, 0x81, 0xA5, 0x81, 0x81, 0xBD,
0x99, 0x81, 0x81, 0x7E, 0x00, 0x00, 0x00, 0x00,
// Character 2 (inverse smiley)
0x00, 0x00, 0x7E, 0xFF, 0xDB, 0xFF, 0xFF, 0xC3,
0xE7, 0xFF, 0xFF, 0x7E, 0x00, 0x00, 0x00, 0x00,
// Character 3 (heart)
0x00, 0x00, 0x00, 0x00, 0x6C, 0xFE, 0xFE, 0xFE,
0xFE, 0x7C, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00,
// Character 4 (diamond)
0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x7C, 0xFE,
0x7C, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 5 (club)
0x00, 0x00, 0x00, 0x18, 0x3C, 0x3C, 0xE7, 0xE7,
0xE7, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
// Character 6 (spade)
0x00, 0x00, 0x00, 0x18, 0x3C, 0x7E, 0xFF, 0xFF,
0x7E, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
// Character 7 (bullet)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3C,
0x3C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 8 (inverse bullet)
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xC3,
0xC3, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// Character 9 (circle)
0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x66, 0x42,
0x42, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 10 (inverse circle)
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0x99, 0xBD,
0xBD, 0x99, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// Character 11 (male sign)
0x00, 0x00, 0x1E, 0x0E, 0x1A, 0x32, 0x78, 0xCC,
0xCC, 0xCC, 0xCC, 0x78, 0x00, 0x00, 0x00, 0x00,
// Character 12 (female sign)
0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x3C,
0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
// Character 13 (note)
0x00, 0x00, 0x3F, 0x33, 0x3F, 0x30, 0x30, 0x30,
0x30, 0x70, 0xF0, 0xE0, 0x00, 0x00, 0x00, 0x00,
// Character 14 (double note)
0x00, 0x00, 0x7F, 0x63, 0x7F, 0x63, 0x63, 0x63,
0x63, 0x67, 0xE7, 0xE6, 0xC0, 0x00, 0x00, 0x00,
// Character 15 (sun)
0x00, 0x00, 0x00, 0x18, 0x18, 0xDB, 0x3C, 0xE7,
0x3C, 0xDB, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
// Character 16 (right triangle)
0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFE, 0xF8,
0xF0, 0xE0, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00,
// Character 17 (left triangle)
0x00, 0x02, 0x06, 0x0E, 0x1E, 0x3E, 0xFE, 0x3E,
0x1E, 0x0E, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00,
// Character 18 (up-down arrow)
0x00, 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18,
0x7E, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 19 (double exclamation)
0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
0x66, 0x00, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
// Character 20 (paragraph)
0x00, 0x00, 0x7F, 0xDB, 0xDB, 0xDB, 0x7B, 0x1B,
0x1B, 0x1B, 0x1B, 0x1B, 0x00, 0x00, 0x00, 0x00,
// Character 21 (section)
0x00, 0x7C, 0xC6, 0x60, 0x38, 0x6C, 0xC6, 0xC6,
0x6C, 0x38, 0x0C, 0xC6, 0x7C, 0x00, 0x00, 0x00,
// Character 22 (thick underscore)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFE, 0xFE, 0xFE, 0xFE, 0x00, 0x00, 0x00, 0x00,
// Character 23 (up-down arrow underlined)
0x00, 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18,
0x7E, 0x3C, 0x18, 0x7E, 0x00, 0x00, 0x00, 0x00,
// Character 24 (up arrow)
0x00, 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18,
0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
// Character 25 (down arrow)
0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
0x18, 0x7E, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00,
// Character 26 (right arrow)
0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0C, 0xFE,
0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 27 (left arrow)
0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x60, 0xFE,
0x60, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 28 (right angle)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0,
0xC0, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 29 (left-right arrow)
0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x66, 0xFF,
0x66, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 30 (up triangle)
0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x38, 0x7C,
0x7C, 0xFE, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 31 (down triangle)
0x00, 0x00, 0x00, 0x00, 0xFE, 0xFE, 0x7C, 0x7C,
0x38, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 32 (space)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 33 '!'
0x00, 0x00, 0x18, 0x3C, 0x3C, 0x3C, 0x18, 0x18,
0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
// Character 34 '"'
0x00, 0x66, 0x66, 0x66, 0x24, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 35 '#'
0x00, 0x00, 0x00, 0x6C, 0x6C, 0xFE, 0x6C, 0x6C,
0x6C, 0xFE, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00,
// Character 36 '$'
0x18, 0x18, 0x7C, 0xC6, 0xC2, 0xC0, 0x7C, 0x06,
0x06, 0x86, 0xC6, 0x7C, 0x18, 0x18, 0x00, 0x00,
// Character 37 '%'
0x00, 0x00, 0x00, 0x00, 0xC2, 0xC6, 0x0C, 0x18,
0x30, 0x60, 0xC6, 0x86, 0x00, 0x00, 0x00, 0x00,
// Character 38 '&'
0x00, 0x00, 0x38, 0x6C, 0x6C, 0x38, 0x76, 0xDC,
0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00,
// Character 39 '''
0x00, 0x30, 0x30, 0x30, 0x60, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 40 '('
0x00, 0x00, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00,
// Character 41 ')'
0x00, 0x00, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x0C,
0x0C, 0x0C, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00,
// Character 42 '*'
0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x3C, 0xFF,
0x3C, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 43 '+'
0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7E,
0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 44 ','
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x18, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00,
// Character 45 '-'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 46 '.'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
// Character 47 '/'
0x00, 0x00, 0x00, 0x00, 0x02, 0x06, 0x0C, 0x18,
0x30, 0x60, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00,
// Character 48 '0'
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xCE, 0xDE, 0xF6,
0xE6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 49 '1'
0x00, 0x00, 0x18, 0x38, 0x78, 0x18, 0x18, 0x18,
0x18, 0x18, 0x18, 0x7E, 0x00, 0x00, 0x00, 0x00,
// Character 50 '2'
0x00, 0x00, 0x7C, 0xC6, 0x06, 0x0C, 0x18, 0x30,
0x60, 0xC0, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00,
// Character 51 '3'
0x00, 0x00, 0x7C, 0xC6, 0x06, 0x06, 0x3C, 0x06,
0x06, 0x06, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 52 '4'
0x00, 0x00, 0x0C, 0x1C, 0x3C, 0x6C, 0xCC, 0xFE,
0x0C, 0x0C, 0x0C, 0x1E, 0x00, 0x00, 0x00, 0x00,
// Character 53 '5'
0x00, 0x00, 0xFE, 0xC0, 0xC0, 0xC0, 0xFC, 0x06,
0x06, 0x06, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 54 '6'
0x00, 0x00, 0x38, 0x60, 0xC0, 0xC0, 0xFC, 0xC6,
0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 55 '7'
0x00, 0x00, 0xFE, 0xC6, 0x06, 0x06, 0x0C, 0x18,
0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00,
// Character 56 '8'
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0x7C, 0xC6,
0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 57 '9'
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0x7E, 0x06,
0x06, 0x06, 0x0C, 0x78, 0x00, 0x00, 0x00, 0x00,
// Character 58 ':'
0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00,
0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 59 ';'
0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00,
0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00,
// Character 60 '<'
0x00, 0x00, 0x00, 0x06, 0x0C, 0x18, 0x30, 0x60,
0x30, 0x18, 0x0C, 0x06, 0x00, 0x00, 0x00, 0x00,
// Character 61 '='
0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00,
0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 62 '>'
0x00, 0x00, 0x00, 0x60, 0x30, 0x18, 0x0C, 0x06,
0x0C, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00,
// Character 63 '?'
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18,
0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
// Character 64 '@'
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xDE, 0xDE,
0xDE, 0xDC, 0xC0, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 65 'A'
0x00, 0x00, 0x10, 0x38, 0x6C, 0xC6, 0xC6, 0xFE,
0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00,
// Character 66 'B'
0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x66,
0x66, 0x66, 0x66, 0xFC, 0x00, 0x00, 0x00, 0x00,
// Character 67 'C'
0x00, 0x00, 0x3C, 0x66, 0xC2, 0xC0, 0xC0, 0xC0,
0xC0, 0xC2, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
// Character 68 'D'
0x00, 0x00, 0xF8, 0x6C, 0x66, 0x66, 0x66, 0x66,
0x66, 0x66, 0x6C, 0xF8, 0x00, 0x00, 0x00, 0x00,
// Character 69 'E'
0x00, 0x00, 0xFE, 0x66, 0x62, 0x68, 0x78, 0x68,
0x60, 0x62, 0x66, 0xFE, 0x00, 0x00, 0x00, 0x00,
// Character 70 'F'
0x00, 0x00, 0xFE, 0x66, 0x62, 0x68, 0x78, 0x68,
0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00, 0x00,
// Character 71 'G'
0x00, 0x00, 0x3C, 0x66, 0xC2, 0xC0, 0xC0, 0xDE,
0xC6, 0xC6, 0x66, 0x3A, 0x00, 0x00, 0x00, 0x00,
// Character 72 'H'
0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xFE, 0xC6,
0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00,
// Character 73 'I'
0x00, 0x00, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18,
0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
// Character 74 'J'
0x00, 0x00, 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
0xCC, 0xCC, 0xCC, 0x78, 0x00, 0x00, 0x00, 0x00,
// Character 75 'K'
0x00, 0x00, 0xE6, 0x66, 0x66, 0x6C, 0x78, 0x78,
0x6C, 0x66, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00,
// Character 76 'L'
0x00, 0x00, 0xF0, 0x60, 0x60, 0x60, 0x60, 0x60,
0x60, 0x62, 0x66, 0xFE, 0x00, 0x00, 0x00, 0x00,
// Character 77 'M'
0x00, 0x00, 0xC6, 0xEE, 0xFE, 0xFE, 0xD6, 0xC6,
0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00,
// Character 78 'N'
0x00, 0x00, 0xC6, 0xE6, 0xF6, 0xFE, 0xDE, 0xCE,
0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00,
// Character 79 'O'
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6,
0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 80 'P'
0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x60,
0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00, 0x00,
// Character 81 'Q'
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6,
0xC6, 0xD6, 0xDE, 0x7C, 0x0C, 0x0E, 0x00, 0x00,
// Character 82 'R'
0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x6C,
0x66, 0x66, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00,
// Character 83 'S'
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x60, 0x38, 0x0C,
0x06, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 84 'T'
0x00, 0x00, 0xFF, 0xDB, 0x99, 0x18, 0x18, 0x18,
0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
// Character 85 'U'
0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6,
0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 86 'V'
0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6,
0xC6, 0x6C, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00,
// Character 87 'W'
0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xD6, 0xD6,
0xD6, 0xFE, 0xEE, 0x6C, 0x00, 0x00, 0x00, 0x00,
// Character 88 'X'
0x00, 0x00, 0xC6, 0xC6, 0x6C, 0x7C, 0x38, 0x38,
0x7C, 0x6C, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00,
// Character 89 'Y'
0x00, 0x00, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x18,
0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
// Character 90 'Z'
0x00, 0x00, 0xFE, 0xC6, 0x86, 0x0C, 0x18, 0x30,
0x60, 0xC2, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00,
// Character 91 '['
0x00, 0x00, 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x3C, 0x00, 0x00, 0x00, 0x00,
// Character 92 '\'
0x00, 0x00, 0x00, 0x80, 0xC0, 0xE0, 0x70, 0x38,
0x1C, 0x0E, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00,
// Character 93 ']'
0x00, 0x00, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
0x0C, 0x0C, 0x0C, 0x3C, 0x00, 0x00, 0x00, 0x00,
// Character 94 '^'
0x10, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 95 '_'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
// Character 96 '`'
0x30, 0x30, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 97 'a'
0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x0C, 0x7C,
0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00,
// Character 98 'b'
0x00, 0x00, 0xE0, 0x60, 0x60, 0x78, 0x6C, 0x66,
0x66, 0x66, 0x66, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 99 'c'
0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC0,
0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 100 'd'
0x00, 0x00, 0x1C, 0x0C, 0x0C, 0x3C, 0x6C, 0xCC,
0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00,
// Character 101 'e'
0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xFE,
0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 102 'f'
0x00, 0x00, 0x38, 0x6C, 0x64, 0x60, 0xF0, 0x60,
0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00, 0x00,
// Character 103 'g'
0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xCC, 0xCC,
0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0xCC, 0x78, 0x00,
// Character 104 'h'
0x00, 0x00, 0xE0, 0x60, 0x60, 0x6C, 0x76, 0x66,
0x66, 0x66, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00,
// Character 105 'i'
0x00, 0x00, 0x18, 0x18, 0x00, 0x38, 0x18, 0x18,
0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
// Character 106 'j'
0x00, 0x00, 0x06, 0x06, 0x00, 0x0E, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3C, 0x00,
// Character 107 'k'
0x00, 0x00, 0xE0, 0x60, 0x60, 0x66, 0x6C, 0x78,
0x78, 0x6C, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00,
// Character 108 'l'
0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18,
0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
// Character 109 'm'
0x00, 0x00, 0x00, 0x00, 0x00, 0xEC, 0xFE, 0xD6,
0xD6, 0xD6, 0xD6, 0xC6, 0x00, 0x00, 0x00, 0x00,
// Character 110 'n'
0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x66, 0x66,
0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
// Character 111 'o'
0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC6,
0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 112 'p'
0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x66, 0x66,
0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xF0, 0x00,
// Character 113 'q'
0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xCC, 0xCC,
0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0x0C, 0x1E, 0x00,
// Character 114 'r'
0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x76, 0x66,
0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00, 0x00,
// Character 115 's'
0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0x60,
0x38, 0x0C, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 116 't'
0x00, 0x00, 0x10, 0x30, 0x30, 0xFC, 0x30, 0x30,
0x30, 0x30, 0x36, 0x1C, 0x00, 0x00, 0x00, 0x00,
// Character 117 'u'
0x00, 0x00, 0x00, 0x00, 0x00, 0xCC, 0xCC, 0xCC,
0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00,
// Character 118 'v'
0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xC6,
0xC6, 0x6C, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00,
// Character 119 'w'
0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xD6,
0xD6, 0xD6, 0xFE, 0x6C, 0x00, 0x00, 0x00, 0x00,
// Character 120 'x'
0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0x6C, 0x38,
0x38, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00,
// Character 121 'y'
0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xC6,
0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x0C, 0xF8, 0x00,
// Character 122 'z'
0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xCC, 0x18,
0x30, 0x60, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00,
// Character 123 '{'
0x00, 0x00, 0x0E, 0x18, 0x18, 0x18, 0x70, 0x18,
0x18, 0x18, 0x18, 0x0E, 0x00, 0x00, 0x00, 0x00,
// Character 124 '|'
0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18,
0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
// Character 125 '}'
0x00, 0x00, 0x70, 0x18, 0x18, 0x18, 0x0E, 0x18,
0x18, 0x18, 0x18, 0x70, 0x00, 0x00, 0x00, 0x00,
// Character 126 '~'
0x00, 0x00, 0x76, 0xDC, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 127 (DEL - block)
0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x6C, 0xC6,
0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 128 (C cedilla)
0x00, 0x00, 0x3C, 0x66, 0xC2, 0xC0, 0xC0, 0xC0,
0xC2, 0x66, 0x3C, 0x0C, 0x06, 0x7C, 0x00, 0x00,
// Character 129 (u umlaut)
0x00, 0x00, 0xCC, 0x00, 0x00, 0xCC, 0xCC, 0xCC,
0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00,
// Character 130 (e acute)
0x00, 0x0C, 0x18, 0x30, 0x00, 0x7C, 0xC6, 0xFE,
0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 131 (a circumflex)
0x00, 0x10, 0x38, 0x6C, 0x00, 0x78, 0x0C, 0x7C,
0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00,
// Character 132 (a umlaut)
0x00, 0x00, 0xCC, 0x00, 0x00, 0x78, 0x0C, 0x7C,
0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00,
// Character 133 (a grave)
0x00, 0x60, 0x30, 0x18, 0x00, 0x78, 0x0C, 0x7C,
0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00,
// Character 134 (a ring)
0x00, 0x38, 0x6C, 0x38, 0x00, 0x78, 0x0C, 0x7C,
0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00,
// Character 135 (c cedilla)
0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC0,
0xC0, 0xC6, 0x7C, 0x0C, 0x06, 0x7C, 0x00, 0x00,
// Character 136 (e circumflex)
0x00, 0x10, 0x38, 0x6C, 0x00, 0x7C, 0xC6, 0xFE,
0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 137 (e umlaut)
0x00, 0x00, 0xC6, 0x00, 0x00, 0x7C, 0xC6, 0xFE,
0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 138 (e grave)
0x00, 0x60, 0x30, 0x18, 0x00, 0x7C, 0xC6, 0xFE,
0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 139 (i umlaut)
0x00, 0x00, 0x66, 0x00, 0x00, 0x38, 0x18, 0x18,
0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
// Character 140 (i circumflex)
0x00, 0x18, 0x3C, 0x66, 0x00, 0x38, 0x18, 0x18,
0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
// Character 141 (i grave)
0x00, 0x60, 0x30, 0x18, 0x00, 0x38, 0x18, 0x18,
0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
// Character 142 (A umlaut)
0x00, 0xC6, 0x00, 0x10, 0x38, 0x6C, 0xC6, 0xC6,
0xFE, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00,
// Character 143 (A ring)
0x38, 0x6C, 0x38, 0x10, 0x38, 0x6C, 0xC6, 0xC6,
0xFE, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00,
// Character 144 (E acute)
0x0C, 0x18, 0x00, 0xFE, 0x66, 0x62, 0x68, 0x78,
0x68, 0x62, 0x66, 0xFE, 0x00, 0x00, 0x00, 0x00,
// Character 145 (ae)
0x00, 0x00, 0x00, 0x00, 0x00, 0x6E, 0x3B, 0x1B,
0x7E, 0xD8, 0xDC, 0x77, 0x00, 0x00, 0x00, 0x00,
// Character 146 (AE)
0x00, 0x00, 0x3E, 0x6C, 0xCC, 0xCC, 0xFE, 0xCC,
0xCC, 0xCC, 0xCC, 0xCE, 0x00, 0x00, 0x00, 0x00,
// Character 147 (o circumflex)
0x00, 0x10, 0x38, 0x6C, 0x00, 0x7C, 0xC6, 0xC6,
0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 148 (o umlaut)
0x00, 0x00, 0xC6, 0x00, 0x00, 0x7C, 0xC6, 0xC6,
0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 149 (o grave)
0x00, 0x60, 0x30, 0x18, 0x00, 0x7C, 0xC6, 0xC6,
0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 150 (u circumflex)
0x00, 0x30, 0x78, 0xCC, 0x00, 0xCC, 0xCC, 0xCC,
0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00,
// Character 151 (u grave)
0x00, 0x60, 0x30, 0x18, 0x00, 0xCC, 0xCC, 0xCC,
0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00,
// Character 152 (y umlaut)
0x00, 0x00, 0xC6, 0x00, 0x00, 0xC6, 0xC6, 0xC6,
0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x0C, 0x78, 0x00,
// Character 153 (O umlaut)
0x00, 0xC6, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6,
0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 154 (U umlaut)
0x00, 0xC6, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6,
0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 155 (cent)
0x00, 0x18, 0x18, 0x7C, 0xC6, 0xC0, 0xC0, 0xC0,
0xC6, 0x7C, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
// Character 156 (pound)
0x00, 0x38, 0x6C, 0x64, 0x60, 0xF0, 0x60, 0x60,
0x60, 0x60, 0xE6, 0xFC, 0x00, 0x00, 0x00, 0x00,
// Character 157 (yen)
0x00, 0x00, 0xC6, 0x6C, 0x38, 0x38, 0xFE, 0x38,
0xFE, 0x38, 0x38, 0x38, 0x00, 0x00, 0x00, 0x00,
// Character 158 (Pt)
0x00, 0xF8, 0xCC, 0xCC, 0xF8, 0xC4, 0xCC, 0xDE,
0xCC, 0xCC, 0xCC, 0xC6, 0x00, 0x00, 0x00, 0x00,
// Character 159 (f hook)
0x00, 0x0E, 0x1B, 0x18, 0x18, 0x18, 0x7E, 0x18,
0x18, 0x18, 0xD8, 0x70, 0x00, 0x00, 0x00, 0x00,
// Character 160 (a acute)
0x00, 0x18, 0x30, 0x60, 0x00, 0x78, 0x0C, 0x7C,
0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00,
// Character 161 (i acute)
0x00, 0x0C, 0x18, 0x30, 0x00, 0x38, 0x18, 0x18,
0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
// Character 162 (o acute)
0x00, 0x18, 0x30, 0x60, 0x00, 0x7C, 0xC6, 0xC6,
0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 163 (u acute)
0x00, 0x18, 0x30, 0x60, 0x00, 0xCC, 0xCC, 0xCC,
0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00,
// Character 164 (n tilde)
0x00, 0x00, 0x76, 0xDC, 0x00, 0xDC, 0x66, 0x66,
0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
// Character 165 (N tilde)
0x76, 0xDC, 0x00, 0xC6, 0xE6, 0xF6, 0xFE, 0xDE,
0xCE, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00,
// Character 166 (feminine ordinal)
0x00, 0x3C, 0x6C, 0x6C, 0x3E, 0x00, 0x7E, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 167 (masculine ordinal)
0x00, 0x38, 0x6C, 0x6C, 0x38, 0x00, 0x7C, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 168 (inverted question)
0x00, 0x00, 0x30, 0x30, 0x00, 0x30, 0x30, 0x60,
0xC0, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
// Character 169 (not left)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xC0,
0xC0, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 170 (not right)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x06,
0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 171 (fraction 1/2)
0x00, 0xC0, 0xC0, 0xC2, 0xC6, 0xCC, 0x18, 0x30,
0x60, 0xDC, 0x86, 0x0C, 0x18, 0x3E, 0x00, 0x00,
// Character 172 (fraction 1/4)
0x00, 0xC0, 0xC0, 0xC2, 0xC6, 0xCC, 0x18, 0x30,
0x66, 0xCE, 0x9E, 0x3E, 0x06, 0x06, 0x00, 0x00,
// Character 173 (inverted exclamation)
0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18,
0x3C, 0x3C, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00,
// Character 174 (double left angle)
0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x6C, 0xD8,
0x6C, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 175 (double right angle)
0x00, 0x00, 0x00, 0x00, 0x00, 0xD8, 0x6C, 0x36,
0x6C, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 176 (light shade)
0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44,
0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44,
// Character 177 (medium shade)
0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA,
0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA,
// Character 178 (dark shade)
0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77,
0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77,
// Character 179 (box vertical)
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Character 180 (box vertical-left)
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xF8,
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Character 181 (box double vertical-left single)
0x18, 0x18, 0x18, 0x18, 0x18, 0xF8, 0x18, 0xF8,
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Character 182 (box double vertical-left)
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xF6,
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Character 183 (box double horizontal-down)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE,
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Character 184 (box double vertical-left single)
0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x18, 0xF8,
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Character 185 (box double vertical-left double)
0x36, 0x36, 0x36, 0x36, 0x36, 0xF6, 0x06, 0xF6,
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Character 186 (box double vertical)
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Character 187 (box double down-left)
0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x06, 0xF6,
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Character 188 (box double up-left)
0x36, 0x36, 0x36, 0x36, 0x36, 0xF6, 0x06, 0xFE,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 189 (box double up-right)
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xFE,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 190 (box horizontal-down)
0x18, 0x18, 0x18, 0x18, 0x18, 0xF8, 0x18, 0xF8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 191 (box down-left)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8,
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Character 192 (box up-right)
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 193 (box horizontal-up)
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 194 (box horizontal-down)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Character 195 (box vertical-right)
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1F,
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Character 196 (box horizontal)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 197 (box cross)
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xFF,
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Character 198 (box single vert-right double)
0x18, 0x18, 0x18, 0x18, 0x18, 0x1F, 0x18, 0x1F,
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Character 199 (box double vert-right)
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x37,
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Character 200 (box double up-right)
0x36, 0x36, 0x36, 0x36, 0x36, 0x37, 0x30, 0x3F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 201 (box double down-right)
0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x30, 0x37,
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Character 202 (box double horizontal-up)
0x36, 0x36, 0x36, 0x36, 0x36, 0xF7, 0x00, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 203 (box double horizontal-down)
0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xF7,
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Character 204 (box double vertical-right)
0x36, 0x36, 0x36, 0x36, 0x36, 0x37, 0x30, 0x37,
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Character 205 (box double horizontal)
0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 206 (box double cross)
0x36, 0x36, 0x36, 0x36, 0x36, 0xF7, 0x00, 0xF7,
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Character 207 (box single horiz-up double)
0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, 0x00, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 208 (box double horizontal-up single)
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 209 (box single horiz-down double)
0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF,
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Character 210 (box double horizontal-down single)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Character 211 (box double up-right single)
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x3F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 212 (box single up-right double)
0x18, 0x18, 0x18, 0x18, 0x18, 0x1F, 0x18, 0x1F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 213 (box single down-right double)
0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x18, 0x1F,
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Character 214 (box double down-right single)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F,
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Character 215 (box double cross single)
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xFF,
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Character 216 (box single cross double)
0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0xFF,
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Character 217 (box up-left)
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xF8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 218 (box down-right)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F,
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Character 219 (full block)
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// Character 220 (bottom half block)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// Character 221 (left half block)
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
// Character 222 (right half block)
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
// Character 223 (top half block)
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 224 (alpha)
0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xDC, 0xD8,
0xD8, 0xD8, 0xDC, 0x76, 0x00, 0x00, 0x00, 0x00,
// Character 225 (beta/sharp s)
0x00, 0x00, 0x78, 0xCC, 0xCC, 0xCC, 0xD8, 0xCC,
0xC6, 0xC6, 0xC6, 0xCC, 0x00, 0x00, 0x00, 0x00,
// Character 226 (gamma)
0x00, 0x00, 0xFE, 0xC6, 0xC6, 0xC0, 0xC0, 0xC0,
0xC0, 0xC0, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00,
// Character 227 (pi)
0x00, 0x00, 0x00, 0x00, 0xFE, 0x6C, 0x6C, 0x6C,
0x6C, 0x6C, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00,
// Character 228 (sigma uppercase)
0x00, 0x00, 0x00, 0xFE, 0xC6, 0x60, 0x30, 0x18,
0x30, 0x60, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00,
// Character 229 (sigma lowercase)
0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0xD8, 0xD8,
0xD8, 0xD8, 0xD8, 0x70, 0x00, 0x00, 0x00, 0x00,
// Character 230 (mu)
0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66,
0x66, 0x7C, 0x60, 0x60, 0xC0, 0x00, 0x00, 0x00,
// Character 231 (tau)
0x00, 0x00, 0x00, 0x00, 0x76, 0xDC, 0x18, 0x18,
0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
// Character 232 (phi uppercase)
0x00, 0x00, 0x00, 0x7E, 0x18, 0x3C, 0x66, 0x66,
0x66, 0x3C, 0x18, 0x7E, 0x00, 0x00, 0x00, 0x00,
// Character 233 (theta)
0x00, 0x00, 0x00, 0x38, 0x6C, 0xC6, 0xC6, 0xFE,
0xC6, 0xC6, 0x6C, 0x38, 0x00, 0x00, 0x00, 0x00,
// Character 234 (omega)
0x00, 0x00, 0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0xC6,
0x6C, 0x6C, 0x6C, 0xEE, 0x00, 0x00, 0x00, 0x00,
// Character 235 (delta)
0x00, 0x00, 0x1E, 0x30, 0x18, 0x0C, 0x3E, 0x66,
0x66, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
// Character 236 (infinity)
0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xDB, 0xDB,
0xDB, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 237 (phi lowercase)
0x00, 0x00, 0x00, 0x02, 0x06, 0x7C, 0xCE, 0xDE,
0xF6, 0xE6, 0x7C, 0xC0, 0x80, 0x00, 0x00, 0x00,
// Character 238 (epsilon)
0x00, 0x00, 0x1C, 0x30, 0x60, 0x60, 0x7C, 0x60,
0x60, 0x60, 0x30, 0x1C, 0x00, 0x00, 0x00, 0x00,
// Character 239 (intersection)
0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6,
0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00,
// Character 240 (triple bar)
0x00, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0xFE,
0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 241 (plus-minus)
0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7E, 0x18,
0x18, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00,
// Character 242 (greater-equal)
0x00, 0x00, 0x00, 0x30, 0x18, 0x0C, 0x06, 0x0C,
0x18, 0x30, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00,
// Character 243 (less-equal)
0x00, 0x00, 0x00, 0x0C, 0x18, 0x30, 0x60, 0x30,
0x18, 0x0C, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00,
// Character 244 (top integral)
0x00, 0x00, 0x0E, 0x1B, 0x1B, 0x18, 0x18, 0x18,
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Character 245 (bottom integral)
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
0x18, 0xD8, 0xD8, 0xD8, 0x70, 0x00, 0x00, 0x00,
// Character 246 (division)
0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x7E,
0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 247 (approximately equal)
0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xDC, 0x00,
0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 248 (degree)
0x00, 0x38, 0x6C, 0x6C, 0x38, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 249 (bullet operator)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18,
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 250 (middle dot)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 251 (square root)
0x00, 0x0F, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0xEC,
0x6C, 0x6C, 0x3C, 0x1C, 0x00, 0x00, 0x00, 0x00,
// Character 252 (superscript n)
0x00, 0xD8, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 253 (superscript 2)
0x00, 0x70, 0xD8, 0x30, 0x60, 0xC8, 0xF8, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 254 (filled square)
0x00, 0x00, 0x00, 0x00, 0x7C, 0x7C, 0x7C, 0x7C,
0x7C, 0x7C, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00,
// Character 255 (non-breaking space)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
} // namespace gui
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
+4 -1
View File
@@ -153,8 +153,11 @@ extern "C" void _start() {
// ---- Stage 1: Network configuration ---- // ---- Stage 1: Network configuration ----
run_service("0:/os/dhcp.elf", "dhcp"); run_service("0:/os/dhcp.elf", "dhcp");
// ---- Stage 2: Interactive shell ---- // ---- Stage 2: Desktop environment (falls back to shell) ----
if (!run_service("0:/os/desktop.elf", "desktop")) {
log_warn("Desktop failed, falling back to shell");
run_service("0:/os/shell.elf", "shell"); run_service("0:/os/shell.elf", "shell");
}
log_warn("All services exited"); log_warn("All services exited");
BIN
View File
Binary file not shown.
+48
View File
@@ -0,0 +1,48 @@
#!/bin/bash
# copy_icons.sh - Copy selected SVG icons for the desktop environment
# Usage: ./scripts/copy_icons.sh (from project root or programs/ directory)
set -e
# Find project root relative to this script's location
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
ICON_SRC="$PROJECT_ROOT/programs/gui/icons/Flat-Remix-Blue-Light-darkPanel"
ICON_DST="$PROJECT_ROOT/programs/bin/icons"
mkdir -p "$ICON_DST"
# Selected icons for the desktop
ICONS=(
"actions/symbolic/view-app-grid-symbolic.svg"
"actions/symbolic/window-close-symbolic.svg"
"actions/symbolic/window-maximize-symbolic.svg"
"actions/symbolic/window-minimize-symbolic.svg"
"apps/symbolic/utilities-terminal-symbolic.svg"
"apps/symbolic/system-file-manager-symbolic.svg"
"apps/symbolic/preferences-desktop-apps-symbolic.svg"
"places/symbolic/folder-symbolic.svg"
"places/symbolic/folder-documents-symbolic.svg"
"places/symbolic/user-home-symbolic.svg"
"mimetypes/symbolic/text-x-generic-symbolic.svg"
"mimetypes/symbolic/application-x-executable-symbolic.svg"
"devices/symbolic/computer-symbolic.svg"
"devices/symbolic/network-wired-symbolic.svg"
)
copied=0
for icon in "${ICONS[@]}"; do
src="$ICON_SRC/$icon"
# Extract just the filename
name=$(basename "$icon")
dst="$ICON_DST/$name"
if [ -f "$src" ]; then
cp "$src" "$dst"
copied=$((copied + 1))
else
echo "copy_icons: warning: $src not found" >&2
fi
done
echo "copy_icons: copied $copied icons to $ICON_DST"