diff --git a/.gitignore b/.gitignore index 6f09e53..5f392a9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,6 @@ toolchain/build/ toolchain/src/ programs/bin/ programs/obj/ -programs/src/doom/obj/ +programs/src/*/obj/ +programs/lib/libc/liblibc.a programs/gui/icons/ -programs/src/desktop/obj/ diff --git a/kernel/src/Api/Syscall.cpp b/kernel/src/Api/Syscall.cpp index 73df553..15012f3 100644 --- a/kernel/src/Api/Syscall.cpp +++ b/kernel/src/Api/Syscall.cpp @@ -29,6 +29,12 @@ #include #include #include "../Libraries/flanterm/src/flanterm.h" +#include "WinServer.hpp" +#include +#include +#include +#include +#include // Assembly entry point extern "C" void SyscallEntry(); @@ -605,6 +611,228 @@ namespace Zenith { return 0; } + // ---- Process listing / kill ---- + + static int Sys_ProcList(ProcInfo* buf, int maxCount) { + if (buf == nullptr || maxCount <= 0) return 0; + int count = 0; + for (int i = 0; i < Sched::MaxProcesses && count < maxCount; i++) { + auto* proc = Sched::GetProcessSlot(i); + if (!proc || proc->state == Sched::ProcessState::Free) continue; + + buf[count].pid = (int32_t)proc->pid; + buf[count].parentPid = (int32_t)proc->parentPid; + buf[count].state = (uint8_t)proc->state; + buf[count]._pad[0] = 0; + buf[count]._pad[1] = 0; + buf[count]._pad[2] = 0; + { + int j = 0; + for (; j < 63 && proc->name[j]; j++) + buf[count].name[j] = proc->name[j]; + buf[count].name[j] = '\0'; + } + buf[count].heapUsed = (proc->heapNext > Sched::UserHeapBase) + ? proc->heapNext - Sched::UserHeapBase : 0; + count++; + } + return count; + } + + static int Sys_Kill(int pid) { + // Refuse to kill PID 0 (init) + if (pid == 0) return -1; + // Refuse to kill the caller's own process + if (pid == Sched::GetCurrentPid()) return -1; + + auto* proc = Sched::GetProcessByPid(pid); + if (!proc) return -1; + + // Clean up any windows owned by this process + WinServer::CleanupProcess(pid); + + proc->state = Sched::ProcessState::Terminated; + return 0; + } + + // ---- Device list ---- + + static void dl_strcpy(char* dst, const char* src, int max) { + int i = 0; + for (; i < max - 1 && src[i]; i++) dst[i] = src[i]; + dst[i] = '\0'; + } + + static int dl_append(char* dst, int pos, const char* src, int max) { + for (int i = 0; src[i] && pos < max - 1; i++) dst[pos++] = src[i]; + dst[pos] = '\0'; + return pos; + } + + static int dl_append_hex(char* dst, int pos, unsigned val, int digits, int max) { + const char* hex = "0123456789abcdef"; + char tmp[8]; + for (int i = digits - 1; i >= 0; i--) { tmp[i] = hex[val & 0xF]; val >>= 4; } + for (int i = 0; i < digits && pos < max - 1; i++) dst[pos++] = tmp[i]; + dst[pos] = '\0'; + return pos; + } + + static int dl_append_dec(char* dst, int pos, int val, int max) { + if (val == 0) { if (pos < max - 1) dst[pos++] = '0'; dst[pos] = '\0'; return pos; } + char tmp[12]; int i = 0; + while (val > 0) { tmp[i++] = '0' + (val % 10); val /= 10; } + while (i > 0 && pos < max - 1) dst[pos++] = tmp[--i]; + dst[pos] = '\0'; + return pos; + } + + static int Sys_DevList(DevInfo* buf, int maxCount) { + if (buf == nullptr || maxCount <= 0) return 0; + int count = 0; + + auto add = [&](uint8_t cat, const char* name, const char* detail) { + if (count >= maxCount) return; + buf[count].category = cat; + buf[count]._pad[0] = 0; buf[count]._pad[1] = 0; buf[count]._pad[2] = 0; + dl_strcpy(buf[count].name, name, 48); + dl_strcpy(buf[count].detail, detail, 48); + count++; + }; + + // CPU cores (category 0) + int cpuCount = Hal::GetDetectedCpuCount(); + if (cpuCount > 0) { + char detail[48]; + int p = 0; + p = dl_append(detail, p, "x86_64, ", 48); + p = dl_append_dec(detail, p, cpuCount, 48); + p = dl_append(detail, p, " core(s)", 48); + add(0, "Processor", detail); + } + + // Interrupt controllers (category 1) + add(1, "Local APIC", "Per-CPU interrupt controller"); + add(1, "I/O APIC", "System interrupt router"); + + // Timer (category 2) + add(2, "LAPIC Timer", "Local APIC periodic timer"); + + // PS/2 Input (category 3) + add(3, "PS/2 Keyboard", "IRQ 1, scan code set 1"); + if (Drivers::PS2::IsDualChannel()) { + add(3, "PS/2 Mouse", "IRQ 12, dual-channel 8042"); + } + + // USB devices (category 4) + if (Drivers::USB::Xhci::IsInitialized()) { + for (uint8_t slot = 1; slot <= Drivers::USB::Xhci::MAX_SLOTS && count < maxCount; slot++) { + auto* dev = Drivers::USB::Xhci::GetDevice(slot); + if (!dev || !dev->Active) continue; + const char* devName = "USB Device"; + if (dev->InterfaceClass == 3) { + if (dev->InterfaceProtocol == 1) devName = "USB HID Keyboard"; + else if (dev->InterfaceProtocol == 2) devName = "USB HID Mouse"; + else devName = "USB HID Device"; + } else if (dev->InterfaceClass == 8) { + devName = "USB Mass Storage"; + } else if (dev->InterfaceClass == 9) { + devName = "USB Hub"; + } + char detail[48]; + int p = 0; + p = dl_append(detail, p, "Port ", 48); + p = dl_append_dec(detail, p, dev->PortId, 48); + p = dl_append(detail, p, ", VID:", 48); + p = dl_append_hex(detail, p, dev->VendorId, 4, 48); + p = dl_append(detail, p, " PID:", 48); + p = dl_append_hex(detail, p, dev->ProductId, 4, 48); + add(4, devName, detail); + } + } + + // Network (category 5) + if (Drivers::Net::E1000::IsInitialized()) { + add(5, "Intel E1000", "Gigabit Ethernet (82540EM)"); + } + if (Drivers::Net::E1000E::IsInitialized()) { + add(5, "Intel E1000E", "Gigabit Ethernet (82574L)"); + } + + // Display (category 6) + if (Drivers::Graphics::IntelGPU::IsInitialized()) { + auto* gpu = Drivers::Graphics::IntelGPU::GetGpuInfo(); + if (gpu) { + add(6, gpu->name, "Intel Integrated Graphics"); + } + } + + // PCI devices (category 7) + auto& pciDevs = Pci::GetDevices(); + for (int i = 0; i < (int)pciDevs.size() && count < maxCount; i++) { + auto& d = pciDevs[i]; + const char* className = Pci::GetClassName(d.ClassCode, d.SubClass); + char detail[48]; + int p = 0; + p = dl_append_hex(detail, p, d.Bus, 2, 48); + p = dl_append(detail, p, ":", 48); + p = dl_append_hex(detail, p, d.Device, 2, 48); + p = dl_append(detail, p, ".", 48); + p = dl_append_dec(detail, p, d.Function, 48); + p = dl_append(detail, p, " ", 48); + p = dl_append_hex(detail, p, d.VendorId, 4, 48); + p = dl_append(detail, p, ":", 48); + p = dl_append_hex(detail, p, d.DeviceId, 4, 48); + add(7, className, detail); + } + + return count; + } + + // ---- Window server syscalls ---- + + static int Sys_WinCreate(const char* title, int w, int h, WinCreateResult* result) { + if (result == nullptr || title == nullptr) return -1; + auto* proc = Sched::GetCurrentProcessPtr(); + if (proc == nullptr) return -1; + + uint64_t outVa = 0; + int id = WinServer::Create(proc->pid, proc->pml4Phys, title, w, h, + proc->heapNext, outVa); + result->id = id; + result->pixelVa = (id >= 0) ? outVa : 0; + return id >= 0 ? 0 : -1; + } + + static int Sys_WinDestroy(int windowId) { + return WinServer::Destroy(windowId, Sched::GetCurrentPid()); + } + + static int Sys_WinPresent(int windowId) { + return WinServer::Present(windowId, Sched::GetCurrentPid()); + } + + static int Sys_WinPoll(int windowId, WinEvent* outEvent) { + if (outEvent == nullptr) return -1; + return WinServer::Poll(windowId, Sched::GetCurrentPid(), outEvent); + } + + static int Sys_WinEnum(WinInfo* outArray, int maxCount) { + if (outArray == nullptr || maxCount <= 0) return 0; + return WinServer::Enumerate(outArray, maxCount); + } + + static uint64_t Sys_WinMap(int windowId) { + auto* proc = Sched::GetCurrentProcessPtr(); + if (proc == nullptr) return 0; + return WinServer::Map(windowId, proc->pid, proc->pml4Phys, proc->heapNext); + } + + static int Sys_WinSendEvent(int windowId, const WinEvent* event) { + if (event == nullptr) return -1; + return WinServer::SendEvent(windowId, event); + } + // ---- Dispatch ---- extern "C" int64_t SyscallDispatch(SyscallFrame* frame) { @@ -743,6 +971,27 @@ namespace Zenith { 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); + case SYS_WINCREATE: + return (int64_t)Sys_WinCreate((const char*)frame->arg1, (int)frame->arg2, + (int)frame->arg3, (WinCreateResult*)frame->arg4); + case SYS_WINDESTROY: + return (int64_t)Sys_WinDestroy((int)frame->arg1); + case SYS_WINPRESENT: + return (int64_t)Sys_WinPresent((int)frame->arg1); + case SYS_WINPOLL: + return (int64_t)Sys_WinPoll((int)frame->arg1, (WinEvent*)frame->arg2); + case SYS_WINENUM: + return (int64_t)Sys_WinEnum((WinInfo*)frame->arg1, (int)frame->arg2); + case SYS_WINMAP: + return (int64_t)Sys_WinMap((int)frame->arg1); + case SYS_WINSENDEVENT: + return (int64_t)Sys_WinSendEvent((int)frame->arg1, (const WinEvent*)frame->arg2); + case SYS_PROCLIST: + return (int64_t)Sys_ProcList((ProcInfo*)frame->arg1, (int)frame->arg2); + case SYS_KILL: + return (int64_t)Sys_Kill((int)frame->arg1); + case SYS_DEVLIST: + return (int64_t)Sys_DevList((DevInfo*)frame->arg1, (int)frame->arg2); default: return -1; } @@ -769,7 +1018,7 @@ namespace Zenith { Hal::WriteMSR(Hal::IA32_FMASK, 0x200); Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR=" - << kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 53 syscalls)"; + << kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 64 syscalls)"; } } diff --git a/kernel/src/Api/Syscall.hpp b/kernel/src/Api/Syscall.hpp index 13e9026..17eea57 100644 --- a/kernel/src/Api/Syscall.hpp +++ b/kernel/src/Api/Syscall.hpp @@ -66,6 +66,20 @@ namespace Zenith { static constexpr uint64_t SYS_CHILDIO_WRITEKEY = 52; static constexpr uint64_t SYS_CHILDIO_SETTERMSZ = 53; + // Window server syscalls + static constexpr uint64_t SYS_WINCREATE = 54; + static constexpr uint64_t SYS_WINDESTROY = 55; + static constexpr uint64_t SYS_WINPRESENT = 56; + static constexpr uint64_t SYS_WINPOLL = 57; + static constexpr uint64_t SYS_WINENUM = 58; + static constexpr uint64_t SYS_WINMAP = 59; + static constexpr uint64_t SYS_WINSENDEVENT = 60; + + // Process management syscalls + static constexpr uint64_t SYS_PROCLIST = 61; + static constexpr uint64_t SYS_KILL = 62; + static constexpr uint64_t SYS_DEVLIST = 63; + static constexpr int SOCK_TCP = 1; static constexpr int SOCK_UDP = 2; @@ -118,6 +132,48 @@ namespace Zenith { uint8_t buttons; }; + // Window server shared types + struct WinEvent { + uint8_t type; // 0=key, 1=mouse, 2=resize, 3=close + uint8_t _pad[3]; + union { + KeyEvent key; + struct { int32_t x, y, scroll; uint8_t buttons, prev_buttons; } mouse; + struct { int32_t w, h; } resize; + }; + }; + + struct WinInfo { + int32_t id; + int32_t ownerPid; + char title[64]; + int32_t width, height; + uint8_t dirty; + uint8_t _pad[3]; + }; + + struct WinCreateResult { + int32_t id; // -1 on failure + uint32_t _pad; + uint64_t pixelVa; // VA of pixel buffer in caller's address space + }; + + struct DevInfo { + uint8_t category; // 0=CPU, 1=Interrupt, 2=Timer, 3=Input, 4=USB, 5=Network, 6=Display, 7=PCI + uint8_t _pad[3]; + char name[48]; + char detail[48]; + }; + + struct ProcInfo { + int32_t pid; + int32_t parentPid; + uint8_t state; // 0=Free, 1=Ready, 2=Running, 3=Terminated + uint8_t _pad[3]; + char name[64]; + uint64_t heapUsed; // heapNext - UserHeapBase (bytes) + }; + // Stack frame pushed by SyscallEntry.asm struct SyscallFrame { uint64_t r15, r14, r13, r12, rbp, rbx; // callee-saved diff --git a/kernel/src/Api/WinServer.cpp b/kernel/src/Api/WinServer.cpp new file mode 100644 index 0000000..21800ce --- /dev/null +++ b/kernel/src/Api/WinServer.cpp @@ -0,0 +1,175 @@ +/* + * WinServer.cpp + * Window server kernel implementation for external process windows + * Copyright (c) 2026 Daniel Hammer +*/ + +#include "WinServer.hpp" +#include +#include +#include +#include +#include + +namespace WinServer { + + static WindowSlot g_slots[MaxWindows]; + + int Create(int ownerPid, uint64_t ownerPml4, const char* title, int w, int h, + uint64_t& heapNext, uint64_t& outVa) { + // Find a free slot + int slotIdx = -1; + for (int i = 0; i < MaxWindows; i++) { + if (!g_slots[i].used) { + slotIdx = i; + break; + } + } + if (slotIdx < 0) return -1; + + // Validate dimensions + if (w <= 0 || h <= 0) return -1; + uint64_t bufSize = (uint64_t)w * h * 4; + int numPages = (int)((bufSize + 0xFFF) / 0x1000); + if (numPages > MaxPixelPages) return -1; + + WindowSlot& slot = g_slots[slotIdx]; + memset(&slot, 0, sizeof(WindowSlot)); + slot.used = true; + slot.ownerPid = ownerPid; + slot.width = w; + slot.height = h; + slot.pixelNumPages = numPages; + slot.eventHead = 0; + slot.eventTail = 0; + slot.dirty = false; + slot.desktopVa = 0; + slot.desktopPid = 0; + + // Copy title + int tlen = 0; + while (title[tlen] && tlen < 63) { + slot.title[tlen] = title[tlen]; + tlen++; + } + slot.title[tlen] = '\0'; + + // Allocate physical pages and map into owner's address space + uint64_t userVa = heapNext; + for (int i = 0; i < numPages; i++) { + void* page = Memory::g_pfa->AllocateZeroed(); + if (page == nullptr) { + // Cleanup on failure - mark slot unused + slot.used = false; + return -1; + } + uint64_t physAddr = Memory::SubHHDM((uint64_t)page); + slot.pixelPhysPages[i] = physAddr; + Memory::VMM::Paging::MapUserIn(ownerPml4, physAddr, userVa + (uint64_t)i * 0x1000); + } + + slot.ownerVa = userVa; + heapNext += (uint64_t)numPages * 0x1000; + outVa = userVa; + + Kt::KernelLogStream(Kt::OK, "WinServer") << "Created window " << slotIdx + << " (" << w << "x" << h << ") for PID " << ownerPid; + + return slotIdx; + } + + int Destroy(int windowId, int callerPid) { + if (windowId < 0 || windowId >= MaxWindows) return -1; + WindowSlot& slot = g_slots[windowId]; + if (!slot.used || slot.ownerPid != callerPid) return -1; + + slot.used = false; + return 0; + } + + int Present(int windowId, int callerPid) { + if (windowId < 0 || windowId >= MaxWindows) return -1; + WindowSlot& slot = g_slots[windowId]; + if (!slot.used || slot.ownerPid != callerPid) return -1; + + slot.dirty = true; + return 0; + } + + int Poll(int windowId, int callerPid, Zenith::WinEvent* outEvent) { + if (windowId < 0 || windowId >= MaxWindows) return -1; + WindowSlot& slot = g_slots[windowId]; + if (!slot.used || slot.ownerPid != callerPid) return -1; + + if (slot.eventHead == slot.eventTail) return 0; // no events + + *outEvent = slot.events[slot.eventTail]; + slot.eventTail = (slot.eventTail + 1) % MaxEvents; + return 1; + } + + int Enumerate(Zenith::WinInfo* outArray, int maxCount) { + int count = 0; + for (int i = 0; i < MaxWindows && count < maxCount; i++) { + if (!g_slots[i].used) continue; + Zenith::WinInfo& info = outArray[count]; + info.id = i; + info.ownerPid = g_slots[i].ownerPid; + for (int j = 0; j < 64; j++) info.title[j] = g_slots[i].title[j]; + info.width = g_slots[i].width; + info.height = g_slots[i].height; + info.dirty = g_slots[i].dirty ? 1 : 0; + g_slots[i].dirty = false; // clear dirty after read + count++; + } + return count; + } + + uint64_t Map(int windowId, int callerPid, uint64_t callerPml4, uint64_t& heapNext) { + if (windowId < 0 || windowId >= MaxWindows) return 0; + WindowSlot& slot = g_slots[windowId]; + if (!slot.used) return 0; + + // If already mapped into this process, return existing VA + if (slot.desktopPid == callerPid && slot.desktopVa != 0) { + return slot.desktopVa; + } + + uint64_t userVa = heapNext; + + for (int i = 0; i < slot.pixelNumPages; i++) { + Memory::VMM::Paging::MapUserIn(callerPml4, slot.pixelPhysPages[i], + userVa + (uint64_t)i * 0x1000); + } + + slot.desktopVa = userVa; + slot.desktopPid = callerPid; + heapNext += (uint64_t)slot.pixelNumPages * 0x1000; + + return userVa; + } + + int SendEvent(int windowId, const Zenith::WinEvent* event) { + if (windowId < 0 || windowId >= MaxWindows) return -1; + WindowSlot& slot = g_slots[windowId]; + if (!slot.used) return -1; + + int nextHead = (slot.eventHead + 1) % MaxEvents; + if (nextHead == slot.eventTail) return -1; // queue full, drop event + + slot.events[slot.eventHead] = *event; + slot.eventHead = nextHead; + return 0; + } + + void CleanupProcess(int pid) { + for (int i = 0; i < MaxWindows; i++) { + if (g_slots[i].used && g_slots[i].ownerPid == pid) { + Kt::KernelLogStream(Kt::INFO, "WinServer") << "Cleaning up window " + << i << " for exited PID " << pid; + g_slots[i].used = false; + } + } + } + +} diff --git a/kernel/src/Api/WinServer.hpp b/kernel/src/Api/WinServer.hpp new file mode 100644 index 0000000..53fd63f --- /dev/null +++ b/kernel/src/Api/WinServer.hpp @@ -0,0 +1,42 @@ +/* + * WinServer.hpp + * Window server kernel state for external process windows + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once +#include "Syscall.hpp" +#include + +namespace WinServer { + + static constexpr int MaxWindows = 8; + static constexpr int MaxEvents = 64; + static constexpr int MaxPixelPages = 2048; // up to 2048x1024 @ 32bpp = 8MB + + struct WindowSlot { + bool used; + int ownerPid; + char title[64]; + int width, height; + uint64_t pixelPhysPages[MaxPixelPages]; + int pixelNumPages; + uint64_t ownerVa; // VA in owner's address space + uint64_t desktopVa; // VA in desktop's address space (0 = not yet mapped) + int desktopPid; // PID of the process that mapped it + Zenith::WinEvent events[MaxEvents]; + int eventHead, eventTail; + bool dirty; + }; + + int Create(int ownerPid, uint64_t ownerPml4, const char* title, int w, int h, + uint64_t& heapNext, uint64_t& outVa); + int Destroy(int windowId, int callerPid); + int Present(int windowId, int callerPid); + int Poll(int windowId, int callerPid, Zenith::WinEvent* outEvent); + int Enumerate(Zenith::WinInfo* outArray, int maxCount); + uint64_t Map(int windowId, int callerPid, uint64_t callerPml4, uint64_t& heapNext); + int SendEvent(int windowId, const Zenith::WinEvent* event); + void CleanupProcess(int pid); + +} diff --git a/kernel/src/Drivers/USB/UsbDevice.cpp b/kernel/src/Drivers/USB/UsbDevice.cpp index 73f8373..b811b71 100644 --- a/kernel/src/Drivers/USB/UsbDevice.cpp +++ b/kernel/src/Drivers/USB/UsbDevice.cpp @@ -461,9 +461,11 @@ namespace Drivers::USB::UsbDevice { } // ----------------------------------------------------------------- - // Step 10: SET_PROTOCOL(0) -- request Boot Protocol + // Step 10: SET_PROTOCOL -- Boot Protocol for keyboards only // ----------------------------------------------------------------- - if (foundEp) { + // Boot Protocol constrains mice to 3-byte reports (no scroll wheel). + // Keep mice in Report Protocol (the default) so scroll data is included. + if (foundEp && dev->InterfaceProtocol == PROTOCOL_KEYBOARD) { cc = Xhci::ControlTransfer(slotId, REQTYPE_CLASS_IFACE, REQ_SET_PROTOCOL, 0, 0, 0, nullptr, false); if (cc != Xhci::CC_SUCCESS) { @@ -475,7 +477,7 @@ namespace Drivers::USB::UsbDevice { // ----------------------------------------------------------------- // Step 11: SET_IDLE(4) -- 16ms idle rate for software typematic // ----------------------------------------------------------------- - if (foundEp) { + if (foundEp && dev->InterfaceProtocol == PROTOCOL_KEYBOARD) { // wValue upper byte = duration in 4ms units, lower byte = report ID cc = Xhci::ControlTransfer(slotId, REQTYPE_CLASS_IFACE, REQ_SET_IDLE, (4 << 8), 0, 0, nullptr, false); diff --git a/kernel/src/Hal/Apic/ApicInit.cpp b/kernel/src/Hal/Apic/ApicInit.cpp index f5d7ebe..1929249 100644 --- a/kernel/src/Hal/Apic/ApicInit.cpp +++ b/kernel/src/Hal/Apic/ApicInit.cpp @@ -19,6 +19,10 @@ using namespace Kt; namespace Hal { + static int g_detectedCpuCount = 0; + + int GetDetectedCpuCount() { return g_detectedCpuCount; } + void ApicInitialize(ACPI::CommonSDTHeader* xsdt) { KernelLogStream(INFO, "APIC") << "Initializing APIC subsystem"; @@ -29,6 +33,8 @@ namespace Hal { return; } + g_detectedCpuCount = madt.LocalApicCount; + if (madt.IoApicAddress == 0) { KernelLogStream(ERROR, "APIC") << "No IOAPIC found in MADT"; return; diff --git a/kernel/src/Hal/Apic/ApicInit.hpp b/kernel/src/Hal/Apic/ApicInit.hpp index 3d4ea38..a3122c7 100644 --- a/kernel/src/Hal/Apic/ApicInit.hpp +++ b/kernel/src/Hal/Apic/ApicInit.hpp @@ -19,4 +19,7 @@ namespace Hal { // // xsdt: pointer to the XSDT (already HHDM-mapped) void ApicInitialize(ACPI::CommonSDTHeader* xsdt); + + // Number of CPU cores detected via MADT (available after ApicInitialize) + int GetDetectedCpuCount(); }; diff --git a/kernel/src/Sched/Context.asm b/kernel/src/Sched/Context.asm index 56bdfe7..b86d00e 100644 --- a/kernel/src/Sched/Context.asm +++ b/kernel/src/Sched/Context.asm @@ -1,18 +1,30 @@ ; ; Context.asm -; Context switch: save/restore callee-saved registers, stack pointer, and CR3 +; Context switch: save/restore callee-saved registers, stack pointer, CR3, and FPU state ; Copyright (c) 2025 Daniel Hammer ; [bits 64] section .text -; void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3) +; void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3, +; uint8_t* oldFpuArea, uint8_t* newFpuArea) ; rdi = pointer to save old RSP ; rsi = new RSP to restore ; rdx = new PML4 physical address (for CR3) +; rcx = old FPU state area (may be null) +; r8 = new FPU state area (may be null) global SchedContextSwitch SchedContextSwitch: + ; Save FPU state before pushing registers (rcx/r8 are caller-saved) + test rcx, rcx + jz .skip_fxsave + fxsave [rcx] +.skip_fxsave: + + ; Stash r8 in r9 (callee-saved registers will clobber r8 slot on stack) + mov r9, r8 + ; Save callee-saved registers on the current stack push rbp push rbx @@ -42,4 +54,10 @@ SchedContextSwitch: pop rbx pop rbp + ; Restore FPU state + test r9, r9 + jz .skip_fxrstor + fxrstor [r9] +.skip_fxrstor: + ret diff --git a/kernel/src/Sched/Scheduler.cpp b/kernel/src/Sched/Scheduler.cpp index 7b2de94..a7574a7 100644 --- a/kernel/src/Sched/Scheduler.cpp +++ b/kernel/src/Sched/Scheduler.cpp @@ -14,9 +14,11 @@ #include #include #include +#include -// Assembly: context switch with CR3 parameter -extern "C" void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3); +// Assembly: context switch with CR3 and FPU state parameters +extern "C" void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3, + uint8_t* oldFpuArea, uint8_t* newFpuArea); // Assembly: jump to user mode via IRETQ extern "C" void JumpToUserMode(uint64_t rip, uint64_t rsp); @@ -66,7 +68,7 @@ namespace Sched { for (int i = 0; i < MaxProcesses; i++) { processTable[i].pid = i; processTable[i].state = ProcessState::Free; - processTable[i].name = nullptr; + processTable[i].name[0] = '\0'; processTable[i].savedRsp = 0; processTable[i].stackBase = 0; processTable[i].entryPoint = 0; @@ -191,7 +193,11 @@ namespace Sched { Process& proc = processTable[slot]; proc.pid = nextPid++; proc.state = ProcessState::Ready; - proc.name = vfsPath; + { + int i = 0; + for (; i < 63 && vfsPath[i]; i++) proc.name[i] = vfsPath[i]; + proc.name[i] = '\0'; + } proc.savedRsp = (uint64_t)sp; proc.stackBase = (uint64_t)kernelStackBase; proc.entryPoint = entry; @@ -224,6 +230,11 @@ namespace Sched { proc.termCols = 0; proc.termRows = 0; + // Initialize FPU state: zero out, then set default FCW and MXCSR + memset(proc.fpuState, 0, 512); + *(uint16_t*)&proc.fpuState[0] = 0x037F; // FCW: default x87 control word + *(uint32_t*)&proc.fpuState[24] = 0x1F80; // MXCSR: default SSE control/status + return proc.pid; } @@ -276,7 +287,9 @@ namespace Sched { // Update TSS RSP0 for hardware interrupts from ring 3 Hal::g_tss.rsp0 = processTable[next].kernelStackTop; - SchedContextSwitch(oldRspPtr, processTable[next].savedRsp, newCR3); + uint8_t* oldFpu = (currentPid >= 0) ? processTable[currentPid].fpuState : nullptr; + uint8_t* newFpu = processTable[next].fpuState; + SchedContextSwitch(oldRspPtr, processTable[next].savedRsp, newCR3, oldFpu, newFpu); } void Tick() { @@ -309,6 +322,9 @@ namespace Sched { return; } + // Clean up any windows owned by this process + WinServer::CleanupProcess(processTable[currentPid].pid); + processTable[currentPid].state = ProcessState::Terminated; int next = -1; @@ -329,11 +345,13 @@ namespace Sched { g_kernelRsp = processTable[next].kernelStackTop; Hal::g_tss.rsp0 = processTable[next].kernelStackTop; - SchedContextSwitch(&processTable[old].savedRsp, processTable[next].savedRsp, newCR3); + SchedContextSwitch(&processTable[old].savedRsp, processTable[next].savedRsp, newCR3, + processTable[old].fpuState, processTable[next].fpuState); } else { int old = currentPid; currentPid = -1; - SchedContextSwitch(&processTable[old].savedRsp, idleSavedRsp, GetKernelCR3()); + SchedContextSwitch(&processTable[old].savedRsp, idleSavedRsp, GetKernelCR3(), + processTable[old].fpuState, nullptr); } for (;;) { @@ -362,4 +380,9 @@ namespace Sched { return nullptr; } + Process* GetProcessSlot(int slot) { + if (slot < 0 || slot >= MaxProcesses) return nullptr; + return &processTable[slot]; + } + } diff --git a/kernel/src/Sched/Scheduler.hpp b/kernel/src/Sched/Scheduler.hpp index 04b35d9..218b870 100644 --- a/kernel/src/Sched/Scheduler.hpp +++ b/kernel/src/Sched/Scheduler.hpp @@ -30,7 +30,7 @@ namespace Sched { struct Process { int pid; ProcessState state; - const char* name; + char name[64]; uint64_t savedRsp; uint64_t stackBase; // Bottom of allocated kernel stack (lowest address) uint64_t entryPoint; @@ -58,6 +58,9 @@ namespace Sched { // GUI terminal dimensions (set by desktop, read by SYS_TERMSIZE) int termCols = 0; int termRows = 0; + + // FPU/SSE state (FXSAVE format, must be 16-byte aligned) + uint8_t fpuState[512] __attribute__((aligned(16))); }; void Initialize(); @@ -82,4 +85,7 @@ namespace Sched { // Find a process by PID (returns nullptr if not found or not alive) Process* GetProcessByPid(int pid); + // Get a pointer to slot i in the process table (for enumeration) + Process* GetProcessSlot(int slot); + } diff --git a/programs/GNUmakefile b/programs/GNUmakefile index 0d8ecc1..278d5b8 100644 --- a/programs/GNUmakefile +++ b/programs/GNUmakefile @@ -82,9 +82,9 @@ CA_CERTS := $(BINDIR)/etc/ca-certificates.crt # Home directory placeholder. HOMEKEEP := $(BINDIR)/home/.keep -.PHONY: all clean doom fetch wiki desktop icons bearssl libc +.PHONY: all clean doom fetch wiki desktop icons fonts bearssl libc -all: bearssl libc $(TARGETS) fetch wiki doom desktop icons $(GAME_DATA) $(MANDST) $(WWWDST) $(CA_CERTS) $(HOMEKEEP) +all: bearssl libc $(TARGETS) fetch wiki doom desktop icons fonts $(GAME_DATA) $(MANDST) $(WWWDST) $(CA_CERTS) $(HOMEKEEP) # Build BearSSL static library. bearssl: @@ -110,6 +110,10 @@ desktop: icons: ../scripts/copy_icons.sh +# Copy TTF fonts for the desktop into bin/fonts/. +fonts: + ../scripts/copy_fonts.sh + # Build doom via its own Makefile. doom: $(MAKE) -C src/doom diff --git a/programs/gui/fonts/JetBrains_Mono/JetBrainsMono-Italic-VariableFont_wght.ttf b/programs/gui/fonts/JetBrains_Mono/JetBrainsMono-Italic-VariableFont_wght.ttf new file mode 100644 index 0000000..ecb5f73 Binary files /dev/null and b/programs/gui/fonts/JetBrains_Mono/JetBrainsMono-Italic-VariableFont_wght.ttf differ diff --git a/programs/gui/fonts/JetBrains_Mono/JetBrainsMono-VariableFont_wght.ttf b/programs/gui/fonts/JetBrains_Mono/JetBrainsMono-VariableFont_wght.ttf new file mode 100644 index 0000000..4c96e79 Binary files /dev/null and b/programs/gui/fonts/JetBrains_Mono/JetBrainsMono-VariableFont_wght.ttf differ diff --git a/programs/gui/fonts/JetBrains_Mono/OFL.txt b/programs/gui/fonts/JetBrains_Mono/OFL.txt new file mode 100644 index 0000000..3f34847 --- /dev/null +++ b/programs/gui/fonts/JetBrains_Mono/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/programs/gui/fonts/JetBrains_Mono/README.txt b/programs/gui/fonts/JetBrains_Mono/README.txt new file mode 100644 index 0000000..534de33 --- /dev/null +++ b/programs/gui/fonts/JetBrains_Mono/README.txt @@ -0,0 +1,79 @@ +JetBrains Mono Variable Font +============================ + +This download contains JetBrains Mono as both variable fonts and static fonts. + +JetBrains Mono is a variable font with this axis: + wght + +This means all the styles are contained in these files: + JetBrains_Mono/JetBrainsMono-VariableFont_wght.ttf + JetBrains_Mono/JetBrainsMono-Italic-VariableFont_wght.ttf + +If your app fully supports variable fonts, you can now pick intermediate styles +that aren’t available as static fonts. Not all apps support variable fonts, and +in those cases you can use the static font files for JetBrains Mono: + JetBrains_Mono/static/JetBrainsMono-Thin.ttf + JetBrains_Mono/static/JetBrainsMono-ExtraLight.ttf + JetBrains_Mono/static/JetBrainsMono-Light.ttf + JetBrains_Mono/static/JetBrainsMono-Regular.ttf + JetBrains_Mono/static/JetBrainsMono-Medium.ttf + JetBrains_Mono/static/JetBrainsMono-SemiBold.ttf + JetBrains_Mono/static/JetBrainsMono-Bold.ttf + JetBrains_Mono/static/JetBrainsMono-ExtraBold.ttf + JetBrains_Mono/static/JetBrainsMono-ThinItalic.ttf + JetBrains_Mono/static/JetBrainsMono-ExtraLightItalic.ttf + JetBrains_Mono/static/JetBrainsMono-LightItalic.ttf + JetBrains_Mono/static/JetBrainsMono-Italic.ttf + JetBrains_Mono/static/JetBrainsMono-MediumItalic.ttf + JetBrains_Mono/static/JetBrainsMono-SemiBoldItalic.ttf + JetBrains_Mono/static/JetBrainsMono-BoldItalic.ttf + JetBrains_Mono/static/JetBrainsMono-ExtraBoldItalic.ttf + +Get started +----------- + +1. Install the font files you want to use + +2. Use your app's font picker to view the font family and all the +available styles + +Learn more about variable fonts +------------------------------- + + https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts + https://variablefonts.typenetwork.com + https://medium.com/variable-fonts + +In desktop apps + + https://theblog.adobe.com/can-variable-fonts-illustrator-cc + https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts + +Online + + https://developers.google.com/fonts/docs/getting_started + https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide + https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts + +Installing fonts + + MacOS: https://support.apple.com/en-us/HT201749 + Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux + Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows + +Android Apps + + https://developers.google.com/fonts/docs/android + https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts + +License +------- +Please read the full license text (OFL.txt) to understand the permissions, +restrictions and requirements for usage, redistribution, and modification. + +You can use them in your products & projects – print or digital, +commercial or otherwise. + +This isn't legal advice, please consider consulting a lawyer and see the full +license for all details. diff --git a/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-Bold.ttf b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-Bold.ttf new file mode 100644 index 0000000..1926c80 Binary files /dev/null and b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-Bold.ttf differ diff --git a/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-BoldItalic.ttf b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-BoldItalic.ttf new file mode 100644 index 0000000..a447751 Binary files /dev/null and b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-BoldItalic.ttf differ diff --git a/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraBold.ttf b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraBold.ttf new file mode 100644 index 0000000..0c5f863 Binary files /dev/null and b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraBold.ttf differ diff --git a/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraBoldItalic.ttf b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraBoldItalic.ttf new file mode 100644 index 0000000..d62fc3c Binary files /dev/null and b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraBoldItalic.ttf differ diff --git a/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraLight.ttf b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraLight.ttf new file mode 100644 index 0000000..605f79d Binary files /dev/null and b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraLight.ttf differ diff --git a/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraLightItalic.ttf b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraLightItalic.ttf new file mode 100644 index 0000000..befe9d2 Binary files /dev/null and b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraLightItalic.ttf differ diff --git a/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-Italic.ttf b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-Italic.ttf new file mode 100644 index 0000000..8cf794a Binary files /dev/null and b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-Italic.ttf differ diff --git a/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-Light.ttf b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-Light.ttf new file mode 100644 index 0000000..9d5d8a5 Binary files /dev/null and b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-Light.ttf differ diff --git a/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-LightItalic.ttf b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-LightItalic.ttf new file mode 100644 index 0000000..4c91d3e Binary files /dev/null and b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-LightItalic.ttf differ diff --git a/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-Medium.ttf b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-Medium.ttf new file mode 100644 index 0000000..ad71d92 Binary files /dev/null and b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-Medium.ttf differ diff --git a/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-MediumItalic.ttf b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-MediumItalic.ttf new file mode 100644 index 0000000..4c96cc5 Binary files /dev/null and b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-MediumItalic.ttf differ diff --git a/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-Regular.ttf b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-Regular.ttf new file mode 100644 index 0000000..436c982 Binary files /dev/null and b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-Regular.ttf differ diff --git a/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-SemiBold.ttf b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-SemiBold.ttf new file mode 100644 index 0000000..b00a648 Binary files /dev/null and b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-SemiBold.ttf differ diff --git a/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-SemiBoldItalic.ttf b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-SemiBoldItalic.ttf new file mode 100644 index 0000000..5b6c9a8 Binary files /dev/null and b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-SemiBoldItalic.ttf differ diff --git a/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-Thin.ttf b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-Thin.ttf new file mode 100644 index 0000000..322705a Binary files /dev/null and b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-Thin.ttf differ diff --git a/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-ThinItalic.ttf b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-ThinItalic.ttf new file mode 100644 index 0000000..58b4085 Binary files /dev/null and b/programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-ThinItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/OFL.txt b/programs/gui/fonts/Roboto/OFL.txt new file mode 100644 index 0000000..a417551 --- /dev/null +++ b/programs/gui/fonts/Roboto/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2011 The Roboto Project Authors (https://github.com/googlefonts/roboto-classic) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/programs/gui/fonts/Roboto/README.txt b/programs/gui/fonts/Roboto/README.txt new file mode 100644 index 0000000..3d6736b --- /dev/null +++ b/programs/gui/fonts/Roboto/README.txt @@ -0,0 +1,118 @@ +Roboto Variable Font +==================== + +This download contains Roboto as both variable fonts and static fonts. + +Roboto is a variable font with these axes: + wdth + wght + +This means all the styles are contained in these files: + Roboto/Roboto-VariableFont_wdth,wght.ttf + Roboto/Roboto-Italic-VariableFont_wdth,wght.ttf + +If your app fully supports variable fonts, you can now pick intermediate styles +that aren’t available as static fonts. Not all apps support variable fonts, and +in those cases you can use the static font files for Roboto: + Roboto/static/Roboto_Condensed-Thin.ttf + Roboto/static/Roboto_Condensed-ExtraLight.ttf + Roboto/static/Roboto_Condensed-Light.ttf + Roboto/static/Roboto_Condensed-Regular.ttf + Roboto/static/Roboto_Condensed-Medium.ttf + Roboto/static/Roboto_Condensed-SemiBold.ttf + Roboto/static/Roboto_Condensed-Bold.ttf + Roboto/static/Roboto_Condensed-ExtraBold.ttf + Roboto/static/Roboto_Condensed-Black.ttf + Roboto/static/Roboto_SemiCondensed-Thin.ttf + Roboto/static/Roboto_SemiCondensed-ExtraLight.ttf + Roboto/static/Roboto_SemiCondensed-Light.ttf + Roboto/static/Roboto_SemiCondensed-Regular.ttf + Roboto/static/Roboto_SemiCondensed-Medium.ttf + Roboto/static/Roboto_SemiCondensed-SemiBold.ttf + Roboto/static/Roboto_SemiCondensed-Bold.ttf + Roboto/static/Roboto_SemiCondensed-ExtraBold.ttf + Roboto/static/Roboto_SemiCondensed-Black.ttf + Roboto/static/Roboto-Thin.ttf + Roboto/static/Roboto-ExtraLight.ttf + Roboto/static/Roboto-Light.ttf + Roboto/static/Roboto-Regular.ttf + Roboto/static/Roboto-Medium.ttf + Roboto/static/Roboto-SemiBold.ttf + Roboto/static/Roboto-Bold.ttf + Roboto/static/Roboto-ExtraBold.ttf + Roboto/static/Roboto-Black.ttf + Roboto/static/Roboto_Condensed-ThinItalic.ttf + Roboto/static/Roboto_Condensed-ExtraLightItalic.ttf + Roboto/static/Roboto_Condensed-LightItalic.ttf + Roboto/static/Roboto_Condensed-Italic.ttf + Roboto/static/Roboto_Condensed-MediumItalic.ttf + Roboto/static/Roboto_Condensed-SemiBoldItalic.ttf + Roboto/static/Roboto_Condensed-BoldItalic.ttf + Roboto/static/Roboto_Condensed-ExtraBoldItalic.ttf + Roboto/static/Roboto_Condensed-BlackItalic.ttf + Roboto/static/Roboto_SemiCondensed-ThinItalic.ttf + Roboto/static/Roboto_SemiCondensed-ExtraLightItalic.ttf + Roboto/static/Roboto_SemiCondensed-LightItalic.ttf + Roboto/static/Roboto_SemiCondensed-Italic.ttf + Roboto/static/Roboto_SemiCondensed-MediumItalic.ttf + Roboto/static/Roboto_SemiCondensed-SemiBoldItalic.ttf + Roboto/static/Roboto_SemiCondensed-BoldItalic.ttf + Roboto/static/Roboto_SemiCondensed-ExtraBoldItalic.ttf + Roboto/static/Roboto_SemiCondensed-BlackItalic.ttf + Roboto/static/Roboto-ThinItalic.ttf + Roboto/static/Roboto-ExtraLightItalic.ttf + Roboto/static/Roboto-LightItalic.ttf + Roboto/static/Roboto-Italic.ttf + Roboto/static/Roboto-MediumItalic.ttf + Roboto/static/Roboto-SemiBoldItalic.ttf + Roboto/static/Roboto-BoldItalic.ttf + Roboto/static/Roboto-ExtraBoldItalic.ttf + Roboto/static/Roboto-BlackItalic.ttf + +Get started +----------- + +1. Install the font files you want to use + +2. Use your app's font picker to view the font family and all the +available styles + +Learn more about variable fonts +------------------------------- + + https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts + https://variablefonts.typenetwork.com + https://medium.com/variable-fonts + +In desktop apps + + https://theblog.adobe.com/can-variable-fonts-illustrator-cc + https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts + +Online + + https://developers.google.com/fonts/docs/getting_started + https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide + https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts + +Installing fonts + + MacOS: https://support.apple.com/en-us/HT201749 + Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux + Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows + +Android Apps + + https://developers.google.com/fonts/docs/android + https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts + +License +------- +Please read the full license text (OFL.txt) to understand the permissions, +restrictions and requirements for usage, redistribution, and modification. + +You can use them in your products & projects – print or digital, +commercial or otherwise. + +This isn't legal advice, please consider consulting a lawyer and see the full +license for all details. diff --git a/programs/gui/fonts/Roboto/Roboto-Italic-VariableFont_wdth,wght.ttf b/programs/gui/fonts/Roboto/Roboto-Italic-VariableFont_wdth,wght.ttf new file mode 100644 index 0000000..fb3c626 Binary files /dev/null and b/programs/gui/fonts/Roboto/Roboto-Italic-VariableFont_wdth,wght.ttf differ diff --git a/programs/gui/fonts/Roboto/Roboto-VariableFont_wdth,wght.ttf b/programs/gui/fonts/Roboto/Roboto-VariableFont_wdth,wght.ttf new file mode 100644 index 0000000..01656a3 Binary files /dev/null and b/programs/gui/fonts/Roboto/Roboto-VariableFont_wdth,wght.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto-Black.ttf b/programs/gui/fonts/Roboto/static/Roboto-Black.ttf new file mode 100644 index 0000000..f939fc7 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto-Black.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto-BlackItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto-BlackItalic.ttf new file mode 100644 index 0000000..e2a2028 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto-BlackItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto-Bold.ttf b/programs/gui/fonts/Roboto/static/Roboto-Bold.ttf new file mode 100644 index 0000000..6516185 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto-Bold.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto-BoldItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto-BoldItalic.ttf new file mode 100644 index 0000000..adc7d44 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto-BoldItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto-ExtraBold.ttf b/programs/gui/fonts/Roboto/static/Roboto-ExtraBold.ttf new file mode 100644 index 0000000..9dc48a1 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto-ExtraBold.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto-ExtraBoldItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto-ExtraBoldItalic.ttf new file mode 100644 index 0000000..f297672 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto-ExtraBoldItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto-ExtraLight.ttf b/programs/gui/fonts/Roboto/static/Roboto-ExtraLight.ttf new file mode 100644 index 0000000..5e517b3 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto-ExtraLight.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto-ExtraLightItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto-ExtraLightItalic.ttf new file mode 100644 index 0000000..ff2c0d9 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto-ExtraLightItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto-Italic.ttf b/programs/gui/fonts/Roboto/static/Roboto-Italic.ttf new file mode 100644 index 0000000..01f2c5d Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto-Italic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto-Light.ttf b/programs/gui/fonts/Roboto/static/Roboto-Light.ttf new file mode 100644 index 0000000..ee7268f Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto-Light.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto-LightItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto-LightItalic.ttf new file mode 100644 index 0000000..cb70f96 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto-LightItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto-Medium.ttf b/programs/gui/fonts/Roboto/static/Roboto-Medium.ttf new file mode 100644 index 0000000..bc5b170 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto-Medium.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto-MediumItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto-MediumItalic.ttf new file mode 100644 index 0000000..739dcf3 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto-MediumItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto-Regular.ttf b/programs/gui/fonts/Roboto/static/Roboto-Regular.ttf new file mode 100644 index 0000000..3db0d1f Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto-Regular.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto-SemiBold.ttf b/programs/gui/fonts/Roboto/static/Roboto-SemiBold.ttf new file mode 100644 index 0000000..7a8ef87 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto-SemiBold.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto-SemiBoldItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto-SemiBoldItalic.ttf new file mode 100644 index 0000000..224bc96 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto-SemiBoldItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto-Thin.ttf b/programs/gui/fonts/Roboto/static/Roboto-Thin.ttf new file mode 100644 index 0000000..cdd56c5 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto-Thin.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto-ThinItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto-ThinItalic.ttf new file mode 100644 index 0000000..ec4b537 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto-ThinItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_Condensed-Black.ttf b/programs/gui/fonts/Roboto/static/Roboto_Condensed-Black.ttf new file mode 100644 index 0000000..222ede4 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_Condensed-Black.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_Condensed-BlackItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto_Condensed-BlackItalic.ttf new file mode 100644 index 0000000..532c8ca Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_Condensed-BlackItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_Condensed-Bold.ttf b/programs/gui/fonts/Roboto/static/Roboto_Condensed-Bold.ttf new file mode 100644 index 0000000..d589f51 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_Condensed-Bold.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_Condensed-BoldItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto_Condensed-BoldItalic.ttf new file mode 100644 index 0000000..08d7c05 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_Condensed-BoldItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_Condensed-ExtraBold.ttf b/programs/gui/fonts/Roboto/static/Roboto_Condensed-ExtraBold.ttf new file mode 100644 index 0000000..d4c604a Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_Condensed-ExtraBold.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_Condensed-ExtraBoldItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto_Condensed-ExtraBoldItalic.ttf new file mode 100644 index 0000000..036486f Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_Condensed-ExtraBoldItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_Condensed-ExtraLight.ttf b/programs/gui/fonts/Roboto/static/Roboto_Condensed-ExtraLight.ttf new file mode 100644 index 0000000..50a71d8 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_Condensed-ExtraLight.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_Condensed-ExtraLightItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto_Condensed-ExtraLightItalic.ttf new file mode 100644 index 0000000..0e91dfd Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_Condensed-ExtraLightItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_Condensed-Italic.ttf b/programs/gui/fonts/Roboto/static/Roboto_Condensed-Italic.ttf new file mode 100644 index 0000000..86853c3 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_Condensed-Italic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_Condensed-Light.ttf b/programs/gui/fonts/Roboto/static/Roboto_Condensed-Light.ttf new file mode 100644 index 0000000..edaf03e Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_Condensed-Light.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_Condensed-LightItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto_Condensed-LightItalic.ttf new file mode 100644 index 0000000..1b6a511 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_Condensed-LightItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_Condensed-Medium.ttf b/programs/gui/fonts/Roboto/static/Roboto_Condensed-Medium.ttf new file mode 100644 index 0000000..dcad58b Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_Condensed-Medium.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_Condensed-MediumItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto_Condensed-MediumItalic.ttf new file mode 100644 index 0000000..521057b Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_Condensed-MediumItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_Condensed-Regular.ttf b/programs/gui/fonts/Roboto/static/Roboto_Condensed-Regular.ttf new file mode 100644 index 0000000..0c6feda Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_Condensed-Regular.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_Condensed-SemiBold.ttf b/programs/gui/fonts/Roboto/static/Roboto_Condensed-SemiBold.ttf new file mode 100644 index 0000000..3f20051 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_Condensed-SemiBold.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_Condensed-SemiBoldItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto_Condensed-SemiBoldItalic.ttf new file mode 100644 index 0000000..da9625c Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_Condensed-SemiBoldItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_Condensed-Thin.ttf b/programs/gui/fonts/Roboto/static/Roboto_Condensed-Thin.ttf new file mode 100644 index 0000000..5a81776 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_Condensed-Thin.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_Condensed-ThinItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto_Condensed-ThinItalic.ttf new file mode 100644 index 0000000..4c58469 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_Condensed-ThinItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-Black.ttf b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-Black.ttf new file mode 100644 index 0000000..d321657 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-Black.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-BlackItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-BlackItalic.ttf new file mode 100644 index 0000000..3fddfd2 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-BlackItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-Bold.ttf b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-Bold.ttf new file mode 100644 index 0000000..3b09bdc Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-Bold.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-BoldItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-BoldItalic.ttf new file mode 100644 index 0000000..58a292d Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-BoldItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-ExtraBold.ttf b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-ExtraBold.ttf new file mode 100644 index 0000000..2ed59f6 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-ExtraBold.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-ExtraBoldItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-ExtraBoldItalic.ttf new file mode 100644 index 0000000..6a6ea25 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-ExtraBoldItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-ExtraLight.ttf b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-ExtraLight.ttf new file mode 100644 index 0000000..d86e218 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-ExtraLight.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-ExtraLightItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-ExtraLightItalic.ttf new file mode 100644 index 0000000..44777e0 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-ExtraLightItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-Italic.ttf b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-Italic.ttf new file mode 100644 index 0000000..fb944fb Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-Italic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-Light.ttf b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-Light.ttf new file mode 100644 index 0000000..aa2fd11 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-Light.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-LightItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-LightItalic.ttf new file mode 100644 index 0000000..16331bd Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-LightItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-Medium.ttf b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-Medium.ttf new file mode 100644 index 0000000..54c1f07 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-Medium.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-MediumItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-MediumItalic.ttf new file mode 100644 index 0000000..f0d504b Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-MediumItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-Regular.ttf b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-Regular.ttf new file mode 100644 index 0000000..462fe48 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-Regular.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-SemiBold.ttf b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-SemiBold.ttf new file mode 100644 index 0000000..2abf7bc Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-SemiBold.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-SemiBoldItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-SemiBoldItalic.ttf new file mode 100644 index 0000000..62905c8 Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-SemiBoldItalic.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-Thin.ttf b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-Thin.ttf new file mode 100644 index 0000000..a51429a Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-Thin.ttf differ diff --git a/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-ThinItalic.ttf b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-ThinItalic.ttf new file mode 100644 index 0000000..a1cd1cf Binary files /dev/null and b/programs/gui/fonts/Roboto/static/Roboto_SemiCondensed-ThinItalic.ttf differ diff --git a/programs/include/Api/Syscall.hpp b/programs/include/Api/Syscall.hpp index 7ff7a20..43f309f 100644 --- a/programs/include/Api/Syscall.hpp +++ b/programs/include/Api/Syscall.hpp @@ -66,6 +66,20 @@ namespace Zenith { static constexpr uint64_t SYS_CHILDIO_WRITEKEY = 52; static constexpr uint64_t SYS_CHILDIO_SETTERMSZ = 53; + // Window server syscalls + static constexpr uint64_t SYS_WINCREATE = 54; + static constexpr uint64_t SYS_WINDESTROY = 55; + static constexpr uint64_t SYS_WINPRESENT = 56; + static constexpr uint64_t SYS_WINPOLL = 57; + static constexpr uint64_t SYS_WINENUM = 58; + static constexpr uint64_t SYS_WINMAP = 59; + static constexpr uint64_t SYS_WINSENDEVENT = 60; + + // Process management syscalls + static constexpr uint64_t SYS_PROCLIST = 61; + static constexpr uint64_t SYS_KILL = 62; + static constexpr uint64_t SYS_DEVLIST = 63; + static constexpr int SOCK_TCP = 1; static constexpr int SOCK_UDP = 2; @@ -118,4 +132,46 @@ namespace Zenith { uint8_t buttons; }; + // Window server shared types + struct WinEvent { + uint8_t type; // 0=key, 1=mouse, 2=resize, 3=close + uint8_t _pad[3]; + union { + KeyEvent key; + struct { int32_t x, y, scroll; uint8_t buttons, prev_buttons; } mouse; + struct { int32_t w, h; } resize; + }; + }; + + struct WinInfo { + int32_t id; + int32_t ownerPid; + char title[64]; + int32_t width, height; + uint8_t dirty; + uint8_t _pad2[3]; + }; + + struct WinCreateResult { + int32_t id; // -1 on failure + uint32_t _pad; + uint64_t pixelVa; // VA of pixel buffer in caller's address space + }; + + struct DevInfo { + uint8_t category; // 0=CPU, 1=Interrupt, 2=Timer, 3=Input, 4=USB, 5=Network, 6=Display, 7=PCI + uint8_t _pad[3]; + char name[48]; + char detail[48]; + }; + + struct ProcInfo { + int32_t pid; + int32_t parentPid; + uint8_t state; // 0=Free, 1=Ready, 2=Running, 3=Terminated + uint8_t _pad[3]; + char name[64]; + uint64_t heapUsed; // heapNext - UserHeapBase (bytes) + }; + } diff --git a/programs/include/gui/canvas.hpp b/programs/include/gui/canvas.hpp index 10c16fd..a3f3853 100644 --- a/programs/include/gui/canvas.hpp +++ b/programs/include/gui/canvas.hpp @@ -99,6 +99,10 @@ struct Canvas { // ---- Text ---- void text(int x, int y, const char* str, Color c) { + if (fonts::system_font && fonts::system_font->valid) { + fonts::system_font->draw_to_buffer(pixels, w, h, x, y, str, c, fonts::UI_SIZE); + return; + } uint32_t px = c.to_pixel(); for (int i = 0; str[i] && x + (i + 1) * FONT_WIDTH <= w; i++) { const uint8_t* glyph = &font_data[(unsigned char)str[i] * FONT_HEIGHT]; @@ -118,6 +122,10 @@ struct Canvas { } void text_2x(int x, int y, const char* str, Color c) { + if (fonts::system_font && fonts::system_font->valid) { + fonts::system_font->draw_to_buffer(pixels, w, h, x, y, str, c, fonts::LARGE_SIZE); + return; + } uint32_t px = c.to_pixel(); for (int i = 0; str[i] && x + (i + 1) * FONT_WIDTH * 2 <= w; i++) { const uint8_t* glyph = &font_data[(unsigned char)str[i] * FONT_HEIGHT]; @@ -141,6 +149,14 @@ struct Canvas { } } + void text_mono(int x, int y, const char* str, Color c) { + if (fonts::mono && fonts::mono->valid) { + fonts::mono->draw_to_buffer(pixels, w, h, x, y, str, c, fonts::TERM_SIZE); + return; + } + text(x, y, str, c); + } + // ---- Icons ---- void icon(int x, int y, const SvgIcon& ic) { @@ -176,7 +192,8 @@ struct Canvas { // ---- High-level helpers ---- - void kv_line(int x, int* y, const char* line, Color c, int line_h = FONT_HEIGHT + 6) { + void kv_line(int x, int* y, const char* line, Color c, int line_h = 0) { + if (line_h == 0) line_h = system_font_height() + 6; text(x, *y, line, c); *y += line_h; } @@ -190,8 +207,9 @@ struct Canvas { Color bg, Color fg, int radius = 4) { fill_rounded_rect(x, y, bw, bh, radius, bg); int tw = text_width(label); + int fh = system_font_height(); int tx = x + (bw - tw) / 2; - int ty = y + (bh - FONT_HEIGHT) / 2; + int ty = y + (bh - fh) / 2; text(tx, ty, label, fg); } }; diff --git a/programs/include/gui/desktop.hpp b/programs/include/gui/desktop.hpp index 51b5689..d46f693 100644 --- a/programs/include/gui/desktop.hpp +++ b/programs/include/gui/desktop.hpp @@ -18,6 +18,25 @@ namespace gui { static constexpr int MAX_WINDOWS = 8; static constexpr int PANEL_HEIGHT = 32; +struct DesktopSettings { + // Background + bool bg_gradient; // true = gradient, false = solid + Color bg_solid; // solid background color + Color bg_grad_top; // gradient top color + Color bg_grad_bottom; // gradient bottom color + + // Panel + Color panel_color; // panel background color + + // Accent + Color accent_color; // buttons, highlights, active indicators + + // Display + bool show_shadows; // window shadows on/off + bool clock_24h; // 24-hour clock format + int ui_scale; // 0=Small, 1=Default, 2=Large +}; + struct DesktopState { Framebuffer fb; Window windows[MAX_WINDOWS]; @@ -54,6 +73,11 @@ struct DesktopState { SvgIcon icon_settings; SvgIcon icon_reboot; + SvgIcon icon_doom; + SvgIcon icon_procmgr; + SvgIcon icon_mandelbrot; + SvgIcon icon_devexplorer; + bool ctx_menu_open; int ctx_menu_x, ctx_menu_y; @@ -63,6 +87,8 @@ struct DesktopState { Rect net_icon_rect; int screen_w, screen_h; + + DesktopSettings settings; }; // Forward declarations - implemented in main.cpp diff --git a/programs/include/gui/font.hpp b/programs/include/gui/font.hpp index 11c5f6a..b96b2de 100644 --- a/programs/include/gui/font.hpp +++ b/programs/include/gui/font.hpp @@ -1,12 +1,13 @@ /* * font.hpp - * ZenithOS 8x16 VGA bitmap font rendering + * ZenithOS text rendering — TrueType with bitmap fallback * Copyright (c) 2025 Daniel Hammer */ #pragma once #include "gui/gui.hpp" #include "gui/framebuffer.hpp" +#include "gui/truetype.hpp" namespace gui { @@ -16,6 +17,30 @@ static constexpr int FONT_HEIGHT = 16; // Defined in font_data.cpp extern const uint8_t font_data[256 * 16]; +// Dynamic font height: TTF line height or 16 (bitmap fallback) +inline int system_font_height() { + if (fonts::system_font && fonts::system_font->valid) + return fonts::system_font->get_line_height(fonts::UI_SIZE); + return FONT_HEIGHT; +} + +// Dynamic mono font cell dimensions +inline int mono_cell_width() { + if (fonts::mono && fonts::mono->valid) { + // Monospace: all glyphs have the same advance + GlyphCache* gc = fonts::mono->get_cache(fonts::TERM_SIZE); + CachedGlyph* g = fonts::mono->get_glyph(gc, 'M'); + if (g) return g->advance; + } + return FONT_WIDTH; +} + +inline int mono_cell_height() { + if (fonts::mono && fonts::mono->valid) + return fonts::mono->get_line_height(fonts::TERM_SIZE); + return FONT_HEIGHT; +} + 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++) { @@ -43,18 +68,29 @@ inline void draw_char_bg(Framebuffer& fb, int x, int y, char c, Color fg, Color } inline void draw_text(Framebuffer& fb, int x, int y, const char* text, Color fg) { + if (fonts::system_font && fonts::system_font->valid) { + fonts::system_font->draw(fb, x, y, text, fg, fonts::UI_SIZE); + return; + } 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) { + if (fonts::system_font && fonts::system_font->valid) { + fonts::system_font->draw_bg(fb, x, y, text, fg, bg, fonts::UI_SIZE); + return; + } 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) { + if (fonts::system_font && fonts::system_font->valid) { + return fonts::system_font->measure_text(text, fonts::UI_SIZE); + } int len = 0; while (text[len]) len++; return len * FONT_WIDTH; diff --git a/programs/include/gui/gui.hpp b/programs/include/gui/gui.hpp index 2f178bc..ddb9c6f 100644 --- a/programs/include/gui/gui.hpp +++ b/programs/include/gui/gui.hpp @@ -25,13 +25,13 @@ inline fixed_t fixed_from_parts(int whole, int frac_num, int 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) { + static constexpr Color from_rgb(uint8_t r, uint8_t g, uint8_t b) { return {r, g, b, 255}; } + static constexpr Color from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { return {r, g, b, a}; } + static constexpr 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; } + constexpr 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 diff --git a/programs/include/gui/stb_math.h b/programs/include/gui/stb_math.h new file mode 100644 index 0000000..07875b2 --- /dev/null +++ b/programs/include/gui/stb_math.h @@ -0,0 +1,95 @@ +/* + * stb_math.h + * Math functions for stb_truetype in freestanding environment + * Copyright (c) 2026 Daniel Hammer +*/ + +#ifndef STB_MATH_H +#define STB_MATH_H + +#ifdef __cplusplus +extern "C" { +#endif + +static inline double stb_floor(double x) { + double i = (double)(long long)x; + return (x < i) ? i - 1.0 : i; +} + +static inline double stb_ceil(double x) { + double f = stb_floor(x); + return (x > f) ? f + 1.0 : f; +} + +static inline double stb_fabs(double x) { + return x < 0.0 ? -x : x; +} + +static inline double stb_fmod(double x, double y) { + if (y == 0.0) return 0.0; + return x - (double)((long long)(x / y)) * y; +} + +static inline double stb_sqrt(double x) { + if (x <= 0.0) return 0.0; + double guess = x; + for (int i = 0; i < 30; i++) + guess = (guess + x / guess) * 0.5; + return guess; +} + +static inline double stb_pow(double base, double exp) { + if (exp == 0.0) return 1.0; + if (exp == 1.0) return base; + if (base == 0.0) return 0.0; + // Integer exponent fast path + if (exp == (double)(long long)exp) { + long long e = (long long)exp; + int neg = 0; + if (e < 0) { neg = 1; e = -e; } + double r = 1.0; + double b = base; + while (e > 0) { + if (e & 1) r *= b; + b *= b; + e >>= 1; + } + return neg ? 1.0 / r : r; + } + return 0.0; +} + +static inline double stb_cos(double x) { + // Reduce to [0, 2*pi] + const double PI = 3.14159265358979323846; + const double TWO_PI = 6.28318530717958647692; + x = stb_fmod(stb_fabs(x), TWO_PI); + // Taylor series: cos(x) = 1 - x^2/2! + x^4/4! - x^6/6! + ... + double x2 = x * x; + double term = 1.0; + double result = 1.0; + for (int i = 1; i <= 10; i++) { + term *= -x2 / (double)((2 * i - 1) * (2 * i)); + result += term; + } + return result; +} + +static inline double stb_acos(double x) { + // Clamp input + if (x <= -1.0) return 3.14159265358979323846; + if (x >= 1.0) return 0.0; + // Polynomial approximation (Abramowitz & Stegun style) + double ax = stb_fabs(x); + double result = (-0.0187293 * ax + 0.0742610) * ax - 0.2121144; + result = (result * ax + 1.5707288) * stb_sqrt(1.0 - ax); + if (x < 0.0) + return 3.14159265358979323846 - result; + return result; +} + +#ifdef __cplusplus +} +#endif + +#endif // STB_MATH_H diff --git a/programs/include/gui/stb_truetype.h b/programs/include/gui/stb_truetype.h new file mode 100644 index 0000000..90a5c2e --- /dev/null +++ b/programs/include/gui/stb_truetype.h @@ -0,0 +1,5079 @@ +// stb_truetype.h - v1.26 - public domain +// authored from 2009-2021 by Sean Barrett / RAD Game Tools +// +// ======================================================================= +// +// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES +// +// This library does no range checking of the offsets found in the file, +// meaning an attacker can use it to read arbitrary memory. +// +// ======================================================================= +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe +// Cass Everitt Martins Mozeiko github:aloucks +// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam +// Brian Hook Omar Cornut github:vassvik +// Walter van Niftrik Ryan Griege +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. Brian Costabile +// Ken Voskuil (kaesve) Yakov Galka +// +// VERSION HISTORY +// +// 1.26 (2021-08-28) fix broken rasterizer +// 1.25 (2021-07-11) many fixes +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places need to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); +// Query the font vertical metrics without having to create a font first. + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); +// If skip != 0, this tells stb_truetype to skip any codepoints for which +// there is no corresponding glyph. If skip=0, which is the default, then +// codepoints without a glyph recived the font's "missing character" glyph, +// typically an empty box by convention. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publicly so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. +// Returns 0 if the character codepoint is not defined in the font. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 +// table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + +typedef struct stbtt_kerningentry +{ + int glyph1; // use stbtt_FindGlyphIndex + int glyph2; + int advance; +} stbtt_kerningentry; + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); +// Retrieves a complete list of all of the kerning pairs provided by the font +// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. +// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of contours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +STBTT_DEF unsigned char *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl); +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); +// fills svg with the character's SVG data. +// returns data size or 0 if SVG not found. + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +// since most people won't use this, find this table the first time it's needed +static int stbtt__get_svg(stbtt_fontinfo *info) +{ + stbtt_uint32 t; + if (info->svg < 0) { + t = stbtt__find_table(info->data, info->fontstart, "SVG "); + if (t) { + stbtt_uint32 offset = ttULONG(info->data + t + 2); + info->svg = t + offset; + } else { + info->svg = 0; + } + } + return info->svg; +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + info->svg = -1; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start, last; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + last = ttUSHORT(data + endCount + 2*item); + if (unicode_codepoint < start || unicode_codepoint > last) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours < 0) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0 && vertices) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // FALLTHROUGH + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && b0 < 32) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) +{ + stbtt_uint8 *data = info->data + info->kern; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + return ttUSHORT(data+10); +} + +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) +{ + stbtt_uint8 *data = info->data + info->kern; + int k, length; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + length = ttUSHORT(data+10); + if (table_length < length) + length = table_length; + + for (k = 0; k < length; k++) + { + table[k].glyph1 = ttUSHORT(data+18+(k*6)); + table[k].glyph2 = ttUSHORT(data+20+(k*6)); + table[k].advance = ttSHORT(data+22+(k*6)); + } + + return length; +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch (coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + break; + } + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + break; + } + + default: return -1; // unsupported + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch (classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + break; + } + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + break; + } + + default: + return -1; // Unsupported definition type, return an error. + } + + // "All glyphs not assigned to a class fall into class 0". (OpenType spec) + return 0; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i, sti; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i= pairSetCount) return 0; + + needle=glyph2; + r=pairValueCount-1; + l=0; + + // Binary search. + while (l <= r) { + stbtt_uint16 secondGlyph; + stbtt_uint8 *pairValue; + m = (l + r) >> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } else + return 0; + break; + } + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + if (valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats? + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + stbtt_uint8 *class1Records, *class2Records; + stbtt_int16 xAdvance; + + if (glyph1class < 0 || glyph1class >= class1Count) return 0; // malformed + if (glyph2class < 0 || glyph2class >= class2Count) return 0; // malformed + + class1Records = table + 16; + class2Records = class1Records + 2 * (glyph1class * class2Count); + xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } else + return 0; + break; + } + + default: + return 0; // Unsupported position format + } + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) +{ + int i; + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); + + int numEntries = ttUSHORT(svg_doc_list); + stbtt_uint8 *svg_docs = svg_doc_list + 2; + + for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) + return svg_doc; + } + return 0; +} + +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) +{ + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc; + + if (info->svg == 0) + return 0; + + svg_doc = stbtt_FindSVGDoc(info, gl); + if (svg_doc != NULL) { + *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); + return ttULONG(svg_doc + 8); + } else { + return 0; + } +} + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) +{ + return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static float stbtt__sized_trapezoid_area(float height, float top_width, float bottom_width) +{ + STBTT_assert(top_width >= 0); + STBTT_assert(bottom_width >= 0); + return (top_width + bottom_width) / 2.0f * height; +} + +static float stbtt__position_trapezoid_area(float height, float tx0, float tx1, float bx0, float bx1) +{ + return stbtt__sized_trapezoid_area(height, tx1 - tx0, bx1 - bx0); +} + +static float stbtt__sized_triangle_area(float height, float width) +{ + return height * width / 2; +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = (sy1 - sy0) * e->direction; + STBTT_assert(x >= 0 && x < len); + scanline[x] += stbtt__position_trapezoid_area(height, x_top, x+1.0f, x_bottom, x+1.0f); + scanline_fill[x] += height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, y_final, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + STBTT_assert(dy >= 0); + STBTT_assert(dx >= 0); + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = y_top + dy * (x1+1 - x0); + + // compute intersection with y axis at x2 + y_final = y_top + dy * (x2 - x0); + + // x1 x_top x2 x_bottom + // y_top +------|-----+------------+------------+--------|---+------------+ + // | | | | | | + // | | | | | | + // sy0 | Txxxxx|............|............|............|............| + // y_crossing | *xxxxx.......|............|............|............| + // | | xxxxx..|............|............|............| + // | | /- xx*xxxx........|............|............| + // | | dy < | xxxxxx..|............|............| + // y_final | | \- | xx*xxx.........|............| + // sy1 | | | | xxxxxB...|............| + // | | | | | | + // | | | | | | + // y_bottom +------------+------------+------------+------------+------------+ + // + // goal is to measure the area covered by '.' in each pixel + + // if x2 is right at the right edge of x1, y_crossing can blow up, github #1057 + // @TODO: maybe test against sy1 rather than y_bottom? + if (y_crossing > y_bottom) + y_crossing = y_bottom; + + sign = e->direction; + + // area of the rectangle covered from sy0..y_crossing + area = sign * (y_crossing-sy0); + + // area of the triangle (x_top,sy0), (x1+1,sy0), (x1+1,y_crossing) + scanline[x1] += stbtt__sized_triangle_area(area, x1+1 - x_top); + + // check if final y_crossing is blown up; no test case for this + if (y_final > y_bottom) { + y_final = y_bottom; + dy = (y_final - y_crossing ) / (x2 - (x1+1)); // if denom=0, y_final = y_crossing, so y_final <= y_bottom + } + + // in second pixel, area covered by line segment found in first pixel + // is always a rectangle 1 wide * the height of that line segment; this + // is exactly what the variable 'area' stores. it also gets a contribution + // from the line segment within it. the THIRD pixel will get the first + // pixel's rectangle contribution, the second pixel's rectangle contribution, + // and its own contribution. the 'own contribution' is the same in every pixel except + // the leftmost and rightmost, a trapezoid that slides down in each pixel. + // the second pixel's contribution to the third pixel will be the + // rectangle 1 wide times the height change in the second pixel, which is dy. + + step = sign * dy * 1; // dy is dy/dx, change in y for every 1 change in x, + // which multiplied by 1-pixel-width is how much pixel area changes for each step in x + // so the area advances by 'step' every time + + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; // area of trapezoid is 1*step/2 + area += step; + } + STBTT_assert(STBTT_fabs(area) <= 1.01f); // accumulated error from area += step unless we round step down + STBTT_assert(sy1 > y_final-0.01f); + + // area covered in the last pixel is the rectangle from all the pixels to the left, + // plus the trapezoid filled by the line segment in this pixel all the way to the right edge + scanline[x2] += area + sign * stbtt__position_trapezoid_area(sy1-y_final, (float) x2, x2+1.0f, x_bottom, x2+1.0f); + + // the rest of the line is filled based on the total height of the line segment in this pixel + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + // note though that this does happen some of the time because + // x_top and x_bottom can be extrapolated at the top & bottom of + // the shape and actually lie outside the bounding box + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp rounding error i think + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + spc->skip_missing = 0; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc->skip_missing = skip; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + int missing_glyph_added = 0; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + if (glyph == 0) + missing_glyph_added = 1; + } + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, missing_glyph = -1, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed && r->w != 0 && r->h != 0) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + + if (glyph == 0) + missing_glyph = j; + } else if (spc->skip_missing) { + return_value = 0; + } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { + ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float) i_ascent * scale; + *descent = (float) i_descent * scale; + *lineGap = (float) i_lineGap * scale; +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + + orig[0] = x; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + a*x^2 + b*x + c = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + if (scale == 0) return NULL; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + // distance from singular values (in the same units as the pixel grid) + const float eps = 1./1024, eps2 = eps*eps; + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist < eps) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 >= eps2) + precompute[i] = 1.0f / len2; + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + if (verts[i].type == STBTT_vline && precompute[i] != 0.0f) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + float dist,dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3] = {0.f,0.f,0.f}; + float px,py,t,it,dist2; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (STBTT_fabs(a) < eps2) { // if a is 0, it's linear + if (STBTT_fabs(b) >= eps2) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.25 (2021-07-11) many fixes +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/programs/include/gui/terminal.hpp b/programs/include/gui/terminal.hpp index 8f28453..e40179c 100644 --- a/programs/include/gui/terminal.hpp +++ b/programs/include/gui/terminal.hpp @@ -423,6 +423,11 @@ static inline void terminal_feed(TerminalState* t, const char* data, int len) { } static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, int ph) { + int cell_w = mono_cell_width(); + int cell_h = mono_cell_height(); + bool use_ttf = fonts::mono && fonts::mono->valid; + GlyphCache* gc = use_ttf ? fonts::mono->get_cache(fonts::TERM_SIZE) : nullptr; + // Fill background uint32_t bg_px = colors::TERM_BG.to_pixel(); int total = pw * ph; @@ -431,8 +436,8 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i } // Render each visible cell - int visible_rows = ph / FONT_HEIGHT; - int visible_cols = pw / FONT_WIDTH; + int visible_rows = ph / cell_h; + int visible_cols = pw / cell_w; if (visible_rows > t->rows) visible_rows = t->rows; if (visible_cols > t->cols) visible_cols = t->cols; @@ -441,17 +446,16 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i int idx = r * t->cols + c; TermCell& cell = t->cells[idx]; - int px = c * FONT_WIDTH; - int py = r * FONT_HEIGHT; + int px = c * cell_w; + int py = r * cell_h; 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++) { + for (int fy = 0; fy < cell_h; fy++) { int dy = py + fy; if (dy >= ph) break; - for (int fx = 0; fx < FONT_WIDTH; fx++) { + for (int fx = 0; fx < cell_w; fx++) { int dx = px + fx; if (dx >= pw) break; pixels[dy * pw + dx] = cell_bg; @@ -460,16 +464,23 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i // 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; + if (use_ttf) { + int baseline = py + gc->ascent; + fonts::mono->draw_char_to_buffer(pixels, pw, ph, + px, baseline, (unsigned char)cell.ch, cell.fg, gc); + } else { + uint32_t cell_fg = cell.fg.to_pixel(); + 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; + } } } } @@ -479,13 +490,13 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i // 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; + int cx = t->cursor_x * cell_w; + int cy = t->cursor_y * cell_h; uint32_t cursor_px = colors::WHITE.to_pixel(); - for (int fy = 0; fy < FONT_HEIGHT; fy++) { + for (int fy = 0; fy < cell_h; fy++) { int dy = cy + fy; if (dy >= ph) break; - for (int fx = 0; fx < FONT_WIDTH; fx++) { + for (int fx = 0; fx < cell_w; fx++) { int dx = cx + fx; if (dx >= pw) break; pixels[dy * pw + dx] = cursor_px; @@ -496,17 +507,23 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i 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; + if (use_ttf) { + int baseline = cy + gc->ascent; + fonts::mono->draw_char_to_buffer(pixels, pw, ph, + cx, baseline, (unsigned char)ch, colors::BLACK, gc); + } else { + 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; + } } } } @@ -515,6 +532,58 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i } } +static inline void terminal_resize(TerminalState* t, int new_cols, int new_rows) { + if (new_cols == t->cols && new_rows == t->rows) return; + if (new_cols < 1 || new_rows < 1) return; + + int new_total = new_cols * new_rows; + TermCell* new_cells = (TermCell*)zenith::alloc(new_total * sizeof(TermCell)); + TermCell* new_alt = (TermCell*)zenith::alloc(new_total * sizeof(TermCell)); + + // Clear new buffers + for (int i = 0; i < new_total; i++) { + new_cells[i] = {' ', colors::TERM_FG, colors::TERM_BG}; + new_alt[i] = {' ', colors::TERM_FG, colors::TERM_BG}; + } + + // Copy existing content (as much as fits) + int copy_rows = t->rows < new_rows ? t->rows : new_rows; + int copy_cols = t->cols < new_cols ? t->cols : new_cols; + + // If cursor is beyond the new grid, scroll content up to keep cursor visible + int row_offset = 0; + if (t->cursor_y >= new_rows) { + row_offset = t->cursor_y - new_rows + 1; + } + + for (int r = 0; r < copy_rows && (r + row_offset) < t->rows; r++) { + for (int c = 0; c < copy_cols; c++) { + new_cells[r * new_cols + c] = t->cells[(r + row_offset) * t->cols + c]; + } + } + + if (t->cells) zenith::mfree(t->cells); + if (t->alt_cells) zenith::mfree(t->alt_cells); + + t->cells = new_cells; + t->alt_cells = new_alt; + + int old_cols = t->cols; + t->cols = new_cols; + t->rows = new_rows; + + // Adjust cursor position + t->cursor_y -= row_offset; + if (t->cursor_x >= new_cols) t->cursor_x = new_cols - 1; + if (t->cursor_y >= new_rows) t->cursor_y = new_rows - 1; + if (t->cursor_y < 0) t->cursor_y = 0; + + // Notify child process of new terminal size + if (t->child_pid > 0) { + zenith::childio_settermsz(t->child_pid, new_cols, new_rows); + } +} + static inline void terminal_handle_key(TerminalState* t, const Zenith::KeyEvent& key) { if (t->child_pid > 0) { zenith::childio_writekey(t->child_pid, &key); diff --git a/programs/include/gui/truetype.hpp b/programs/include/gui/truetype.hpp new file mode 100644 index 0000000..ef0192c --- /dev/null +++ b/programs/include/gui/truetype.hpp @@ -0,0 +1,335 @@ +/* + * truetype.hpp + * ZenithOS TrueType font rendering via stb_truetype + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once +#include +#include +#include +#include +#include "gui/gui.hpp" +#include "gui/framebuffer.hpp" + +// Forward-declare stbtt_fontinfo to avoid including stb_truetype.h in every TU. +// The actual struct is defined in stb_truetype.h and only used in truetype.hpp +// method bodies (which are inline but only instantiated where stb_truetype.h +// is also included — i.e. stb_truetype_impl.cpp). We include the full header +// here since our inline methods need the complete type. +#include "gui/stb_math.h" + +// We need the stb macros defined before including stb_truetype.h (header-only mode) +#ifndef STBTT_ifloor +#define STBTT_ifloor(x) ((int) stb_floor(x)) +#define STBTT_iceil(x) ((int) stb_ceil(x)) +#define STBTT_sqrt(x) stb_sqrt(x) +#define STBTT_pow(x,y) stb_pow(x,y) +#define STBTT_fmod(x,y) stb_fmod(x,y) +#define STBTT_cos(x) stb_cos(x) +#define STBTT_acos(x) stb_acos(x) +#define STBTT_fabs(x) stb_fabs(x) +#define STBTT_malloc(x,u) ((void)(u), zenith::malloc(x)) +#define STBTT_free(x,u) ((void)(u), zenith::mfree(x)) +#define STBTT_memcpy(d,s,n) zenith::memcpy(d,s,n) +#define STBTT_memset(d,v,n) zenith::memset(d,v,n) +#define STBTT_strlen(x) zenith::slen(x) +#define STBTT_assert(x) ((void)(x)) +#endif + +#include "gui/stb_truetype.h" + +namespace gui { + +struct CachedGlyph { + uint8_t* bitmap; + int width, height; + int xoff, yoff; + int advance; + bool loaded; +}; + +struct GlyphCache { + CachedGlyph glyphs[128]; + int pixel_size; + float scale; + int ascent, descent, line_gap; + int line_height; +}; + +struct TrueTypeFont { + stbtt_fontinfo info; + uint8_t* data; + GlyphCache caches[4]; + int cache_count; + bool valid; + + bool init(const char* vfs_path) { + valid = false; + data = nullptr; + cache_count = 0; + + int fd = zenith::open(vfs_path); + if (fd < 0) return false; + + uint64_t size = zenith::getsize(fd); + if (size == 0 || size > 1024 * 1024) { + zenith::close(fd); + return false; + } + + data = (uint8_t*)zenith::alloc(size); + if (!data) { + zenith::close(fd); + return false; + } + + zenith::read(fd, data, 0, size); + zenith::close(fd); + + if (!stbtt_InitFont(&info, data, stbtt_GetFontOffsetForIndex(data, 0))) { + zenith::free(data); + data = nullptr; + return false; + } + + valid = true; + return true; + } + + GlyphCache* get_cache(int pixel_size) { + // Search existing caches + for (int i = 0; i < cache_count; i++) { + if (caches[i].pixel_size == pixel_size) + return &caches[i]; + } + + // Create new cache + if (cache_count >= 4) return &caches[0]; // fallback to first + + GlyphCache* gc = &caches[cache_count++]; + gc->pixel_size = pixel_size; + gc->scale = stbtt_ScaleForPixelHeight(&info, (float)pixel_size); + + int asc, desc, lg; + stbtt_GetFontVMetrics(&info, &asc, &desc, &lg); + gc->ascent = (int)(asc * gc->scale); + gc->descent = (int)(desc * gc->scale); + gc->line_gap = (int)(lg * gc->scale); + gc->line_height = gc->ascent - gc->descent + gc->line_gap; + + for (int i = 0; i < 128; i++) { + gc->glyphs[i].bitmap = nullptr; + gc->glyphs[i].loaded = false; + } + + return gc; + } + + CachedGlyph* get_glyph(GlyphCache* gc, int codepoint) { + if (codepoint < 0 || codepoint >= 128) return nullptr; + CachedGlyph* g = &gc->glyphs[codepoint]; + if (g->loaded) return g; + + g->loaded = true; + int advance, lsb; + stbtt_GetCodepointHMetrics(&info, codepoint, &advance, &lsb); + g->advance = (int)(advance * gc->scale); + + int x0, y0, x1, y1; + stbtt_GetCodepointBitmapBox(&info, codepoint, gc->scale, gc->scale, + &x0, &y0, &x1, &y1); + g->width = x1 - x0; + g->height = y1 - y0; + g->xoff = x0; + g->yoff = y0; + + if (g->width > 0 && g->height > 0) { + g->bitmap = (uint8_t*)zenith::malloc(g->width * g->height); + stbtt_MakeCodepointBitmap(&info, g->bitmap, g->width, g->height, + g->width, gc->scale, gc->scale, codepoint); + } + + return g; + } + + int measure_text(const char* text, int pixel_size) { + if (!valid) return 0; + GlyphCache* gc = get_cache(pixel_size); + int w = 0; + for (int i = 0; text[i]; i++) { + CachedGlyph* g = get_glyph(gc, (unsigned char)text[i]); + if (g) w += g->advance; + } + return w; + } + + int get_line_height(int pixel_size) { + if (!valid) return 16; + GlyphCache* gc = get_cache(pixel_size); + return gc->line_height; + } + + void draw(Framebuffer& fb, int x, int y, const char* text, + Color color, int pixel_size) { + if (!valid) return; + GlyphCache* gc = get_cache(pixel_size); + int cx = x; + int baseline = y + gc->ascent; + + for (int i = 0; text[i]; i++) { + CachedGlyph* g = get_glyph(gc, (unsigned char)text[i]); + if (!g) continue; + + if (g->bitmap) { + int gx = cx + g->xoff; + int gy = baseline + g->yoff; + for (int row = 0; row < g->height; row++) { + for (int col = 0; col < g->width; col++) { + uint8_t alpha = g->bitmap[row * g->width + col]; + if (alpha > 0) { + Color c = {color.r, color.g, color.b, alpha}; + fb.put_pixel_alpha(gx + col, gy + row, c); + } + } + } + } + cx += g->advance; + } + } + + void draw_bg(Framebuffer& fb, int x, int y, const char* text, + Color fg, Color bg, int pixel_size) { + if (!valid) return; + GlyphCache* gc = get_cache(pixel_size); + + // Fill background for the text extent + int tw = measure_text(text, pixel_size); + fb.fill_rect(x, y, tw, gc->line_height, bg); + + // Then draw foreground + draw(fb, x, y, text, fg, pixel_size); + } + + void draw_to_buffer(uint32_t* pixels, int buf_w, int buf_h, + int x, int y, const char* text, + Color color, int pixel_size) { + if (!valid) return; + GlyphCache* gc = get_cache(pixel_size); + int cx = x; + int baseline = y + gc->ascent; + + for (int i = 0; text[i]; i++) { + CachedGlyph* g = get_glyph(gc, (unsigned char)text[i]); + if (!g) continue; + + if (g->bitmap) { + int gx = cx + g->xoff; + int gy = baseline + g->yoff; + for (int row = 0; row < g->height; row++) { + int dy = gy + row; + if (dy < 0 || dy >= buf_h) continue; + for (int col = 0; col < g->width; col++) { + int dx = gx + col; + if (dx < 0 || dx >= buf_w) continue; + uint8_t alpha = g->bitmap[row * g->width + col]; + if (alpha == 0) continue; + + if (alpha == 255) { + pixels[dy * buf_w + dx] = + 0xFF000000 | ((uint32_t)color.r << 16) | + ((uint32_t)color.g << 8) | color.b; + } else { + uint32_t dst = pixels[dy * buf_w + dx]; + uint8_t dr = (dst >> 16) & 0xFF; + uint8_t dg = (dst >> 8) & 0xFF; + uint8_t db = dst & 0xFF; + uint32_t a = alpha, inv_a = 255 - alpha; + uint32_t rr = (a * color.r + inv_a * dr + 128) / 255; + uint32_t gg = (a * color.g + inv_a * dg + 128) / 255; + uint32_t bb = (a * color.b + inv_a * db + 128) / 255; + pixels[dy * buf_w + dx] = + 0xFF000000 | (rr << 16) | (gg << 8) | bb; + } + } + } + } + cx += g->advance; + } + } + + // Draw single character to buffer, returning advance width + int draw_char_to_buffer(uint32_t* pixels, int buf_w, int buf_h, + int x, int baseline, int codepoint, + Color color, GlyphCache* gc) { + CachedGlyph* g = get_glyph(gc, codepoint); + if (!g) return 0; + + if (g->bitmap) { + int gx = x + g->xoff; + int gy = baseline + g->yoff; + for (int row = 0; row < g->height; row++) { + int dy = gy + row; + if (dy < 0 || dy >= buf_h) continue; + for (int col = 0; col < g->width; col++) { + int dx = gx + col; + if (dx < 0 || dx >= buf_w) continue; + uint8_t alpha = g->bitmap[row * g->width + col]; + if (alpha == 0) continue; + + if (alpha == 255) { + pixels[dy * buf_w + dx] = + 0xFF000000 | ((uint32_t)color.r << 16) | + ((uint32_t)color.g << 8) | color.b; + } else { + uint32_t dst = pixels[dy * buf_w + dx]; + uint8_t dr = (dst >> 16) & 0xFF; + uint8_t dg = (dst >> 8) & 0xFF; + uint8_t db = dst & 0xFF; + uint32_t a = alpha, inv_a = 255 - alpha; + uint32_t rr = (a * color.r + inv_a * dr + 128) / 255; + uint32_t gg = (a * color.g + inv_a * dg + 128) / 255; + uint32_t bb = (a * color.b + inv_a * db + 128) / 255; + pixels[dy * buf_w + dx] = + 0xFF000000 | (rr << 16) | (gg << 8) | bb; + } + } + } + } + return g->advance; + } +}; + +// Global font manager +namespace fonts { + inline TrueTypeFont* system_font = nullptr; + inline TrueTypeFont* system_bold = nullptr; + inline TrueTypeFont* mono = nullptr; + inline TrueTypeFont* mono_bold = nullptr; + + inline int UI_SIZE = 18; + inline int TITLE_SIZE = 18; + inline int TERM_SIZE = 18; + inline int LARGE_SIZE = 28; + + inline bool init() { + auto load = [](const char* path) -> TrueTypeFont* { + TrueTypeFont* f = (TrueTypeFont*)zenith::malloc(sizeof(TrueTypeFont)); + zenith::memset(f, 0, sizeof(TrueTypeFont)); + if (!f->init(path)) { + zenith::mfree(f); + return nullptr; + } + return f; + }; + + system_font = load("0:/fonts/Roboto-Medium.ttf"); + system_bold = load("0:/fonts/Roboto-Bold.ttf"); + mono = load("0:/fonts/JetBrainsMono-Regular.ttf"); + mono_bold = load("0:/fonts/JetBrainsMono-Bold.ttf"); + + return system_font != nullptr; + } +} + +} // namespace gui diff --git a/programs/include/gui/widgets.hpp b/programs/include/gui/widgets.hpp index 121ebf8..b46a6de 100644 --- a/programs/include/gui/widgets.hpp +++ b/programs/include/gui/widgets.hpp @@ -79,7 +79,7 @@ struct Button { // Center text int tw = text_width(text); int tx = bounds.x + (bounds.w - tw) / 2; - int ty = bounds.y + (bounds.h - FONT_HEIGHT) / 2; + int ty = bounds.y + (bounds.h - system_font_height()) / 2; draw_text(fb, tx, ty, text, fg); } @@ -150,7 +150,7 @@ struct IconButton { // Draw text if (text) { - int ty = bounds.y + (bounds.h - FONT_HEIGHT) / 2; + int ty = bounds.y + (bounds.h - system_font_height()) / 2; draw_text(fb, content_x, ty, text, text_color); } } @@ -206,13 +206,18 @@ struct TextBox { // Draw text with 4px padding int tx = bounds.x + 4; - int ty = bounds.y + (bounds.h - FONT_HEIGHT) / 2; + int fh = system_font_height(); + int ty = bounds.y + (bounds.h - fh) / 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); + // Measure text up to cursor position for proportional fonts + char prefix[256]; + for (int i = 0; i < cursor && i < 255; i++) prefix[i] = text[i]; + prefix[cursor < 255 ? cursor : 255] = '\0'; + int cx = tx + text_width(prefix); + draw_vline(fb, cx, ty, fh, cursor_color); } } diff --git a/programs/include/gui/window.hpp b/programs/include/gui/window.hpp index 904e78b..7fdc603 100644 --- a/programs/include/gui/window.hpp +++ b/programs/include/gui/window.hpp @@ -64,6 +64,9 @@ struct Window { WindowPollCallback on_poll; void* app_data; + bool external; // true = shared-memory window from external process + int ext_win_id; // window server ID (valid when external == true) + Rect titlebar_rect() const { return {frame.x, frame.y, frame.w, TITLEBAR_HEIGHT}; } diff --git a/programs/include/zenith/syscall.h b/programs/include/zenith/syscall.h index ec45d0e..0a9e3da 100644 --- a/programs/include/zenith/syscall.h +++ b/programs/include/zenith/syscall.h @@ -284,4 +284,38 @@ namespace zenith { return (int)syscall3(Zenith::SYS_CHILDIO_SETTERMSZ, (uint64_t)childPid, (uint64_t)cols, (uint64_t)rows); } + // Process listing / kill + inline int proclist(Zenith::ProcInfo* buf, int max) { + return (int)syscall2(Zenith::SYS_PROCLIST, (uint64_t)buf, (uint64_t)max); + } + inline int kill(int pid) { + return (int)syscall1(Zenith::SYS_KILL, (uint64_t)pid); + } + inline int devlist(Zenith::DevInfo* buf, int max) { + return (int)syscall2(Zenith::SYS_DEVLIST, (uint64_t)buf, (uint64_t)max); + } + + // Window server + inline int win_create(const char* title, int w, int h, Zenith::WinCreateResult* result) { + return (int)syscall4(Zenith::SYS_WINCREATE, (uint64_t)title, (uint64_t)w, (uint64_t)h, (uint64_t)result); + } + inline int win_destroy(int id) { + return (int)syscall1(Zenith::SYS_WINDESTROY, (uint64_t)id); + } + inline int win_present(int id) { + return (int)syscall1(Zenith::SYS_WINPRESENT, (uint64_t)id); + } + inline int win_poll(int id, Zenith::WinEvent* event) { + return (int)syscall2(Zenith::SYS_WINPOLL, (uint64_t)id, (uint64_t)event); + } + inline int win_enumerate(Zenith::WinInfo* info, int max) { + return (int)syscall2(Zenith::SYS_WINENUM, (uint64_t)info, (uint64_t)max); + } + inline uint64_t win_map(int id) { + return (uint64_t)syscall1(Zenith::SYS_WINMAP, (uint64_t)id); + } + inline int win_sendevent(int id, const Zenith::WinEvent* event) { + return (int)syscall2(Zenith::SYS_WINSENDEVENT, (uint64_t)id, (uint64_t)event); + } + } diff --git a/programs/src/clear/main.cpp b/programs/src/clear/main.cpp index a6210bc..0e1a5d3 100644 --- a/programs/src/clear/main.cpp +++ b/programs/src/clear/main.cpp @@ -1,26 +1,12 @@ /* * main.cpp - * clear - Clear terminal screen and framebuffer + * clear - Clear terminal screen * Copyright (c) 2025-2026 Daniel Hammer */ #include extern "C" void _start() { - // Clear the raw framebuffer (needed after graphical programs like DOOM) - Zenith::FbInfo fb; - zenith::fb_info(&fb); - uint8_t* pixels = (uint8_t*)zenith::fb_map(); - if (pixels) { - for (uint64_t y = 0; y < fb.height; y++) { - uint32_t* row = (uint32_t*)(pixels + y * fb.pitch); - for (uint64_t x = 0; x < fb.width; x++) { - row[x] = 0x00000000; - } - } - } - - // Reset the text console zenith::print("\033[2J"); // Clear entire screen zenith::print("\033[H"); // Move cursor to top-left zenith::exit(0); diff --git a/programs/src/desktop/Makefile b/programs/src/desktop/Makefile index 4f43cd3..926befb 100644 --- a/programs/src/desktop/Makefile +++ b/programs/src/desktop/Makefile @@ -20,7 +20,7 @@ LINK_LD := ../../link.ld BINDIR := ../../bin OBJDIR := obj -# ---- Compiler flags ---- +# ---- C++ compiler flags ---- CXXFLAGS := \ -std=gnu++20 \ @@ -40,10 +40,8 @@ CXXFLAGS := \ -fdata-sections \ -m64 \ -march=x86-64 \ - -mno-80387 \ - -mno-mmx \ - -mno-sse \ - -mno-sse2 \ + -msse \ + -msse2 \ -mno-red-zone \ -mcmodel=small \ -I $(PROG_INC) \ @@ -61,9 +59,9 @@ LDFLAGS := \ -z max-page-size=0x1000 \ -T $(LINK_LD) -# ---- Source files ---- +# ---- C++ source files ---- -SRCS := main.cpp font_data.cpp app_terminal.cpp app_filemanager.cpp app_sysinfo.cpp app_calculator.cpp app_texteditor.cpp app_klog.cpp app_wiki.cpp app_settings.cpp +SRCS := main.cpp font_data.cpp stb_truetype_impl.cpp app_terminal.cpp app_filemanager.cpp app_sysinfo.cpp app_calculator.cpp app_texteditor.cpp app_klog.cpp app_wiki.cpp app_procmgr.cpp app_mandelbrot.cpp app_devexplorer.cpp app_settings.cpp app_doom.cpp OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o)) # ---- Target ---- @@ -78,6 +76,7 @@ $(TARGET): $(OBJS) $(LINK_LD) Makefile mkdir -p $(BINDIR)/os $(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) -o $@ +# C++ sources $(OBJDIR)/%.o: %.cpp Makefile mkdir -p $(OBJDIR) $(CXX) $(CXXFLAGS) -c $< -o $@ diff --git a/programs/src/desktop/app_calculator.cpp b/programs/src/desktop/app_calculator.cpp index 6ddfad5..64685ea 100644 --- a/programs/src/desktop/app_calculator.cpp +++ b/programs/src/desktop/app_calculator.cpp @@ -231,10 +231,18 @@ static void calculator_on_draw(Window* win, Framebuffer& fb) { c.fill_rect(0, 0, c.w, CALC_DISPLAY_H, Color::from_rgb(0x2D, 0x2D, 0x2D)); // Display text (right-aligned, 2x scale) - int text_len = zenith::slen(cs->display_str); - int text_w = text_len * FONT_WIDTH * 2; + int text_w; + int large_h; + if (fonts::system_font && fonts::system_font->valid) { + text_w = fonts::system_font->measure_text(cs->display_str, fonts::LARGE_SIZE); + large_h = fonts::system_font->get_line_height(fonts::LARGE_SIZE); + } else { + int text_len = zenith::slen(cs->display_str); + text_w = text_len * FONT_WIDTH * 2; + large_h = FONT_HEIGHT * 2; + } int tx = c.w - text_w - 12; - int ty = (CALC_DISPLAY_H - FONT_HEIGHT * 2) / 2; + int ty = (CALC_DISPLAY_H - large_h) / 2; if (tx < 4) tx = 4; c.text_2x(tx, ty, cs->display_str, colors::WHITE); @@ -271,9 +279,9 @@ static void calculator_on_draw(Window* win, Framebuffer& fb) { // Button text const char* label = calc_labels[row][col]; Color label_color = (col == 3) ? colors::WHITE : colors::TEXT_COLOR; - int label_w = zenith::slen(label) * FONT_WIDTH; + int label_w = text_width(label); int lx = bx + (bw - label_w) / 2; - int ly = by + (CALC_BTN_H - FONT_HEIGHT) / 2; + int ly = by + (CALC_BTN_H - system_font_height()) / 2; c.text(lx, ly, label, label_color); } } diff --git a/programs/src/desktop/app_devexplorer.cpp b/programs/src/desktop/app_devexplorer.cpp new file mode 100644 index 0000000..71818be --- /dev/null +++ b/programs/src/desktop/app_devexplorer.cpp @@ -0,0 +1,475 @@ +/* + * app_devexplorer.cpp + * ZenithOS Desktop - Device Explorer (lists hardware detected by the kernel) + * Copyright (c) 2026 Daniel Hammer +*/ + +#include "apps_common.hpp" + +// ============================================================================ +// Device Explorer state +// ============================================================================ + +static constexpr int DE_TOOLBAR_H = 36; +static constexpr int DE_CAT_H = 28; +static constexpr int DE_ITEM_H = 24; +static constexpr int DE_MAX_DEVS = 64; +static constexpr int DE_POLL_MS = 2000; +static constexpr int DE_INDENT = 28; + +// Category names matching DevInfo.category values +static const char* category_names[] = { + "CPU", // 0 + "Interrupt", // 1 + "Timer", // 2 + "Input", // 3 + "USB", // 4 + "Network", // 5 + "Display", // 6 + "PCI", // 7 +}; +static constexpr int NUM_CATEGORIES = 8; + +static Color category_colors[] = { + Color::from_rgb(0x33, 0x66, 0xCC), // CPU - blue + Color::from_rgb(0x88, 0x44, 0xAA), // Interrupt - purple + Color::from_rgb(0x22, 0x88, 0x22), // Timer - green + Color::from_rgb(0xCC, 0x88, 0x00), // Input - amber + Color::from_rgb(0x00, 0x88, 0x88), // USB - teal + Color::from_rgb(0xCC, 0x55, 0x22), // Network - orange + Color::from_rgb(0x44, 0x66, 0xCC), // Display - indigo + Color::from_rgb(0x66, 0x66, 0x66), // PCI - gray +}; + +struct DevExplorerState { + DesktopState* desktop; + Zenith::DevInfo devs[DE_MAX_DEVS]; + int dev_count; + bool collapsed[NUM_CATEGORIES]; // per-category collapse state + int selected_row; // index into visible display rows (-1 = none) + int scroll_y; // scroll offset in display rows + uint64_t last_poll_ms; +}; + +// ============================================================================ +// Display row model +// ============================================================================ + +enum RowType { ROW_CATEGORY, ROW_DEVICE }; + +struct DisplayRow { + RowType type; + int category; // category index (0..7) + int dev_index; // index into devs[] (only valid for ROW_DEVICE) +}; + +static constexpr int MAX_DISPLAY_ROWS = DE_MAX_DEVS + NUM_CATEGORIES; + +static int build_display_rows(DevExplorerState* de, DisplayRow* rows) { + int count = 0; + for (int cat = 0; cat < NUM_CATEGORIES; cat++) { + // Count devices in this category + int cat_count = 0; + for (int d = 0; d < de->dev_count; d++) { + if (de->devs[d].category == cat) cat_count++; + } + if (cat_count == 0) continue; + + // Emit category header + rows[count].type = ROW_CATEGORY; + rows[count].category = cat; + rows[count].dev_index = -1; + count++; + + // Emit device rows if expanded + if (!de->collapsed[cat]) { + for (int d = 0; d < de->dev_count; d++) { + if (de->devs[d].category == cat) { + rows[count].type = ROW_DEVICE; + rows[count].category = cat; + rows[count].dev_index = d; + count++; + } + } + } + } + return count; +} + +// ============================================================================ +// Triangle drawing helpers +// ============================================================================ + +static void draw_triangle_right(Canvas& c, int x, int y, int size, Color col) { + // Right-pointing filled triangle (▶) + int half = size / 2; + for (int row = 0; row < size; row++) { + int dist = (row <= half) ? row : (size - 1 - row); + for (int col_px = 0; col_px <= dist; col_px++) { + c.put_pixel(x + col_px, y + row, col); + } + } +} + +static void draw_triangle_down(Canvas& c, int x, int y, int size, Color col) { + // Down-pointing filled triangle (▼) + int half = size / 2; + for (int row = 0; row < half + 1; row++) { + int w = size - row * 2; + for (int col_px = 0; col_px < w; col_px++) { + c.put_pixel(x + row + col_px, y + row, col); + } + } +} + +// ============================================================================ +// Callbacks +// ============================================================================ + +static void devexplorer_on_poll(Window* win) { + DevExplorerState* de = (DevExplorerState*)win->app_data; + if (!de) return; + + uint64_t now = zenith::get_milliseconds(); + if (now - de->last_poll_ms < DE_POLL_MS) return; + de->last_poll_ms = now; + + de->dev_count = zenith::devlist(de->devs, DE_MAX_DEVS); +} + +static void devexplorer_on_draw(Window* win, Framebuffer& fb) { + DevExplorerState* de = (DevExplorerState*)win->app_data; + if (!de) return; + + Canvas c(win); + c.fill(colors::WINDOW_BG); + + int fh = system_font_height(); + + // --- Toolbar --- + c.fill_rect(0, 0, c.w, DE_TOOLBAR_H, Color::from_rgb(0xF5, 0xF5, 0xF5)); + c.hline(0, DE_TOOLBAR_H - 1, c.w, colors::BORDER); + + // "Refresh" button + int btn_w = 80; + int btn_h = 26; + int btn_x = 8; + int btn_y = (DE_TOOLBAR_H - btn_h) / 2; + c.button(btn_x, btn_y, btn_w, btn_h, "Refresh", + Color::from_rgb(0x33, 0x66, 0xCC), colors::WHITE, 4); + + // Device count on right + char count_str[24]; + snprintf(count_str, sizeof(count_str), "%d devices", de->dev_count); + int cw = text_width(count_str); + c.text(c.w - cw - 12, (DE_TOOLBAR_H - fh) / 2, count_str, colors::TEXT_COLOR); + + // --- Build display rows --- + DisplayRow rows[MAX_DISPLAY_ROWS]; + int row_count = build_display_rows(de, rows); + + // --- Compute visible area --- + int list_y = DE_TOOLBAR_H; + int list_h = c.h - list_y; + if (list_h < 1) return; + + // Clamp scroll + // Calculate total content height + int total_h = 0; + for (int i = 0; i < row_count; i++) { + total_h += (rows[i].type == ROW_CATEGORY) ? DE_CAT_H : DE_ITEM_H; + } + int max_scroll_px = total_h - list_h; + if (max_scroll_px < 0) max_scroll_px = 0; + + // Convert scroll_y (in rows) to pixel offset for simplicity, + // but keep the row-based model: scroll_y = row offset + // We'll compute cumulative pixel offsets + int scroll_px = 0; + for (int i = 0; i < de->scroll_y && i < row_count; i++) { + scroll_px += (rows[i].type == ROW_CATEGORY) ? DE_CAT_H : DE_ITEM_H; + } + if (scroll_px > max_scroll_px) { + scroll_px = max_scroll_px; + // Recalculate scroll_y to match + de->scroll_y = 0; + int acc = 0; + for (int i = 0; i < row_count; i++) { + int rh = (rows[i].type == ROW_CATEGORY) ? DE_CAT_H : DE_ITEM_H; + if (acc + rh > max_scroll_px) break; + acc += rh; + de->scroll_y = i + 1; + } + } + if (de->scroll_y < 0) de->scroll_y = 0; + + // Clamp selected_row + if (de->selected_row >= row_count) de->selected_row = row_count - 1; + + // --- Draw rows --- + int draw_y = list_y - scroll_px; + // Advance to first visible area by recomputing from scroll offset + draw_y = list_y; + int first_visible = de->scroll_y; + + // Compute starting y by accumulating heights of skipped rows + // Actually, let's just iterate all rows and skip those above the viewport + draw_y = list_y; + for (int i = 0; i < first_visible && i < row_count; i++) { + draw_y -= (rows[i].type == ROW_CATEGORY) ? DE_CAT_H : DE_ITEM_H; + } + + // Draw from first_visible onward + int cur_y = list_y; + for (int i = first_visible; i < row_count; i++) { + int row_h = (rows[i].type == ROW_CATEGORY) ? DE_CAT_H : DE_ITEM_H; + if (cur_y >= c.h) break; // Past bottom of window + + if (rows[i].type == ROW_CATEGORY) { + // Category header + int cat = rows[i].category; + c.fill_rect(0, cur_y, c.w, DE_CAT_H, Color::from_rgb(0xF0, 0xF0, 0xF0)); + + // Highlight if selected + if (i == de->selected_row) { + c.fill_rect(0, cur_y, c.w, DE_CAT_H, colors::MENU_HOVER); + } + + // Expand/collapse triangle + int tri_x = 10; + int tri_y = cur_y + (DE_CAT_H - 8) / 2; + Color tri_color = Color::from_rgb(0x55, 0x55, 0x55); + if (de->collapsed[cat]) { + draw_triangle_right(c, tri_x, tri_y, 8, tri_color); + } else { + draw_triangle_down(c, tri_x, tri_y, 8, tri_color); + } + + // Colored dot + int dot_x = 24; + int dot_y = cur_y + (DE_CAT_H - 8) / 2; + Color cat_col = (cat >= 0 && cat < NUM_CATEGORIES) + ? category_colors[cat] : colors::TEXT_COLOR; + c.fill_rounded_rect(dot_x, dot_y, 8, 8, 4, cat_col); + + // Category name + const char* cat_name = (cat >= 0 && cat < NUM_CATEGORIES) + ? category_names[cat] : "?"; + int text_y = cur_y + (DE_CAT_H - fh) / 2; + c.text(36, text_y, cat_name, Color::from_rgb(0x33, 0x33, 0x33)); + + // Device count in parentheses on right + int cat_count = 0; + for (int d = 0; d < de->dev_count; d++) { + if (de->devs[d].category == cat) cat_count++; + } + char cnt_buf[16]; + snprintf(cnt_buf, sizeof(cnt_buf), "(%d)", cat_count); + int name_w = text_width(cat_name); + c.text(36 + name_w + 8, text_y, cnt_buf, + Color::from_rgb(0x88, 0x88, 0x88)); + + // Bottom border + c.hline(0, cur_y + DE_CAT_H - 1, c.w, Color::from_rgb(0xE0, 0xE0, 0xE0)); + } else { + // Device item row + int di = rows[i].dev_index; + + // Selected row highlight + if (i == de->selected_row) { + c.fill_rect(0, cur_y, c.w, DE_ITEM_H, colors::MENU_HOVER); + } + + int text_y = cur_y + (DE_ITEM_H - fh) / 2; + + // Device name (indented) + c.text(DE_INDENT + 10, text_y, de->devs[di].name, colors::TEXT_COLOR); + + // Detail string (right column, gray) + int detail_x = c.w / 2 + 20; + c.text(detail_x, text_y, de->devs[di].detail, + Color::from_rgb(0x66, 0x66, 0x66)); + } + + cur_y += row_h; + } + + // --- Scrollbar --- + if (total_h > list_h) { + int sb_x = c.w - 6; + int thumb_h = (list_h * list_h) / total_h; + if (thumb_h < 20) thumb_h = 20; + int thumb_y = list_y + (scroll_px * (list_h - thumb_h)) / max_scroll_px; + c.fill_rect(sb_x, list_y, 4, list_h, Color::from_rgb(0xE0, 0xE0, 0xE0)); + c.fill_rect(sb_x, thumb_y, 4, thumb_h, Color::from_rgb(0xAA, 0xAA, 0xAA)); + } +} + +static void devexplorer_on_mouse(Window* win, MouseEvent& ev) { + DevExplorerState* de = (DevExplorerState*)win->app_data; + if (!de) return; + + Rect cr = win->content_rect(); + int lx = ev.x - cr.x; + int ly = ev.y - cr.y; + + // Scroll + if (ev.scroll != 0) { + de->scroll_y -= ev.scroll; + if (de->scroll_y < 0) de->scroll_y = 0; + return; + } + + if (ev.left_pressed()) { + // Check "Refresh" button click + int btn_w = 80; + int btn_h = 26; + int btn_x = 8; + int btn_y = (DE_TOOLBAR_H - btn_h) / 2; + Rect btn_rect = {btn_x, btn_y, btn_w, btn_h}; + if (btn_rect.contains(lx, ly)) { + de->last_poll_ms = 0; // force refresh + return; + } + + // Check row click in list area + int list_y = DE_TOOLBAR_H; + if (ly >= list_y) { + // Build display rows to map click to row + DisplayRow rows[MAX_DISPLAY_ROWS]; + int row_count = build_display_rows(de, rows); + + // Walk rows from scroll offset, accumulating y + int cur_y = list_y; + for (int i = de->scroll_y; i < row_count; i++) { + int row_h = (rows[i].type == ROW_CATEGORY) ? DE_CAT_H : DE_ITEM_H; + if (ly >= cur_y && ly < cur_y + row_h) { + // Hit this row + if (rows[i].type == ROW_CATEGORY) { + // Toggle collapse + int cat = rows[i].category; + de->collapsed[cat] = !de->collapsed[cat]; + de->selected_row = -1; + } else { + de->selected_row = i; + } + return; + } + cur_y += row_h; + if (cur_y >= win->content_h) break; + } + // Clicked below all rows + de->selected_row = -1; + } + } +} + +static void devexplorer_on_key(Window* win, const Zenith::KeyEvent& key) { + DevExplorerState* de = (DevExplorerState*)win->app_data; + if (!de || !key.pressed) return; + + DisplayRow rows[MAX_DISPLAY_ROWS]; + int row_count = build_display_rows(de, rows); + if (row_count == 0) return; + + if (key.scancode == 0x48) { // Up arrow + if (de->selected_row <= 0) { + de->selected_row = 0; + } else { + de->selected_row--; + } + // Scroll to keep selection visible + if (de->selected_row < de->scroll_y) { + de->scroll_y = de->selected_row; + } + } else if (key.scancode == 0x50) { // Down arrow + if (de->selected_row < row_count - 1) { + de->selected_row++; + } + // Scroll to keep selection visible — estimate visible rows + int list_h = win->content_h - DE_TOOLBAR_H; + int cur_h = 0; + int last_visible = de->scroll_y; + for (int i = de->scroll_y; i < row_count; i++) { + int rh = (rows[i].type == ROW_CATEGORY) ? DE_CAT_H : DE_ITEM_H; + if (cur_h + rh > list_h) break; + cur_h += rh; + last_visible = i; + } + if (de->selected_row > last_visible) { + de->scroll_y += (de->selected_row - last_visible); + } + } else if (key.scancode == 0x4B) { // Left arrow — collapse + if (de->selected_row >= 0 && de->selected_row < row_count) { + int cat = rows[de->selected_row].category; + if (!de->collapsed[cat]) { + de->collapsed[cat] = true; + // If we were on a device row, move selection to the category header + if (rows[de->selected_row].type == ROW_DEVICE) { + // Find the category header row for this category + for (int i = de->selected_row - 1; i >= 0; i--) { + if (rows[i].type == ROW_CATEGORY && rows[i].category == cat) { + de->selected_row = i; + break; + } + } + // After collapsing, rebuild and clamp + int new_count = build_display_rows(de, rows); + if (de->selected_row >= new_count) + de->selected_row = new_count - 1; + } + } + } + } else if (key.scancode == 0x4D) { // Right arrow — expand + if (de->selected_row >= 0 && de->selected_row < row_count) { + int cat = rows[de->selected_row].category; + de->collapsed[cat] = false; + } + } else if (key.scancode == 0x1C) { // Enter — toggle on category row + if (de->selected_row >= 0 && de->selected_row < row_count) { + if (rows[de->selected_row].type == ROW_CATEGORY) { + int cat = rows[de->selected_row].category; + de->collapsed[cat] = !de->collapsed[cat]; + } + } + } +} + +static void devexplorer_on_close(Window* win) { + if (win->app_data) { + zenith::mfree(win->app_data); + win->app_data = nullptr; + } +} + +// ============================================================================ +// Device Explorer launcher +// ============================================================================ + +void open_devexplorer(DesktopState* ds) { + int idx = desktop_create_window(ds, "Devices", 140, 70, 640, 460); + if (idx < 0) return; + + Window* win = &ds->windows[idx]; + + DevExplorerState* de = (DevExplorerState*)zenith::malloc(sizeof(DevExplorerState)); + zenith::memset(de, 0, sizeof(DevExplorerState)); + de->desktop = ds; + de->selected_row = -1; + de->scroll_y = 0; + de->last_poll_ms = 0; + + // All categories start expanded + for (int i = 0; i < NUM_CATEGORIES; i++) + de->collapsed[i] = false; + + // Initial poll + de->dev_count = zenith::devlist(de->devs, DE_MAX_DEVS); + + win->app_data = de; + win->on_draw = devexplorer_on_draw; + win->on_mouse = devexplorer_on_mouse; + win->on_key = devexplorer_on_key; + win->on_close = devexplorer_on_close; + win->on_poll = devexplorer_on_poll; +} diff --git a/programs/src/desktop/app_doom.cpp b/programs/src/desktop/app_doom.cpp new file mode 100644 index 0000000..c5d7804 --- /dev/null +++ b/programs/src/desktop/app_doom.cpp @@ -0,0 +1,12 @@ +/* + * app_doom.cpp + * ZenithOS Desktop - DOOM launcher (spawns standalone doom.elf) + * Copyright (c) 2026 Daniel Hammer +*/ + +#include "apps_common.hpp" + +void open_doom(DesktopState* ds) { + (void)ds; + zenith::spawn("0:/games/doom.elf"); +} diff --git a/programs/src/desktop/app_filemanager.cpp b/programs/src/desktop/app_filemanager.cpp index 2c2220e..b50e5c7 100644 --- a/programs/src/desktop/app_filemanager.cpp +++ b/programs/src/desktop/app_filemanager.cpp @@ -373,7 +373,7 @@ static void filemanager_on_draw(Window* win, Framebuffer& fb) { int tx = cell_x + (FM_GRID_CELL_W - tw) / 2; if (tx < cell_x) tx = cell_x; int ty = icon_y + FM_GRID_ICON + 2; - if (ty >= list_y && ty + FONT_HEIGHT <= c.h) + if (ty >= list_y && ty + system_font_height() <= c.h) c.text(tx, ty, label, colors::TEXT_COLOR); } } else { @@ -447,19 +447,20 @@ static void filemanager_on_draw(Window* win, Framebuffer& fb) { // Name int tx = 30; - int ty = iy + (FM_ITEM_H - FONT_HEIGHT) / 2; - if (ty >= list_y && ty + FONT_HEIGHT <= c.h) + int fm_sfh = system_font_height(); + int ty = iy + (FM_ITEM_H - fm_sfh) / 2; + if (ty >= list_y && ty + fm_sfh <= c.h) c.text(tx, ty, fm->entry_names[i], colors::TEXT_COLOR); // Size - if (size_col_x > 100 && !fm->is_dir[i] && ty >= list_y && ty + FONT_HEIGHT <= c.h) { + if (size_col_x > 100 && !fm->is_dir[i] && ty >= list_y && ty + fm_sfh <= c.h) { char size_str[16]; format_size(size_str, fm->entry_sizes[i]); c.text(size_col_x, ty, size_str, dim); } // Type - if (type_col_x > 160 && ty >= list_y && ty + FONT_HEIGHT <= c.h) { + if (type_col_x > 160 && ty >= list_y && ty + fm_sfh <= c.h) { const char* type_str = "File"; if (fm->entry_types[i] == 1) type_str = "Dir"; else if (fm->entry_types[i] == 2) type_str = "Exec"; diff --git a/programs/src/desktop/app_klog.cpp b/programs/src/desktop/app_klog.cpp index bdb2097..6c0c495 100644 --- a/programs/src/desktop/app_klog.cpp +++ b/programs/src/desktop/app_klog.cpp @@ -119,8 +119,8 @@ void open_klog(DesktopState* ds) { Window* win = &ds->windows[idx]; Rect cr = win->content_rect(); - int cols = cr.w / FONT_WIDTH; - int rows = cr.h / FONT_HEIGHT; + int cols = cr.w / mono_cell_width(); + int rows = cr.h / mono_cell_height(); KlogState* klog = (KlogState*)zenith::malloc(sizeof(KlogState)); zenith::memset(klog, 0, sizeof(KlogState)); diff --git a/programs/src/desktop/app_mandelbrot.cpp b/programs/src/desktop/app_mandelbrot.cpp new file mode 100644 index 0000000..add2146 --- /dev/null +++ b/programs/src/desktop/app_mandelbrot.cpp @@ -0,0 +1,296 @@ +/* + * app_mandelbrot.cpp + * ZenithOS Desktop - Mandelbrot set visualizer + * Supports zoom (scroll wheel), pan (drag), and reset (R key) + * Copyright (c) 2026 Daniel Hammer +*/ + +#include "apps_common.hpp" + +// ============================================================================ +// Fixed-point Mandelbrot (avoids FPU/SSE dependency issues) +// Uses 28.36 fixed-point: 36 fractional bits gives ~1e-10 precision +// ============================================================================ + +using fp_t = int64_t; +static constexpr int FP_SHIFT = 36; +static constexpr fp_t FP_ONE = (fp_t)1 << FP_SHIFT; + +static inline fp_t fp_from_int(int v) { return (fp_t)v << FP_SHIFT; } + +static inline fp_t fp_mul(fp_t a, fp_t b) { + // Split to avoid overflow: a * b >> SHIFT + // Use 128-bit intermediate via compiler builtin + __int128 r = (__int128)a * b; + return (fp_t)(r >> FP_SHIFT); +} + +// fp_div for small numerators (avoids __divti3 by using 64-bit division) +// Only valid when a is small enough that a << FP_SHIFT fits in int64_t +static inline fp_t fp_div_small(int a, int b) { + return ((fp_t)a << FP_SHIFT) / (fp_t)b; +} + +// ============================================================================ +// State +// ============================================================================ + +static constexpr int MB_MAX_ITER = 256; +static constexpr int MB_TOOLBAR_H = 32; + +struct MandelbrotState { + DesktopState* desktop; + fp_t center_x, center_y; // center of view in fractal coords + fp_t scale; // units per pixel (fixed-point) + int max_iter; + bool needs_render; + // Drag state + bool dragging; + int drag_start_x, drag_start_y; + fp_t drag_start_cx, drag_start_cy; +}; + +// ============================================================================ +// Color palette +// ============================================================================ + +static uint32_t mandelbrot_color(int iter, int max_iter) { + if (iter >= max_iter) return 0xFF000000; // black for inside set + + // Smooth cycling palette using bit manipulation + int t = (iter * 7) & 0xFF; + int phase = (iter * 7) >> 8; + + uint8_t r, g, b; + switch (phase % 6) { + case 0: r = 255; g = (uint8_t)t; b = 0; break; + case 1: r = (uint8_t)(255 - t); g = 255; b = 0; break; + case 2: r = 0; g = 255; b = (uint8_t)t; break; + case 3: r = 0; g = (uint8_t)(255 - t); b = 255; break; + case 4: r = (uint8_t)t; g = 0; b = 255; break; + default: r = 255; g = 0; b = (uint8_t)(255 - t); break; + } + + return 0xFF000000 | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; +} + +// ============================================================================ +// Render +// ============================================================================ + +static void mandelbrot_render(MandelbrotState* mb, uint32_t* pixels, int w, int h) { + int render_h = h - MB_TOOLBAR_H; + if (render_h <= 0) return; + + fp_t half_w = fp_mul(fp_from_int(w / 2), mb->scale); + fp_t half_h = fp_mul(fp_from_int(render_h / 2), mb->scale); + fp_t x_min = mb->center_x - half_w; + fp_t y_min = mb->center_y - half_h; + fp_t bailout = fp_from_int(4); + + for (int py = 0; py < render_h; py++) { + fp_t ci = y_min + fp_mul(fp_from_int(py), mb->scale); + uint32_t* row = pixels + (py + MB_TOOLBAR_H) * w; + + for (int px = 0; px < w; px++) { + fp_t cr = x_min + fp_mul(fp_from_int(px), mb->scale); + + fp_t zr = 0, zi = 0; + int iter = 0; + + while (iter < mb->max_iter) { + fp_t zr2 = fp_mul(zr, zr); + fp_t zi2 = fp_mul(zi, zi); + if (zr2 + zi2 > bailout) break; + fp_t new_zr = zr2 - zi2 + cr; + zi = fp_mul(fp_from_int(2), fp_mul(zr, zi)) + ci; + zr = new_zr; + iter++; + } + + row[px] = mandelbrot_color(iter, mb->max_iter); + } + } + + mb->needs_render = false; +} + +// ============================================================================ +// Callbacks +// ============================================================================ + +static void mb_on_draw(Window* win, Framebuffer& fb) { + MandelbrotState* mb = (MandelbrotState*)win->app_data; + if (!mb) return; + + Canvas c(win); + + if (mb->needs_render) { + mandelbrot_render(mb, win->content, c.w, c.h); + } + + // Draw toolbar over the render + c.fill_rect(0, 0, c.w, MB_TOOLBAR_H, Color::from_rgb(0xF5, 0xF5, 0xF5)); + c.hline(0, MB_TOOLBAR_H - 1, c.w, colors::BORDER); + + // Reset button + c.button(8, 4, 60, 24, "Reset", colors::ACCENT, colors::WHITE, 4); + + // Iter +/- buttons + char iter_str[24]; + snprintf(iter_str, sizeof(iter_str), "Iter: %d", mb->max_iter); + int fh = system_font_height(); + c.text(80, (MB_TOOLBAR_H - fh) / 2, iter_str, colors::TEXT_COLOR); + + int iter_text_w = text_width(iter_str); + int btn_x = 80 + iter_text_w + 8; + c.button(btn_x, 4, 24, 24, "-", Color::from_rgb(0xAA, 0xAA, 0xAA), colors::WHITE, 4); + c.button(btn_x + 28, 4, 24, 24, "+", Color::from_rgb(0xAA, 0xAA, 0xAA), colors::WHITE, 4); + + // Hint + const char* hint = "Scroll=zoom Drag=pan"; + int hw = text_width(hint); + c.text(c.w - hw - 8, (MB_TOOLBAR_H - fh) / 2, hint, Color::from_rgb(0x99, 0x99, 0x99)); +} + +static void mb_on_mouse(Window* win, MouseEvent& ev) { + MandelbrotState* mb = (MandelbrotState*)win->app_data; + if (!mb) return; + + Rect cr = win->content_rect(); + int lx = ev.x - cr.x; + int ly = ev.y - cr.y; + + // Handle toolbar clicks + if (ev.left_pressed() && ly < MB_TOOLBAR_H) { + // Reset button + Rect reset_r = {8, 4, 60, 24}; + if (reset_r.contains(lx, ly)) { + mb->center_x = -fp_from_int(1) / 2; // -0.5 + mb->center_y = 0; + mb->scale = fp_div_small(3, cr.w > 0 ? cr.w : 400); + mb->max_iter = MB_MAX_ITER; + mb->needs_render = true; + return; + } + + // Iter buttons + char iter_str[24]; + snprintf(iter_str, sizeof(iter_str), "Iter: %d", mb->max_iter); + int iter_text_w = text_width(iter_str); + int btn_x = 80 + iter_text_w + 8; + + Rect minus_r = {btn_x, 4, 24, 24}; + Rect plus_r = {btn_x + 28, 4, 24, 24}; + + if (minus_r.contains(lx, ly)) { + if (mb->max_iter > 32) { mb->max_iter /= 2; mb->needs_render = true; } + return; + } + if (plus_r.contains(lx, ly)) { + if (mb->max_iter < 4096) { mb->max_iter *= 2; mb->needs_render = true; } + return; + } + return; + } + + // Drag panning + if (ev.left_pressed() && ly >= MB_TOOLBAR_H) { + mb->dragging = true; + mb->drag_start_x = lx; + mb->drag_start_y = ly; + mb->drag_start_cx = mb->center_x; + mb->drag_start_cy = mb->center_y; + return; + } + + if (mb->dragging && ev.left_held()) { + int dx = lx - mb->drag_start_x; + int dy = ly - mb->drag_start_y; + mb->center_x = mb->drag_start_cx - fp_mul(fp_from_int(dx), mb->scale); + mb->center_y = mb->drag_start_cy - fp_mul(fp_from_int(dy), mb->scale); + mb->needs_render = true; + return; + } + + if (mb->dragging && !ev.left_held()) { + mb->dragging = false; + } + + // Scroll zoom (centered on mouse position) + if (ev.scroll != 0 && ly >= MB_TOOLBAR_H) { + int render_h = cr.h - MB_TOOLBAR_H; + // Mouse position in fractal coords before zoom + fp_t mx_frac = mb->center_x + fp_mul(fp_from_int(lx - cr.w / 2), mb->scale); + fp_t my_frac = mb->center_y + fp_mul(fp_from_int((ly - MB_TOOLBAR_H) - render_h / 2), mb->scale); + + if (ev.scroll < 0) { + // Zoom in + mb->scale = fp_mul(mb->scale, fp_from_int(3) / 4); // * 0.75 + } else { + // Zoom out + mb->scale = fp_mul(mb->scale, fp_from_int(4) / 3); // * 1.33 + } + + // Adjust center so mouse stays at same fractal point + fp_t new_mx = mb->center_x + fp_mul(fp_from_int(lx - cr.w / 2), mb->scale); + fp_t new_my = mb->center_y + fp_mul(fp_from_int((ly - MB_TOOLBAR_H) - render_h / 2), mb->scale); + mb->center_x += mx_frac - new_mx; + mb->center_y += my_frac - new_my; + + mb->needs_render = true; + } +} + +static void mb_on_key(Window* win, const Zenith::KeyEvent& key) { + MandelbrotState* mb = (MandelbrotState*)win->app_data; + if (!mb || !key.pressed) return; + + if (key.ascii == 'r' || key.ascii == 'R') { + Rect cr = win->content_rect(); + mb->center_x = -fp_from_int(1) / 2; + mb->center_y = 0; + mb->scale = fp_div_small(3, cr.w > 0 ? cr.w : 400); + mb->max_iter = MB_MAX_ITER; + mb->needs_render = true; + } else if (key.ascii == '+' || key.ascii == '=') { + if (mb->max_iter < 4096) { mb->max_iter *= 2; mb->needs_render = true; } + } else if (key.ascii == '-') { + if (mb->max_iter > 32) { mb->max_iter /= 2; mb->needs_render = true; } + } +} + +static void mb_on_close(Window* win) { + if (win->app_data) { + zenith::mfree(win->app_data); + win->app_data = nullptr; + } +} + +// ============================================================================ +// Mandelbrot launcher +// ============================================================================ + +void open_mandelbrot(DesktopState* ds) { + int idx = desktop_create_window(ds, "Mandelbrot", 120, 60, 500, 400); + if (idx < 0) return; + + Window* win = &ds->windows[idx]; + Rect cr = win->content_rect(); + + MandelbrotState* mb = (MandelbrotState*)zenith::malloc(sizeof(MandelbrotState)); + zenith::memset(mb, 0, sizeof(MandelbrotState)); + mb->desktop = ds; + mb->center_x = -fp_from_int(1) / 2; // -0.5 + mb->center_y = 0; + mb->scale = fp_div_small(3, cr.w > 0 ? cr.w : 400); + mb->max_iter = MB_MAX_ITER; + mb->needs_render = true; + mb->dragging = false; + + win->app_data = mb; + win->on_draw = mb_on_draw; + win->on_mouse = mb_on_mouse; + win->on_key = mb_on_key; + win->on_close = mb_on_close; +} diff --git a/programs/src/desktop/app_procmgr.cpp b/programs/src/desktop/app_procmgr.cpp new file mode 100644 index 0000000..aaac948 --- /dev/null +++ b/programs/src/desktop/app_procmgr.cpp @@ -0,0 +1,244 @@ +/* + * app_procmgr.cpp + * ZenithOS Desktop - Process Manager + * Copyright (c) 2026 Daniel Hammer +*/ + +#include "apps_common.hpp" + +// ============================================================================ +// Process Manager state +// ============================================================================ + +static constexpr int PM_TOOLBAR_H = 36; +static constexpr int PM_HEADER_H = 24; +static constexpr int PM_ITEM_H = 28; +static constexpr int PM_MAX_PROCS = 16; +static constexpr int PM_POLL_MS = 1000; + +struct ProcMgrState { + DesktopState* desktop; + Zenith::ProcInfo procs[PM_MAX_PROCS]; + int proc_count; + int selected; // selected row index (-1 = none) + uint64_t last_poll_ms; +}; + +// ============================================================================ +// Callbacks +// ============================================================================ + +static void procmgr_on_poll(Window* win) { + ProcMgrState* pm = (ProcMgrState*)win->app_data; + if (!pm) return; + + uint64_t now = zenith::get_milliseconds(); + if (now - pm->last_poll_ms < PM_POLL_MS) return; + pm->last_poll_ms = now; + + // Remember previously selected PID + int prev_pid = -1; + if (pm->selected >= 0 && pm->selected < pm->proc_count) { + prev_pid = pm->procs[pm->selected].pid; + } + + pm->proc_count = zenith::proclist(pm->procs, PM_MAX_PROCS); + + // Restore selection by matching PID + pm->selected = -1; + if (prev_pid >= 0) { + for (int i = 0; i < pm->proc_count; i++) { + if (pm->procs[i].pid == prev_pid) { + pm->selected = i; + break; + } + } + } +} + +static void procmgr_on_draw(Window* win, Framebuffer& fb) { + ProcMgrState* pm = (ProcMgrState*)win->app_data; + if (!pm) return; + + Canvas c(win); + c.fill(colors::WINDOW_BG); + + int fh = system_font_height(); + + // --- Toolbar --- + c.fill_rect(0, 0, c.w, PM_TOOLBAR_H, Color::from_rgb(0xF5, 0xF5, 0xF5)); + c.hline(0, PM_TOOLBAR_H - 1, c.w, colors::BORDER); + + // "End Process" button + int btn_w = 100; + int btn_h = 26; + int btn_x = 8; + int btn_y = (PM_TOOLBAR_H - btn_h) / 2; + Color btn_bg = (pm->selected >= 0 && pm->procs[pm->selected].pid != 0) + ? Color::from_rgb(0xCC, 0x33, 0x33) + : Color::from_rgb(0xAA, 0xAA, 0xAA); + c.button(btn_x, btn_y, btn_w, btn_h, "End Process", btn_bg, colors::WHITE, 4); + + // Process count on right + char count_str[24]; + snprintf(count_str, sizeof(count_str), "%d processes", pm->proc_count); + int cw = text_width(count_str); + c.text(c.w - cw - 12, (PM_TOOLBAR_H - fh) / 2, count_str, colors::TEXT_COLOR); + + // --- Header row --- + int header_y = PM_TOOLBAR_H; + c.fill_rect(0, header_y, c.w, PM_HEADER_H, Color::from_rgb(0xF0, 0xF0, 0xF0)); + + int col_pid = 12; + int col_name = 64; + int col_mem = c.w - 120; + int col_state = c.w - 52; + + int ty = header_y + (PM_HEADER_H - fh) / 2; + c.text(col_pid, ty, "PID", Color::from_rgb(0x66, 0x66, 0x66)); + c.text(col_name, ty, "Name", Color::from_rgb(0x66, 0x66, 0x66)); + c.text(col_mem, ty, "Mem", Color::from_rgb(0x66, 0x66, 0x66)); + c.text(col_state, ty, "St", Color::from_rgb(0x66, 0x66, 0x66)); + + c.hline(0, header_y + PM_HEADER_H - 1, c.w, colors::BORDER); + + // --- Process rows --- + int list_y = PM_TOOLBAR_H + PM_HEADER_H; + for (int i = 0; i < pm->proc_count; i++) { + int row_y = list_y + i * PM_ITEM_H; + if (row_y + PM_ITEM_H > c.h) break; + + // Selected row highlight + if (i == pm->selected) { + c.fill_rect(0, row_y, c.w, PM_ITEM_H, colors::MENU_HOVER); + } + + int ry = row_y + (PM_ITEM_H - fh) / 2; + + // PID (right-aligned in PID column) + char pid_str[8]; + snprintf(pid_str, sizeof(pid_str), "%d", (int)pm->procs[i].pid); + int pid_w = text_width(pid_str); + c.text(col_pid + 32 - pid_w, ry, pid_str, colors::TEXT_COLOR); + + // Name (truncated to fit) + c.text(col_name, ry, pm->procs[i].name, colors::TEXT_COLOR); + + // Memory + char mem_str[16]; + format_size(mem_str, (int)pm->procs[i].heapUsed); + c.text(col_mem, ry, mem_str, colors::TEXT_COLOR); + + // State + const char* st_str; + Color st_color; + switch (pm->procs[i].state) { + case 2: // Running + st_str = "Run"; + st_color = Color::from_rgb(0x22, 0x88, 0x22); + break; + case 1: // Ready + st_str = "Rdy"; + st_color = Color::from_rgb(0x33, 0x66, 0xCC); + break; + case 3: // Terminated + st_str = "Term"; + st_color = Color::from_rgb(0xCC, 0x33, 0x33); + break; + default: + st_str = "?"; + st_color = colors::TEXT_COLOR; + break; + } + c.text(col_state, ry, st_str, st_color); + } +} + +static void procmgr_on_mouse(Window* win, MouseEvent& ev) { + ProcMgrState* pm = (ProcMgrState*)win->app_data; + if (!pm) return; + + Rect cr = win->content_rect(); + int lx = ev.x - cr.x; + int ly = ev.y - cr.y; + + if (ev.left_pressed()) { + // Check "End Process" button click + int btn_w = 100; + int btn_h = 26; + int btn_x = 8; + int btn_y = (PM_TOOLBAR_H - btn_h) / 2; + Rect btn_rect = {btn_x, btn_y, btn_w, btn_h}; + if (btn_rect.contains(lx, ly)) { + if (pm->selected >= 0 && pm->selected < pm->proc_count + && pm->procs[pm->selected].pid != 0) { + zenith::kill(pm->procs[pm->selected].pid); + pm->last_poll_ms = 0; // force refresh + } + return; + } + + // Check row click + int list_y = PM_TOOLBAR_H + PM_HEADER_H; + if (ly >= list_y) { + int row = (ly - list_y) / PM_ITEM_H; + if (row >= 0 && row < pm->proc_count) { + pm->selected = row; + } else { + pm->selected = -1; + } + } + } +} + +static void procmgr_on_key(Window* win, const Zenith::KeyEvent& key) { + ProcMgrState* pm = (ProcMgrState*)win->app_data; + if (!pm || !key.pressed) return; + + if (key.scancode == 0x48) { // Up arrow + if (pm->selected > 0) pm->selected--; + else if (pm->proc_count > 0) pm->selected = 0; + } else if (key.scancode == 0x50) { // Down arrow + if (pm->selected < pm->proc_count - 1) pm->selected++; + } else if (key.scancode == 0x53) { // Delete key + if (pm->selected >= 0 && pm->selected < pm->proc_count + && pm->procs[pm->selected].pid != 0) { + zenith::kill(pm->procs[pm->selected].pid); + pm->last_poll_ms = 0; // force refresh + } + } +} + +static void procmgr_on_close(Window* win) { + if (win->app_data) { + zenith::mfree(win->app_data); + win->app_data = nullptr; + } +} + +// ============================================================================ +// Process Manager launcher +// ============================================================================ + +void open_procmgr(DesktopState* ds) { + int idx = desktop_create_window(ds, "Processes", 180, 80, 520, 400); + if (idx < 0) return; + + Window* win = &ds->windows[idx]; + + ProcMgrState* pm = (ProcMgrState*)zenith::malloc(sizeof(ProcMgrState)); + zenith::memset(pm, 0, sizeof(ProcMgrState)); + pm->desktop = ds; + pm->selected = -1; + pm->last_poll_ms = 0; + + // Initial poll + pm->proc_count = zenith::proclist(pm->procs, PM_MAX_PROCS); + + win->app_data = pm; + win->on_draw = procmgr_on_draw; + win->on_mouse = procmgr_on_mouse; + win->on_key = procmgr_on_key; + win->on_close = procmgr_on_close; + win->on_poll = procmgr_on_poll; +} diff --git a/programs/src/desktop/app_settings.cpp b/programs/src/desktop/app_settings.cpp index 3f9cec9..0cddcf7 100644 --- a/programs/src/desktop/app_settings.cpp +++ b/programs/src/desktop/app_settings.cpp @@ -1,38 +1,252 @@ /* * app_settings.cpp - * ZenithOS Desktop - About application + * ZenithOS Desktop - Settings application * Copyright (c) 2026 Daniel Hammer */ #include "apps_common.hpp" // ============================================================================ -// About state and callbacks +// Settings state // ============================================================================ -struct AboutState { +struct SettingsState { + DesktopState* desktop; + int active_tab; // 0=Appearance, 1=Display, 2=About Zenith::SysInfo sys_info; uint64_t uptime_ms; }; -static void about_on_draw(Window* win, Framebuffer& fb) { - AboutState* st = (AboutState*)win->app_data; - if (!st) return; +// ============================================================================ +// Color palette presets +// ============================================================================ +static constexpr int SWATCH_COUNT = 8; +static constexpr int SWATCH_SIZE = 24; +static constexpr int SWATCH_GAP = 6; + +// Background colors (light tones) +static const Color bg_palette[SWATCH_COUNT] = { + Color::from_rgb(0xD0, 0xD8, 0xE8), // light blue-gray (default) + Color::from_rgb(0xE8, 0xDD, 0xCB), // warm beige + Color::from_rgb(0xC8, 0xE6, 0xD0), // mint green + Color::from_rgb(0xD8, 0xD0, 0xE8), // lavender + Color::from_rgb(0xB8, 0xBE, 0xC8), // slate + Color::from_rgb(0xF0, 0xF0, 0xF0), // white + Color::from_rgb(0xE8, 0xD0, 0xD8), // soft pink + Color::from_rgb(0xE8, 0xE0, 0xC8), // light gold +}; + +// Panel colors (dark tones) +static const Color panel_palette[SWATCH_COUNT] = { + Color::from_rgb(0x2B, 0x3E, 0x50), // dark blue-gray (default) + Color::from_rgb(0x2D, 0x2D, 0x2D), // dark charcoal + Color::from_rgb(0x1B, 0x2A, 0x4A), // navy + Color::from_rgb(0x1A, 0x3A, 0x3A), // dark teal + Color::from_rgb(0x1A, 0x3A, 0x1A), // dark green + Color::from_rgb(0x30, 0x20, 0x40), // dark purple + Color::from_rgb(0x40, 0x1A, 0x1A), // dark red + Color::from_rgb(0x10, 0x10, 0x10), // black +}; + +// Accent colors +static const Color accent_palette[SWATCH_COUNT] = { + Color::from_rgb(0x36, 0x7B, 0xF0), // blue (default) + Color::from_rgb(0x00, 0x9B, 0x9B), // teal + Color::from_rgb(0x2E, 0x9E, 0x3E), // green + Color::from_rgb(0xE0, 0x8A, 0x20), // orange + Color::from_rgb(0xD0, 0x3E, 0x3E), // red + Color::from_rgb(0x7B, 0x3E, 0xB8), // purple + Color::from_rgb(0xD0, 0x5C, 0x9E), // pink + Color::from_rgb(0x44, 0x44, 0xCC), // indigo +}; + +// ============================================================================ +// Layout constants +// ============================================================================ + +static constexpr int TAB_BAR_H = 36; +static constexpr int TAB_COUNT = 3; +static const char* tab_labels[TAB_COUNT] = { "Appearance", "Display", "About" }; + +// ============================================================================ +// Helper: check if two colors match +// ============================================================================ + +static bool color_eq(Color a, Color b) { + return a.r == b.r && a.g == b.g && a.b == b.b; +} + +// ============================================================================ +// Helper: find selected swatch index in a palette +// ============================================================================ + +static int find_swatch(const Color* palette, Color current) { + for (int i = 0; i < SWATCH_COUNT; i++) { + if (color_eq(palette[i], current)) return i; + } + return -1; +} + +// ============================================================================ +// Drawing +// ============================================================================ + +static void draw_swatch_row(Canvas& c, int x, int y, const Color* palette, + Color selected, Color accent) { + int sel = find_swatch(palette, selected); + for (int i = 0; i < SWATCH_COUNT; i++) { + int sx = x + i * (SWATCH_SIZE + SWATCH_GAP); + // Draw selection border + if (i == sel) { + c.fill_rounded_rect(sx - 2, y - 2, SWATCH_SIZE + 4, SWATCH_SIZE + 4, 4, accent); + } + c.fill_rounded_rect(sx, y, SWATCH_SIZE, SWATCH_SIZE, 3, palette[i]); + // Thin border for light colors + c.rect(sx, y, SWATCH_SIZE, SWATCH_SIZE, Color::from_rgb(0xCC, 0xCC, 0xCC)); + } +} + +static void draw_radio(Canvas& c, int x, int y, bool selected, Color accent) { + int r = 7; + // Outer circle (simple square approximation with rounded rect) + c.fill_rounded_rect(x, y, r * 2, r * 2, r, Color::from_rgb(0xCC, 0xCC, 0xCC)); + c.fill_rounded_rect(x + 1, y + 1, r * 2 - 2, r * 2 - 2, r - 1, colors::WHITE); + if (selected) { + c.fill_rounded_rect(x + 4, y + 4, r * 2 - 8, r * 2 - 8, r - 4, accent); + } +} + +static void draw_toggle_btn(Canvas& c, int x, int y, int bw, int bh, + const char* label, bool active, Color accent) { + Color bg = active ? accent : colors::WINDOW_BG; + Color fg = active ? colors::WHITE : colors::TEXT_COLOR; + c.fill_rounded_rect(x, y, bw, bh, 4, bg); + if (!active) { + c.rect(x, y, bw, bh, colors::BORDER); + } + int tw = text_width(label); + int fh = system_font_height(); + c.text(x + (bw - tw) / 2, y + (bh - fh) / 2, label, fg); +} + +static void settings_draw_appearance(Canvas& c, SettingsState* st) { + DesktopSettings& s = st->desktop->settings; + Color accent = s.accent_color; + int x = 16; + int y = 12; + int sfh = system_font_height(); + int line_h = sfh + 10; + + // Section: Background + c.text(x, y, "Background", colors::TEXT_COLOR); + y += line_h; + + // Radio buttons: Gradient / Solid + draw_radio(c, x, y, s.bg_gradient, accent); + c.text(x + 20, y + 2, "Gradient", colors::TEXT_COLOR); + + draw_radio(c, x + 120, y, !s.bg_gradient, accent); + c.text(x + 140, y + 2, "Solid Color", colors::TEXT_COLOR); + y += line_h + 4; + + if (s.bg_gradient) { + // Top color swatches + c.text(x, y + 4, "Top", Color::from_rgb(0x88, 0x88, 0x88)); + draw_swatch_row(c, x + 70, y, bg_palette, s.bg_grad_top, accent); + y += SWATCH_SIZE + 14; + + // Bottom color swatches + c.text(x, y + 4, "Bottom", Color::from_rgb(0x88, 0x88, 0x88)); + draw_swatch_row(c, x + 70, y, bg_palette, s.bg_grad_bottom, accent); + y += SWATCH_SIZE + 14; + } else { + // Solid color swatches + c.text(x, y + 4, "Color", Color::from_rgb(0x88, 0x88, 0x88)); + draw_swatch_row(c, x + 70, y, bg_palette, s.bg_solid, accent); + y += SWATCH_SIZE + 14; + } + + // Separator + c.hline(x, y, c.w - 2 * x, colors::BORDER); + y += 12; + + // Panel color + c.text(x, y + 4, "Panel Color", colors::TEXT_COLOR); + draw_swatch_row(c, x + 110, y, panel_palette, s.panel_color, accent); + y += SWATCH_SIZE + 14; + + // Separator + c.hline(x, y, c.w - 2 * x, colors::BORDER); + y += 12; + + // Accent color + c.text(x, y + 4, "Accent Color", colors::TEXT_COLOR); + draw_swatch_row(c, x + 110, y, accent_palette, s.accent_color, accent); +} + +static void apply_ui_scale(int scale) { + switch (scale) { + case 0: fonts::UI_SIZE=14; fonts::TITLE_SIZE=14; fonts::TERM_SIZE=14; fonts::LARGE_SIZE=22; break; + case 2: fonts::UI_SIZE=22; fonts::TITLE_SIZE=22; fonts::TERM_SIZE=22; fonts::LARGE_SIZE=34; break; + default: fonts::UI_SIZE=18; fonts::TITLE_SIZE=18; fonts::TERM_SIZE=18; fonts::LARGE_SIZE=28; break; + } +} + +static void settings_draw_display(Canvas& c, SettingsState* st) { + DesktopSettings& s = st->desktop->settings; + Color accent = s.accent_color; + int x = 16; + int y = 20; + int btn_w = 60; + int btn_h = 28; + + // Window Shadows + c.text(x, y + 6, "Window Shadows", colors::TEXT_COLOR); + int bx = x + 180; + draw_toggle_btn(c, bx, y, btn_w, btn_h, "On", s.show_shadows, accent); + draw_toggle_btn(c, bx + btn_w + 8, y, btn_w, btn_h, "Off", !s.show_shadows, accent); + y += btn_h + 20; + + // Separator + c.hline(x, y, c.w - 2 * x, colors::BORDER); + y += 16; + + // Clock Format + c.text(x, y + 6, "Clock Format", colors::TEXT_COLOR); + bx = x + 180; + draw_toggle_btn(c, bx, y, btn_w, btn_h, "24h", s.clock_24h, accent); + draw_toggle_btn(c, bx + btn_w + 8, y, btn_w, btn_h, "12h", !s.clock_24h, accent); + y += btn_h + 20; + + // Separator + c.hline(x, y, c.w - 2 * x, colors::BORDER); + y += 16; + + // UI Scale + c.text(x, y + 6, "UI Scale", colors::TEXT_COLOR); + bx = x + 180; + int sbw = 68; + draw_toggle_btn(c, bx, y, sbw, btn_h, "Small", s.ui_scale == 0, accent); + draw_toggle_btn(c, bx + sbw + 8, y, sbw, btn_h, "Default", s.ui_scale == 1, accent); + draw_toggle_btn(c, bx + (sbw + 8) * 2, y, sbw, btn_h, "Large", s.ui_scale == 2, accent); +} + +static void settings_draw_about(Canvas& c, SettingsState* st) { st->uptime_ms = zenith::get_milliseconds(); - Canvas c(win); - c.fill(colors::WINDOW_BG); - Color dim = Color::from_rgb(0x88, 0x88, 0x88); int x = 16; int y = 20; char line[128]; - int line_h = FONT_HEIGHT + 6; + int sfh = system_font_height(); + int line_h = sfh + 6; // OS name in 2x size - c.text_2x(x, y, st->sys_info.osName, colors::ACCENT); - y += FONT_HEIGHT * 2 + 8; + c.text_2x(x, y, st->sys_info.osName, st->desktop->settings.accent_color); + int large_h = (fonts::system_font && fonts::system_font->valid) + ? fonts::system_font->get_line_height(fonts::LARGE_SIZE) : (FONT_HEIGHT * 2); + y += large_h + 8; snprintf(line, sizeof(line), "Version %s", st->sys_info.osVersion); c.text(x, y, line, colors::TEXT_COLOR); @@ -41,27 +255,231 @@ static void about_on_draw(Window* win, Framebuffer& fb) { c.hline(x, y, c.w - 2 * x, colors::BORDER); y += 12; - snprintf(line, sizeof(line), "API version: %d", (int)st->sys_info.apiVersion); + snprintf(line, sizeof(line), "API version: %d", (int)st->sys_info.apiVersion); c.kv_line(x, &y, line, colors::TEXT_COLOR, line_h); int up_sec = (int)(st->uptime_ms / 1000); int up_min = up_sec / 60; int up_hr = up_min / 60; - snprintf(line, sizeof(line), "Uptime: %d:%02d:%02d", up_hr, up_min % 60, up_sec % 60); + snprintf(line, sizeof(line), "Uptime: %d:%02d:%02d", up_hr, up_min % 60, up_sec % 60); c.kv_line(x, &y, line, colors::TEXT_COLOR, line_h); - snprintf(line, sizeof(line), "Build: %s %s", __DATE__, __TIME__); + snprintf(line, sizeof(line), "Build: %s %s", __DATE__, __TIME__); c.text(x, y, line, colors::TEXT_COLOR); y += line_h + 16; c.hline(x, y, c.w - 2 * x, colors::BORDER); y += 12; - c.kv_line(x, &y, "A hobby operating system built from scratch.", dim, line_h); c.text(x, y, "Copyright (c) 2026 Daniel Hammer", dim); } -static void about_on_close(Window* win) { +static void settings_on_draw(Window* win, Framebuffer& fb) { + SettingsState* st = (SettingsState*)win->app_data; + if (!st) return; + + Canvas c(win); + c.fill(colors::WINDOW_BG); + + Color accent = st->desktop->settings.accent_color; + int sfh = system_font_height(); + + // Draw tab bar background + c.fill_rect(0, 0, c.w, TAB_BAR_H, Color::from_rgb(0xF5, 0xF5, 0xF5)); + c.hline(0, TAB_BAR_H - 1, c.w, colors::BORDER); + + // Draw tabs + int tab_w = c.w / TAB_COUNT; + for (int i = 0; i < TAB_COUNT; i++) { + int tx = i * tab_w; + bool active = (i == st->active_tab); + + if (active) { + c.fill_rect(tx, 0, tab_w, TAB_BAR_H, colors::WINDOW_BG); + // Active tab underline + c.fill_rect(tx + 4, TAB_BAR_H - 3, tab_w - 8, 3, accent); + } + + int tw = text_width(tab_labels[i]); + Color tc = active ? accent : Color::from_rgb(0x66, 0x66, 0x66); + c.text(tx + (tab_w - tw) / 2, (TAB_BAR_H - sfh) / 2, tab_labels[i], tc); + } + + // Draw tab content below the tab bar + // Create a sub-canvas offset by TAB_BAR_H + Canvas content(win->content + TAB_BAR_H * win->content_w, + win->content_w, win->content_h - TAB_BAR_H); + + switch (st->active_tab) { + case 0: settings_draw_appearance(content, st); break; + case 1: settings_draw_display(content, st); break; + case 2: settings_draw_about(content, st); break; + } +} + +// ============================================================================ +// Mouse interaction +// ============================================================================ + +static bool swatch_hit(int mx, int my, int row_x, int row_y, int* out_idx) { + for (int i = 0; i < SWATCH_COUNT; i++) { + int sx = row_x + i * (SWATCH_SIZE + SWATCH_GAP); + if (mx >= sx && mx < sx + SWATCH_SIZE && my >= row_y && my < row_y + SWATCH_SIZE) { + *out_idx = i; + return true; + } + } + return false; +} + +static void settings_on_mouse(Window* win, MouseEvent& ev) { + SettingsState* st = (SettingsState*)win->app_data; + if (!st) return; + + if (!ev.left_pressed()) return; + + Rect cr = win->content_rect(); + int mx = ev.x - cr.x; + int my = ev.y - cr.y; + + // Tab bar click + if (my >= 0 && my < TAB_BAR_H) { + int tab_w = win->content_w / TAB_COUNT; + int tab = mx / tab_w; + if (tab >= 0 && tab < TAB_COUNT) { + st->active_tab = tab; + } + return; + } + + // Content area (offset by TAB_BAR_H) + int cy = my - TAB_BAR_H; + DesktopSettings& s = st->desktop->settings; + + if (st->active_tab == 0) { + // Appearance tab + int x = 16; + int sfh = system_font_height(); + int line_h = sfh + 10; + int y = 12; + + // "Background" label + y += line_h; + + // Radio: Gradient + if (mx >= x && mx < x + 100 && cy >= y && cy < y + 16) { + s.bg_gradient = true; + return; + } + // Radio: Solid + if (mx >= x + 120 && mx < x + 260 && cy >= y && cy < y + 16) { + s.bg_gradient = false; + return; + } + y += line_h + 4; + + int idx; + if (s.bg_gradient) { + // Top swatches + if (swatch_hit(mx, cy, x + 70, y, &idx)) { + s.bg_grad_top = bg_palette[idx]; + return; + } + y += SWATCH_SIZE + 14; + + // Bottom swatches + if (swatch_hit(mx, cy, x + 70, y, &idx)) { + s.bg_grad_bottom = bg_palette[idx]; + return; + } + y += SWATCH_SIZE + 14; + } else { + // Solid color swatches + if (swatch_hit(mx, cy, x + 70, y, &idx)) { + s.bg_solid = bg_palette[idx]; + return; + } + y += SWATCH_SIZE + 14; + } + + // Separator + y += 12; + + // Panel color swatches + if (swatch_hit(mx, cy, x + 110, y, &idx)) { + s.panel_color = panel_palette[idx]; + return; + } + y += SWATCH_SIZE + 14; + + // Separator + y += 12; + + // Accent color swatches + if (swatch_hit(mx, cy, x + 110, y, &idx)) { + s.accent_color = accent_palette[idx]; + return; + } + } else if (st->active_tab == 1) { + // Display tab + int x = 16; + int y = 20; + int btn_w = 60; + int btn_h = 28; + int bx = x + 180; + + // Window Shadows: On + if (mx >= bx && mx < bx + btn_w && cy >= y && cy < y + btn_h) { + s.show_shadows = true; + return; + } + // Window Shadows: Off + if (mx >= bx + btn_w + 8 && mx < bx + btn_w * 2 + 8 && cy >= y && cy < y + btn_h) { + s.show_shadows = false; + return; + } + y += btn_h + 20 + 16; + + // Clock: 24h + if (mx >= bx && mx < bx + btn_w && cy >= y && cy < y + btn_h) { + s.clock_24h = true; + return; + } + // Clock: 12h + if (mx >= bx + btn_w + 8 && mx < bx + btn_w * 2 + 8 && cy >= y && cy < y + btn_h) { + s.clock_24h = false; + return; + } + y += btn_h + 20 + 16; + + // UI Scale buttons + int sbw = 68; + // Small + if (mx >= bx && mx < bx + sbw && cy >= y && cy < y + btn_h) { + s.ui_scale = 0; + apply_ui_scale(0); + return; + } + // Default + if (mx >= bx + sbw + 8 && mx < bx + sbw * 2 + 8 && cy >= y && cy < y + btn_h) { + s.ui_scale = 1; + apply_ui_scale(1); + return; + } + // Large + if (mx >= bx + (sbw + 8) * 2 && mx < bx + sbw * 3 + 16 && cy >= y && cy < y + btn_h) { + s.ui_scale = 2; + apply_ui_scale(2); + return; + } + } +} + +// ============================================================================ +// Cleanup +// ============================================================================ + +static void settings_on_close(Window* win) { if (win->app_data) { zenith::mfree(win->app_data); win->app_data = nullptr; @@ -69,20 +487,23 @@ static void about_on_close(Window* win) { } // ============================================================================ -// About launcher +// Settings launcher // ============================================================================ void open_settings(DesktopState* ds) { - int idx = desktop_create_window(ds, "About", 280, 150, 380, 280); + int idx = desktop_create_window(ds, "Settings", 200, 100, 480, 420); if (idx < 0) return; Window* win = &ds->windows[idx]; - AboutState* st = (AboutState*)zenith::malloc(sizeof(AboutState)); - zenith::memset(st, 0, sizeof(AboutState)); + SettingsState* st = (SettingsState*)zenith::malloc(sizeof(SettingsState)); + zenith::memset(st, 0, sizeof(SettingsState)); + st->desktop = ds; + st->active_tab = 0; zenith::get_info(&st->sys_info); st->uptime_ms = zenith::get_milliseconds(); win->app_data = st; - win->on_draw = about_on_draw; - win->on_close = about_on_close; + win->on_draw = settings_on_draw; + win->on_mouse = settings_on_mouse; + win->on_close = settings_on_close; } diff --git a/programs/src/desktop/app_sysinfo.cpp b/programs/src/desktop/app_sysinfo.cpp index ed04fdb..07b52d0 100644 --- a/programs/src/desktop/app_sysinfo.cpp +++ b/programs/src/desktop/app_sysinfo.cpp @@ -31,7 +31,7 @@ static void sysinfo_on_draw(Window* win, Framebuffer& fb) { // Title c.text(x, y, "System Information", colors::ACCENT); - y += FONT_HEIGHT + 12; + y += system_font_height() + 12; // Separator c.hline(x, y, c.w - 2 * x, colors::BORDER); @@ -52,7 +52,7 @@ static void sysinfo_on_draw(Window* win, Framebuffer& fb) { // Max Processes snprintf(line, sizeof(line), "Max PIDs: %d", (int)si->sys_info.maxProcesses); c.text(x, y, line, colors::TEXT_COLOR); - y += FONT_HEIGHT + 12; + y += system_font_height() + 12; // Uptime int up_sec = (int)(si->uptime_ms / 1000); @@ -60,11 +60,11 @@ static void sysinfo_on_draw(Window* win, Framebuffer& fb) { int up_hr = up_min / 60; snprintf(line, sizeof(line), "Uptime: %d:%02d:%02d", up_hr, up_min % 60, up_sec % 60); c.text(x, y, line, colors::TEXT_COLOR); - y += FONT_HEIGHT + 12; + y += system_font_height() + 12; // Network section c.text(x, y, "Network", colors::ACCENT); - y += FONT_HEIGHT + 8; + y += system_font_height() + 8; c.hline(x, y, c.w - 2 * x, colors::BORDER); y += 8; diff --git a/programs/src/desktop/app_terminal.cpp b/programs/src/desktop/app_terminal.cpp index 8fc8468..b4e7cb8 100644 --- a/programs/src/desktop/app_terminal.cpp +++ b/programs/src/desktop/app_terminal.cpp @@ -15,6 +15,14 @@ static void terminal_on_draw(Window* win, Framebuffer& fb) { if (!ts) return; Rect cr = win->content_rect(); + + // Check if window was resized and update terminal grid + int new_cols = cr.w / mono_cell_width(); + int new_rows = cr.h / mono_cell_height(); + if (new_cols != ts->cols || new_rows != ts->rows) { + terminal_resize(ts, new_cols, new_rows); + } + terminal_render(ts, win->content, cr.w, cr.h); } @@ -52,8 +60,8 @@ void open_terminal(DesktopState* ds) { Window* win = &ds->windows[idx]; Rect cr = win->content_rect(); - int cols = cr.w / FONT_WIDTH; - int rows = cr.h / FONT_HEIGHT; + int cols = cr.w / mono_cell_width(); + int rows = cr.h / mono_cell_height(); TerminalState* ts = (TerminalState*)zenith::malloc(sizeof(TerminalState)); zenith::memset(ts, 0, sizeof(TerminalState)); diff --git a/programs/src/desktop/app_texteditor.cpp b/programs/src/desktop/app_texteditor.cpp index 6014685..75b7406 100644 --- a/programs/src/desktop/app_texteditor.cpp +++ b/programs/src/desktop/app_texteditor.cpp @@ -11,6 +11,8 @@ // Text Editor state // ============================================================================ +static constexpr int TE_TOOLBAR_H = 36; +static constexpr int TE_PATHBAR_H = 32; static constexpr int TE_STATUS_H = 24; static constexpr int TE_LINE_NUM_W = 48; static constexpr int TE_INIT_CAP = 4096; @@ -33,6 +35,11 @@ struct TextEditorState { char filepath[256]; char filename[64]; DesktopState* desktop; + + bool show_pathbar; + char pathbar_text[256]; + int pathbar_cursor; + int pathbar_len; }; // ============================================================================ @@ -203,7 +210,7 @@ static void te_move_end(TextEditorState* te) { // Scrolling // ============================================================================ -static void te_ensure_cursor_visible(TextEditorState* te, int visible_lines) { +static void te_ensure_cursor_visible(TextEditorState* te, int visible_lines, int content_w) { if (te->cursor_line < te->scroll_y) { te->scroll_y = te->cursor_line; } @@ -212,13 +219,14 @@ static void te_ensure_cursor_visible(TextEditorState* te, int visible_lines) { } // Horizontal scroll - int cursor_px = te->cursor_col * FONT_WIDTH; - int view_w = 580 - TE_LINE_NUM_W; // approximate - if (cursor_px - te->scroll_x > view_w - FONT_WIDTH * 2) { - te->scroll_x = cursor_px - view_w + FONT_WIDTH * 4; + int cell_w = mono_cell_width(); + int cursor_px = te->cursor_col * cell_w; + int view_w = content_w - TE_LINE_NUM_W; + if (cursor_px - te->scroll_x > view_w - cell_w * 2) { + te->scroll_x = cursor_px - view_w + cell_w * 4; } if (cursor_px < te->scroll_x) { - te->scroll_x = cursor_px - FONT_WIDTH * 2; + te->scroll_x = cursor_px - cell_w * 2; if (te->scroll_x < 0) te->scroll_x = 0; } } @@ -290,21 +298,96 @@ static void texteditor_on_draw(Window* win, Framebuffer& fb) { Canvas c(win); c.fill(colors::WINDOW_BG); - int text_area_h = c.h - TE_STATUS_H; - int visible_lines = text_area_h / FONT_HEIGHT; + int cell_w = mono_cell_width(); + int cell_h = mono_cell_height(); - te_ensure_cursor_visible(te, visible_lines); + // ---- Toolbar (36px) ---- + c.fill_rect(0, 0, c.w, TE_TOOLBAR_H, Color::from_rgb(0xF5, 0xF5, 0xF5)); + + // Open button + Color btn_bg = Color::from_rgb(0xE8, 0xE8, 0xE8); + c.fill_rounded_rect(4, 6, 24, 24, 3, btn_bg); + if (te->desktop && te->desktop->icon_folder.pixels) + c.icon(8, 10, te->desktop->icon_folder); + + // Save button + c.fill_rounded_rect(32, 6, 24, 24, 3, btn_bg); + if (te->desktop && te->desktop->icon_save.pixels) + c.icon(36, 10, te->desktop->icon_save); + + // Vertical separator + c.vline(60, 4, 28, colors::BORDER); + + // Filename + modified flag + char toolbar_label[128]; + if (te->filename[0]) { + snprintf(toolbar_label, 128, "%s%s", te->filename, te->modified ? " [modified]" : ""); + } else { + snprintf(toolbar_label, 128, "Untitled%s", te->modified ? " [modified]" : ""); + } + int sfh = system_font_height(); + c.text(68, (TE_TOOLBAR_H - sfh) / 2, toolbar_label, colors::TEXT_COLOR); + + // Toolbar bottom separator + c.hline(0, TE_TOOLBAR_H - 1, c.w, colors::BORDER); + + // ---- Path bar (conditional, 32px) ---- + int editor_y_start = TE_TOOLBAR_H; + + if (te->show_pathbar) { + int pb_y = TE_TOOLBAR_H; + c.fill_rect(0, pb_y, c.w, TE_PATHBAR_H, Color::from_rgb(0xF0, 0xF0, 0xF0)); + + // Text input box + int inp_x = 8; + int inp_y = pb_y + 4; + int btn_w = 56; + int inp_w = c.w - inp_x - btn_w - 12; + int inp_h = 24; + + c.fill_rect(inp_x, inp_y, inp_w, inp_h, colors::WHITE); + c.rect(inp_x, inp_y, inp_w, inp_h, colors::ACCENT); + + // Path text + int text_y = inp_y + (inp_h - sfh) / 2; + c.text(inp_x + 4, text_y, te->pathbar_text, colors::TEXT_COLOR); + + // Blinking cursor in path input + char prefix[256]; + int plen = te->pathbar_cursor; + if (plen > 255) plen = 255; + for (int i = 0; i < plen; i++) prefix[i] = te->pathbar_text[i]; + prefix[plen] = '\0'; + int cx = inp_x + 4 + text_width(prefix); + c.fill_rect(cx, inp_y + 3, 2, inp_h - 6, colors::ACCENT); + + // Open button + int ob_x = inp_x + inp_w + 6; + c.button(ob_x, inp_y, btn_w, inp_h, "Open", colors::ACCENT, colors::WHITE, 3); + + // Path bar bottom separator + c.hline(0, pb_y + TE_PATHBAR_H - 1, c.w, colors::BORDER); + + editor_y_start = TE_TOOLBAR_H + TE_PATHBAR_H; + } + + // ---- Editor area ---- + int text_area_h = c.h - editor_y_start - TE_STATUS_H; + int visible_lines = text_area_h / cell_h; + if (visible_lines < 1) visible_lines = 1; + + te_ensure_cursor_visible(te, visible_lines, c.w); // Line number gutter background - c.fill_rect(0, 0, TE_LINE_NUM_W, text_area_h, Color::from_rgb(0xF0, 0xF0, 0xF0)); + c.fill_rect(0, editor_y_start, TE_LINE_NUM_W, text_area_h, Color::from_rgb(0xF0, 0xF0, 0xF0)); // Gutter separator - c.vline(TE_LINE_NUM_W, 0, text_area_h, colors::BORDER); + c.vline(TE_LINE_NUM_W, editor_y_start, text_area_h, colors::BORDER); // Draw lines Color linenum_color = Color::from_rgb(0x99, 0x99, 0x99); Color cursor_line_color = Color::from_rgb(0xFF, 0xFD, 0xE8); - uint32_t text_px = colors::TEXT_COLOR.to_pixel(); + Color text_color = colors::TEXT_COLOR; int text_start_x = TE_LINE_NUM_W + 4; @@ -312,12 +395,12 @@ static void texteditor_on_draw(Window* win, Framebuffer& fb) { int line = te->scroll_y + vis; if (line >= te->line_count) break; - int py = vis * FONT_HEIGHT; - if (py >= text_area_h) break; + int py = editor_y_start + vis * cell_h; + if (py >= editor_y_start + text_area_h) break; // Cursor line highlighting if (line == te->cursor_line) { - int hl_h = gui_min(FONT_HEIGHT, text_area_h - py); + int hl_h = gui_min(cell_h, editor_y_start + text_area_h - py); if (hl_h > 0) c.fill_rect(TE_LINE_NUM_W + 1, py, c.w - TE_LINE_NUM_W - 1, hl_h, cursor_line_color); } @@ -325,28 +408,37 @@ static void texteditor_on_draw(Window* win, Framebuffer& fb) { // Line number char num_str[8]; snprintf(num_str, 8, "%4d", line + 1); - c.text(4, py, num_str, linenum_color); + c.text_mono(4, py, num_str, linenum_color); // Line text (per-character rendering with horizontal scroll clipping) int line_start = te->line_offsets[line]; int line_len = te_line_length(te, line); for (int ci = 0; ci < line_len; ci++) { - int px = text_start_x + ci * FONT_WIDTH - te->scroll_x; - if (px + FONT_WIDTH <= TE_LINE_NUM_W + 1) continue; + int px = text_start_x + ci * cell_w - te->scroll_x; + if (px + cell_w <= TE_LINE_NUM_W + 1) continue; if (px >= c.w) break; char ch = te->buffer[line_start + ci]; if (ch >= 32 || ch < 0) { - const uint8_t* glyph = &font_data[(unsigned char)ch * FONT_HEIGHT]; - for (int fy = 0; fy < FONT_HEIGHT && py + fy < text_area_h; fy++) { - uint8_t bits = glyph[fy]; - for (int fx = 0; fx < FONT_WIDTH; fx++) { - if (bits & (0x80 >> fx)) { - int dx = px + fx; - int dy = py + fy; - if (dx > TE_LINE_NUM_W && dx < c.w && dy >= 0 && dy < text_area_h) - c.pixels[dy * c.w + dx] = text_px; + if (fonts::mono && fonts::mono->valid) { + GlyphCache* gc = fonts::mono->get_cache(fonts::TERM_SIZE); + fonts::mono->draw_char_to_buffer( + c.pixels, c.w, c.h, px, py + gc->ascent, + ch, text_color, gc); + } else { + // bitmap fallback + uint32_t text_px = text_color.to_pixel(); + const uint8_t* glyph = &font_data[(unsigned char)ch * FONT_HEIGHT]; + for (int fy = 0; fy < FONT_HEIGHT && py + fy < editor_y_start + text_area_h; fy++) { + uint8_t bits = glyph[fy]; + for (int fx = 0; fx < FONT_WIDTH; fx++) { + if (bits & (0x80 >> fx)) { + int dx = px + fx; + int dy = py + fy; + if (dx > TE_LINE_NUM_W && dx < c.w && dy >= 0 && dy < c.h) + c.pixels[dy * c.w + dx] = text_px; + } } } } @@ -355,9 +447,9 @@ static void texteditor_on_draw(Window* win, Framebuffer& fb) { // Draw cursor if (line == te->cursor_line) { - int cx = text_start_x + te->cursor_col * FONT_WIDTH - te->scroll_x; + int cx = text_start_x + te->cursor_col * cell_w - te->scroll_x; if (cx > TE_LINE_NUM_W && cx + 2 <= c.w) { - int cur_h = gui_min(FONT_HEIGHT, text_area_h - py); + int cur_h = gui_min(cell_h, editor_y_start + text_area_h - py); if (cur_h > 0) c.fill_rect(cx, py, 2, cur_h, colors::ACCENT); } @@ -368,20 +460,21 @@ static void texteditor_on_draw(Window* win, Framebuffer& fb) { int status_y = c.h - TE_STATUS_H; c.fill_rect(0, status_y, c.w, TE_STATUS_H, Color::from_rgb(0x2B, 0x3E, 0x50)); - // Filename + modified flag + // Cursor position (right side) + char status_right[32]; + snprintf(status_right, 32, "Ln %d, Col %d ", te->cursor_line + 1, te->cursor_col + 1); + int sr_w = text_width(status_right); + int status_text_y = status_y + (TE_STATUS_H - sfh) / 2; + c.text(c.w - sr_w - 4, status_text_y, status_right, colors::PANEL_TEXT); + + // Filename + modified flag (left side) char status_left[128]; if (te->filename[0]) { snprintf(status_left, 128, " %s%s", te->filename, te->modified ? " [modified]" : ""); } else { snprintf(status_left, 128, " Untitled%s", te->modified ? " [modified]" : ""); } - c.text(4, status_y + (TE_STATUS_H - FONT_HEIGHT) / 2, status_left, colors::PANEL_TEXT); - - // Cursor position (right side) - char status_right[32]; - snprintf(status_right, 32, "Ln %d, Col %d ", te->cursor_line + 1, te->cursor_col + 1); - int sr_w = zenith::slen(status_right) * FONT_WIDTH; - c.text(c.w - sr_w - 4, status_y + (TE_STATUS_H - FONT_HEIGHT) / 2, status_right, colors::PANEL_TEXT); + c.text(4, status_text_y, status_left, colors::PANEL_TEXT); } // ============================================================================ @@ -395,15 +488,62 @@ static void texteditor_on_mouse(Window* win, MouseEvent& ev) { Rect cr = win->content_rect(); int local_x = ev.x - cr.x; int local_y = ev.y - cr.y; - int text_area_h = cr.h - TE_STATUS_H; - if (ev.left_pressed() && local_y < text_area_h && local_x > TE_LINE_NUM_W) { - // Click to position cursor - int clicked_line = te->scroll_y + local_y / FONT_HEIGHT; + int cell_w = mono_cell_width(); + int cell_h = mono_cell_height(); + + int editor_y_start = TE_TOOLBAR_H + (te->show_pathbar ? TE_PATHBAR_H : 0); + int text_area_h = cr.h - editor_y_start - TE_STATUS_H; + + // ---- Toolbar clicks ---- + if (ev.left_pressed() && local_y < TE_TOOLBAR_H) { + // Open button + if (local_x >= 4 && local_x < 28 && local_y >= 6 && local_y < 30) { + te->show_pathbar = !te->show_pathbar; + if (te->show_pathbar) { + // Pre-fill with current filepath + zenith::strncpy(te->pathbar_text, te->filepath, 255); + te->pathbar_len = zenith::slen(te->pathbar_text); + te->pathbar_cursor = te->pathbar_len; + } + return; + } + // Save button + if (local_x >= 32 && local_x < 56 && local_y >= 6 && local_y < 30) { + te_save_file(te); + return; + } + return; + } + + // ---- Path bar clicks ---- + if (te->show_pathbar && local_y >= TE_TOOLBAR_H && local_y < TE_TOOLBAR_H + TE_PATHBAR_H) { + if (ev.left_pressed()) { + // Check "Open" button region + int btn_w = 56; + int inp_w = cr.w - 8 - btn_w - 12; + int ob_x = 8 + inp_w + 6; + if (local_x >= ob_x && local_x < ob_x + btn_w) { + if (te->pathbar_text[0]) { + te_load_file(te, te->pathbar_text); + // Update window title + char title[64]; + snprintf(title, 64, "%s - Editor", te->filename); + zenith::strncpy(win->title, title, 63); + te->show_pathbar = false; + } + } + } + return; + } + + // ---- Editor area clicks ---- + if (ev.left_pressed() && local_y >= editor_y_start && local_y < editor_y_start + text_area_h && local_x > TE_LINE_NUM_W) { + int clicked_line = te->scroll_y + (local_y - editor_y_start) / cell_h; if (clicked_line >= te->line_count) clicked_line = te->line_count - 1; if (clicked_line < 0) clicked_line = 0; - int clicked_col = (local_x - TE_LINE_NUM_W - 4 + te->scroll_x + FONT_WIDTH / 2) / FONT_WIDTH; + int clicked_col = (local_x - TE_LINE_NUM_W - 4 + te->scroll_x + cell_w / 2) / cell_w; if (clicked_col < 0) clicked_col = 0; int line_len = te_line_length(te, clicked_line); if (clicked_col > line_len) clicked_col = line_len; @@ -412,11 +552,11 @@ static void texteditor_on_mouse(Window* win, MouseEvent& ev) { te_update_cursor_pos(te); } - // Scroll - if (ev.scroll != 0 && local_y < text_area_h) { + // ---- Scroll ---- + if (ev.scroll != 0 && local_y >= editor_y_start && local_y < editor_y_start + text_area_h) { te->scroll_y -= ev.scroll * 3; if (te->scroll_y < 0) te->scroll_y = 0; - int max_scroll = te->line_count - (text_area_h / FONT_HEIGHT) + 1; + int max_scroll = te->line_count - (text_area_h / cell_h) + 1; if (max_scroll < 0) max_scroll = 0; if (te->scroll_y > max_scroll) te->scroll_y = max_scroll; } @@ -430,12 +570,71 @@ static void texteditor_on_key(Window* win, const Zenith::KeyEvent& key) { TextEditorState* te = (TextEditorState*)win->app_data; if (!te || !key.pressed) return; + // ---- Path bar input mode ---- + if (te->show_pathbar) { + if (key.ascii == '\n' || key.ascii == '\r') { + if (te->pathbar_text[0]) { + te_load_file(te, te->pathbar_text); + char title[64]; + snprintf(title, 64, "%s - Editor", te->filename); + zenith::strncpy(win->title, title, 63); + te->show_pathbar = false; + } + return; + } + if (key.scancode == 0x01) { // Escape + te->show_pathbar = false; + return; + } + if (key.ascii == '\b' || key.scancode == 0x0E) { + if (te->pathbar_cursor > 0) { + for (int i = te->pathbar_cursor - 1; i < te->pathbar_len - 1; i++) + te->pathbar_text[i] = te->pathbar_text[i + 1]; + te->pathbar_len--; + te->pathbar_cursor--; + te->pathbar_text[te->pathbar_len] = '\0'; + } + return; + } + if (key.scancode == 0x4B) { // Left + if (te->pathbar_cursor > 0) te->pathbar_cursor--; + return; + } + if (key.scancode == 0x4D) { // Right + if (te->pathbar_cursor < te->pathbar_len) te->pathbar_cursor++; + return; + } + if (key.ascii >= 32 && key.ascii < 127 && te->pathbar_len < 254) { + for (int i = te->pathbar_len; i > te->pathbar_cursor; i--) + te->pathbar_text[i] = te->pathbar_text[i - 1]; + te->pathbar_text[te->pathbar_cursor] = key.ascii; + te->pathbar_cursor++; + te->pathbar_len++; + te->pathbar_text[te->pathbar_len] = '\0'; + return; + } + return; + } + + // ---- Normal editor mode ---- + // Ctrl+S: save if (key.ctrl && (key.ascii == 's' || key.ascii == 'S')) { te_save_file(te); return; } + // Ctrl+O: open + if (key.ctrl && (key.ascii == 'o' || key.ascii == 'O')) { + te->show_pathbar = !te->show_pathbar; + if (te->show_pathbar) { + zenith::strncpy(te->pathbar_text, te->filepath, 255); + te->pathbar_len = zenith::slen(te->pathbar_text); + te->pathbar_cursor = te->pathbar_len; + } + return; + } + // Arrow keys if (key.scancode == 0x48) { te_move_up(te); return; } if (key.scancode == 0x50) { te_move_down(te); return; } @@ -503,6 +702,10 @@ void open_texteditor(DesktopState* ds) { te->buf_len = 0; te->modified = false; te->desktop = ds; + te->show_pathbar = false; + te->pathbar_text[0] = '\0'; + te->pathbar_cursor = 0; + te->pathbar_len = 0; te_recompute_lines(te); te_update_cursor_pos(te); @@ -536,6 +739,10 @@ void open_texteditor_with_file(DesktopState* ds, const char* path) { te->buf_len = 0; te->modified = false; te->desktop = ds; + te->show_pathbar = false; + te->pathbar_text[0] = '\0'; + te->pathbar_cursor = 0; + te->pathbar_len = 0; // Initialize line index for empty document first (ensures line_offsets // is non-null even if te_load_file fails to open the file) diff --git a/programs/src/desktop/app_wiki.cpp b/programs/src/desktop/app_wiki.cpp index 4489875..2a568bc 100644 --- a/programs/src/desktop/app_wiki.cpp +++ b/programs/src/desktop/app_wiki.cpp @@ -168,7 +168,8 @@ static void wiki_build_display(WikiState* ws, const char* title, ws->lineCount = 0; ws->scrollY = 0; - int maxChars = (contentW - 24 - WIKI_SCROLLBAR_W) / FONT_WIDTH; + int char_w = mono_cell_width(); + int maxChars = (contentW - 24 - WIKI_SCROLLBAR_W) / char_w; if (maxChars < 20) maxChars = 20; Color accent_c = colors::ACCENT; @@ -301,7 +302,7 @@ static void wiki_on_draw(Window* win, Framebuffer& fb) { c.rect(tb_x, tb_y, tb_w, tb_h, colors::BORDER); // Search text - c.text(tb_x + 4, tb_y + (tb_h - FONT_HEIGHT) / 2, ws->searchQuery, colors::TEXT_COLOR); + c.text(tb_x + 4, tb_y + (tb_h - system_font_height()) / 2, ws->searchQuery, colors::TEXT_COLOR); // ---- Search button ---- int btn_x = tb_x + tb_w + 6; @@ -313,7 +314,8 @@ static void wiki_on_draw(Window* win, Framebuffer& fb) { // ---- Content area ---- int content_y = WIKI_TOOLBAR_H + 1; int content_h = c.h - content_y; - int visibleLines = content_h / (FONT_HEIGHT + 4); + int wiki_sfh = system_font_height(); + int visibleLines = content_h / (wiki_sfh + 4); if (visibleLines < 1) visibleLines = 1; if (ws->mode == WikiState::FETCHING) { @@ -326,8 +328,8 @@ static void wiki_on_draw(Window* win, Framebuffer& fb) { Color::from_rgb(0x88, 0x88, 0x88)); } else if (ws->mode == WikiState::DONE && ws->lineCount > 0) { int y = content_y + 8; - int lineH = FONT_HEIGHT + 4; - for (int i = ws->scrollY; i < ws->lineCount && y + FONT_HEIGHT < c.h; i++) { + int lineH = wiki_sfh + 4; + for (int i = ws->scrollY; i < ws->lineCount && y + wiki_sfh < c.h; i++) { WikiDisplayLine* dl = &ws->lines[i]; if (dl->text[0] != '\0') { c.text(12, y, dl->text, dl->color); @@ -412,7 +414,7 @@ static void wiki_on_mouse(Window* win, MouseEvent& ev) { if (ev.scroll != 0 && ws->mode == WikiState::DONE && ws->lineCount > 0) { int ch = cr.h; int content_h = ch - WIKI_TOOLBAR_H - 1; - int visibleLines = content_h / (FONT_HEIGHT + 4); + int visibleLines = content_h / (system_font_height() + 4); int maxScroll = ws->lineCount - visibleLines; if (maxScroll < 0) maxScroll = 0; @@ -443,7 +445,7 @@ static void wiki_on_key(Window* win, const Zenith::KeyEvent& key) { } } int content_h = cr.h - WIKI_TOOLBAR_H - 1; - int visibleLines = content_h / (FONT_HEIGHT + 4); + int visibleLines = content_h / (system_font_height() + 4); if (visibleLines < 1) visibleLines = 1; int maxScroll = ws->lineCount - visibleLines; if (maxScroll < 0) maxScroll = 0; diff --git a/programs/src/desktop/apps_common.hpp b/programs/src/desktop/apps_common.hpp index 94147ed..4b7cc77 100644 --- a/programs/src/desktop/apps_common.hpp +++ b/programs/src/desktop/apps_common.hpp @@ -167,5 +167,10 @@ void open_texteditor(DesktopState* ds); void open_texteditor_with_file(DesktopState* ds, const char* path); void open_klog(DesktopState* ds); void open_wiki(DesktopState* ds); +void open_procmgr(DesktopState* ds); +void open_mandelbrot(DesktopState* ds); +void open_devexplorer(DesktopState* ds); void open_settings(DesktopState* ds); +void open_doom(DesktopState* ds); void open_reboot_dialog(DesktopState* ds); +void desktop_poll_external_windows(DesktopState* ds); diff --git a/programs/src/desktop/main.cpp b/programs/src/desktop/main.cpp index 4cb4b65..6816be1 100644 --- a/programs/src/desktop/main.cpp +++ b/programs/src/desktop/main.cpp @@ -23,6 +23,9 @@ void gui::desktop_init(DesktopState* ds) { ds->fb.clear(colors::DESKTOP_BG); ds->fb.flip(); + // Load TrueType fonts + fonts::init(); + ds->window_count = 0; ds->focused_window = -1; ds->prev_buttons = 0; @@ -58,6 +61,22 @@ void gui::desktop_init(DesktopState* ds) { ds->icon_settings = svg_load("0:/icons/help-about.svg", 20, 20, defColor); ds->icon_reboot = svg_load("0:/icons/system-reboot.svg", 20, 20, defColor); + ds->icon_doom = svg_load("0:/icons/doom.svg", 20, 20, defColor); + ds->icon_procmgr = svg_load("0:/icons/system-monitor.svg", 20, 20, defColor); + ds->icon_mandelbrot = svg_load("0:/icons/applications-science.svg", 20, 20, defColor); + ds->icon_devexplorer = svg_load("0:/icons/hardware.svg", 20, 20, defColor); + + // Settings defaults + ds->settings.bg_gradient = true; + ds->settings.bg_grad_top = Color::from_rgb(0xD0, 0xD8, 0xE8); + ds->settings.bg_grad_bottom = Color::from_rgb(0xA0, 0xA8, 0xB8); + ds->settings.bg_solid = Color::from_rgb(0xD0, 0xD8, 0xE8); + ds->settings.panel_color = colors::PANEL_BG; + ds->settings.accent_color = colors::ACCENT; + ds->settings.show_shadows = true; + ds->settings.clock_24h = true; + ds->settings.ui_scale = 1; + ds->ctx_menu_open = false; ds->ctx_menu_x = 0; ds->ctx_menu_y = 0; @@ -100,6 +119,8 @@ int gui::desktop_create_window(DesktopState* ds, const char* title, int x, int y win->on_close = nullptr; win->on_poll = nullptr; win->app_data = nullptr; + win->external = false; + win->ext_win_id = -1; // Unfocus previous window if (ds->focused_window >= 0 && ds->focused_window < ds->window_count) { @@ -115,10 +136,19 @@ void gui::desktop_close_window(DesktopState* ds, int idx) { if (idx < 0 || idx >= ds->window_count) return; Window* win = &ds->windows[idx]; + + // For external windows, send a close event instead of freeing the buffer + if (win->external) { + Zenith::WinEvent ev; + zenith::memset(&ev, 0, sizeof(ev)); + ev.type = 3; // close + zenith::win_sendevent(win->ext_win_id, &ev); + } + if (win->on_close) win->on_close(win); - // Free content buffer - if (win->content) { + // Free content buffer (skip for external windows — shared memory) + if (win->content && !win->external) { zenith::free(win->content); win->content = nullptr; } @@ -180,7 +210,8 @@ void gui::desktop_draw_window(DesktopState* ds, int idx) { int h = win->frame.h; // Draw shadow - draw_shadow(fb, x, y, w, h, SHADOW_SIZE, colors::SHADOW); + if (ds->settings.show_shadows) + draw_shadow(fb, x, y, w, h, SHADOW_SIZE, colors::SHADOW); // Draw window body fb.fill_rect(x, y, w, h, colors::WINDOW_BG); @@ -205,7 +236,7 @@ void gui::desktop_draw_window(DesktopState* ds, int idx) { // Draw title text centered in titlebar (after buttons) int title_x = x + 12 + 44 + BTN_RADIUS * 2 + 12; // after buttons - int title_y = y + (TITLEBAR_HEIGHT - FONT_HEIGHT) / 2; + int title_y = y + (TITLEBAR_HEIGHT - system_font_height()) / 2; int title_w = text_width(win->title); // Center in remaining space int remaining_w = w - (title_x - x) - 12; @@ -222,9 +253,31 @@ void gui::desktop_draw_window(DesktopState* ds, int idx) { // Blit content buffer to framebuffer (clip to actual buffer size during resize) Rect cr = win->content_rect(); if (win->content) { - int blit_w = cr.w < win->content_w ? cr.w : win->content_w; - int blit_h = cr.h < win->content_h ? cr.h : win->content_h; - fb.blit(cr.x, cr.y, blit_w, blit_h, win->content); + if (win->external && (cr.w != win->content_w || cr.h != win->content_h)) { + // Nearest-neighbor scale for external windows (fixed-size shared buffer) + int src_w = win->content_w; + int src_h = win->content_h; + int dst_w = cr.w; + int dst_h = cr.h; + uint32_t* buf = fb.buffer(); + int pitch = fb.pitch(); + for (int y = 0; y < dst_h; y++) { + int dy = cr.y + y; + if (dy < 0 || dy >= fb.height()) continue; + int sy = y * src_h / dst_h; + uint32_t* dst_row = (uint32_t*)((uint8_t*)buf + dy * pitch); + uint32_t* src_row = win->content + sy * src_w; + for (int x = 0; x < dst_w; x++) { + int dx = cr.x + x; + if (dx < 0 || dx >= fb.width()) continue; + dst_row[dx] = src_row[x * src_w / dst_w]; + } + } + } else { + int blit_w = cr.w < win->content_w ? cr.w : win->content_w; + int blit_h = cr.h < win->content_h ? cr.h : win->content_h; + fb.blit(cr.x, cr.y, blit_w, blit_h, win->content); + } } } @@ -233,11 +286,12 @@ void gui::desktop_draw_panel(DesktopState* ds) { int sw = ds->screen_w; // Panel gradient background (slightly lighter at top) + Color pc = ds->settings.panel_color; for (int y = 0; y < PANEL_HEIGHT; y++) { int t = y * 255 / PANEL_HEIGHT; - uint8_t r = colors::PANEL_BG.r + (10 - t * 10 / 255); - uint8_t g = colors::PANEL_BG.g + (10 - t * 10 / 255); - uint8_t b = colors::PANEL_BG.b + (10 - t * 10 / 255); + uint8_t r = pc.r + (10 - t * 10 / 255); + uint8_t g = pc.g + (10 - t * 10 / 255); + uint8_t b = pc.b + (10 - t * 10 / 255); fb.fill_rect(0, y, sw, 1, Color::from_rgb(r, g, b)); } @@ -284,7 +338,7 @@ void gui::desktop_draw_panel(DesktopState* ds) { // Active window accent underline bar if (i == ds->focused_window) { - fb.fill_rect(indicator_x + 4, 26, iw - 8, 2, colors::ACCENT); + fb.fill_rect(indicator_x + 4, 26, iw - 8, 2, ds->settings.accent_color); } // Truncate title if too long @@ -292,7 +346,7 @@ void gui::desktop_draw_panel(DesktopState* ds) { zenith::strncpy(short_title, win->title, 18); int tx = indicator_x + pad; - int ty = 4 + (24 - FONT_HEIGHT) / 2; + int ty = 4 + (24 - system_font_height()) / 2; draw_text(fb, tx, ty, short_title, colors::PANEL_TEXT); indicator_x += iw + 4; @@ -302,11 +356,18 @@ void gui::desktop_draw_panel(DesktopState* ds) { Zenith::DateTime dt; zenith::gettime(&dt); - char clock_str[8]; - snprintf(clock_str, sizeof(clock_str), "%02d:%02d", (int)dt.Hour, (int)dt.Minute); + char clock_str[12]; + if (ds->settings.clock_24h) { + snprintf(clock_str, sizeof(clock_str), "%02d:%02d", (int)dt.Hour, (int)dt.Minute); + } else { + int h12 = (int)dt.Hour % 12; + if (h12 == 0) h12 = 12; + const char* ampm = dt.Hour < 12 ? "AM" : "PM"; + snprintf(clock_str, sizeof(clock_str), "%d:%02d %s", h12, (int)dt.Minute, ampm); + } int clock_w = text_width(clock_str); int clock_x = sw - clock_w - 12; - int clock_y = (PANEL_HEIGHT - FONT_HEIGHT) / 2; + int clock_y = (PANEL_HEIGHT - system_font_height()) / 2; draw_text(fb, clock_x, clock_y, clock_str, colors::PANEL_TEXT); // Date before clock @@ -314,7 +375,7 @@ void gui::desktop_draw_panel(DesktopState* ds) { int month_idx = dt.Month > 0 && dt.Month <= 12 ? dt.Month - 1 : 0; snprintf(date_str, sizeof(date_str), "%s %d", month_names[month_idx], (int)dt.Day); int date_w = text_width(date_str); - int date_x = clock_x - date_w - 16; + int date_x = clock_x - date_w - 10; draw_text(fb, date_x, clock_y, date_str, colors::PANEL_TEXT); // Network icon (to the left of the date) @@ -349,7 +410,7 @@ void gui::desktop_draw_panel(DesktopState* ds) { // App Menu (5 items with separator and rounded corners) // ============================================================================ -static constexpr int MENU_ITEM_COUNT = 9; +static constexpr int MENU_ITEM_COUNT = 13; static constexpr int MENU_W = 220; static constexpr int MENU_ITEM_H = 40; @@ -379,8 +440,12 @@ static void desktop_draw_app_menu(DesktopState* ds) { { "Calculator", &ds->icon_calculator }, { "Text Editor", &ds->icon_texteditor }, { "Kernel Log", &ds->icon_terminal }, + { "Processes", &ds->icon_procmgr }, + { "Mandelbrot", &ds->icon_mandelbrot }, + { "Devices", &ds->icon_devexplorer }, { "Wikipedia", &ds->icon_wikipedia }, - { "About", &ds->icon_settings }, + { "DOOM", &ds->icon_doom }, + { "Settings", &ds->icon_settings }, { "Reboot", &ds->icon_reboot }, }; @@ -391,7 +456,7 @@ static void desktop_draw_app_menu(DesktopState* ds) { int iy = menu_y + 4 + i * MENU_ITEM_H; // Thin separator lines before utility apps and before Settings - if (i == 3 || i == 7) { + if (i == 3 || i == 11) { int sep_y = iy - 1; for (int sx = menu_x + 8; sx < menu_x + MENU_W - 8; sx++) fb.put_pixel(sx, sep_y, colors::BORDER); @@ -414,7 +479,7 @@ static void desktop_draw_app_menu(DesktopState* ds) { // Label int tx = icon_x + 28; - int ty = item_rect.y + (MENU_ITEM_H - FONT_HEIGHT) / 2; + int ty = item_rect.y + (MENU_ITEM_H - system_font_height()) / 2; draw_text(fb, tx, ty, items[i].label, colors::TEXT_COLOR); } } @@ -434,7 +499,7 @@ static void desktop_draw_net_popup(DesktopState* ds) { int tx = popup_x + 12; int ty = popup_y + 10; - int line_h = FONT_HEIGHT + 6; + int line_h = system_font_height() + 6; char line[64]; Zenith::NetCfg& nc = ds->cached_net_cfg; @@ -645,14 +710,19 @@ void gui::desktop_compose(DesktopState* ds) { uint32_t* row = (uint32_t*)((uint8_t*)buf + y * pitch); if (y < grad_start) { // Panel area - will be overwritten by panel drawing - uint32_t px = colors::PANEL_BG.to_pixel(); + uint32_t px = ds->settings.panel_color.to_pixel(); + for (int x = 0; x < sw; x++) row[x] = px; + } else if (ds->settings.bg_gradient) { + int t = y - grad_start; + Color top = ds->settings.bg_grad_top; + Color bot = ds->settings.bg_grad_bottom; + uint8_t r = top.r - (top.r - bot.r) * t / grad_range; + uint8_t g = top.g - (top.g - bot.g) * t / grad_range; + uint8_t b = top.b - (top.b - bot.b) * t / grad_range; + uint32_t px = 0xFF000000 | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; for (int x = 0; x < sw; x++) row[x] = px; } else { - int t = y - grad_start; - uint8_t r = 0xD0 - (0xD0 - 0xA0) * t / grad_range; - uint8_t g = 0xD8 - (0xD8 - 0xA8) * t / grad_range; - uint8_t b = 0xE8 - (0xE8 - 0xB8) * t / grad_range; - uint32_t px = 0xFF000000 | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; + uint32_t px = ds->settings.bg_solid.to_pixel(); for (int x = 0; x < sw; x++) row[x] = px; } } @@ -720,7 +790,7 @@ void gui::desktop_compose(DesktopState* ds) { } int tx = icon_x + 28; - int ty = item_r.y + (CTX_ITEM_H - FONT_HEIGHT) / 2; + int ty = item_r.y + (CTX_ITEM_H - system_font_height()) / 2; draw_text(fb, tx, ty, ctx_items[i].label, colors::TEXT_COLOR); } } @@ -836,25 +906,29 @@ void gui::desktop_handle_mouse(DesktopState* ds) { win->saved_frame = win->frame; win->frame = {0, PANEL_HEIGHT, ds->screen_w / 2, ds->screen_h - PANEL_HEIGHT}; win->state = WIN_MAXIMIZED; - Rect cr = win->content_rect(); - if (cr.w != win->content_w || cr.h != win->content_h) { - if (win->content) zenith::free(win->content); - win->content_w = cr.w; - win->content_h = cr.h; - win->content = (uint32_t*)zenith::alloc(cr.w * cr.h * 4); - zenith::memset(win->content, 0xFF, cr.w * cr.h * 4); + if (!win->external) { + Rect cr = win->content_rect(); + if (cr.w != win->content_w || cr.h != win->content_h) { + if (win->content) zenith::free(win->content); + win->content_w = cr.w; + win->content_h = cr.h; + win->content = (uint32_t*)zenith::alloc(cr.w * cr.h * 4); + zenith::memset(win->content, 0xFF, cr.w * cr.h * 4); + } } } else if (mx >= ds->screen_w - 1) { win->saved_frame = win->frame; win->frame = {ds->screen_w / 2, PANEL_HEIGHT, ds->screen_w / 2, ds->screen_h - PANEL_HEIGHT}; win->state = WIN_MAXIMIZED; - Rect cr = win->content_rect(); - if (cr.w != win->content_w || cr.h != win->content_h) { - if (win->content) zenith::free(win->content); - win->content_w = cr.w; - win->content_h = cr.h; - win->content = (uint32_t*)zenith::alloc(cr.w * cr.h * 4); - zenith::memset(win->content, 0xFF, cr.w * cr.h * 4); + if (!win->external) { + Rect cr = win->content_rect(); + if (cr.w != win->content_w || cr.h != win->content_h) { + if (win->content) zenith::free(win->content); + win->content_w = cr.w; + win->content_h = cr.h; + win->content = (uint32_t*)zenith::alloc(cr.w * cr.h * 4); + zenith::memset(win->content, 0xFF, cr.w * cr.h * 4); + } } } } @@ -903,14 +977,16 @@ void gui::desktop_handle_mouse(DesktopState* ds) { } if (left_released) { win->resizing = false; - // Reallocate content buffer if dimensions changed - Rect cr = win->content_rect(); - if (cr.w != win->content_w || cr.h != win->content_h) { - if (win->content) zenith::free(win->content); - win->content_w = cr.w; - win->content_h = cr.h; - win->content = (uint32_t*)zenith::alloc(cr.w * cr.h * 4); - zenith::memset(win->content, 0xFF, cr.w * cr.h * 4); + // Reallocate content buffer if dimensions changed (skip for external) + if (!win->external) { + Rect cr = win->content_rect(); + if (cr.w != win->content_w || cr.h != win->content_h) { + if (win->content) zenith::free(win->content); + win->content_w = cr.w; + win->content_h = cr.h; + win->content = (uint32_t*)zenith::alloc(cr.w * cr.h * 4); + zenith::memset(win->content, 0xFF, cr.w * cr.h * 4); + } } win->dirty = true; } @@ -936,9 +1012,13 @@ void gui::desktop_handle_mouse(DesktopState* ds) { case 3: open_calculator(ds); break; case 4: open_texteditor(ds); break; case 5: open_klog(ds); break; - case 6: open_wiki(ds); break; - case 7: open_settings(ds); break; - case 8: open_reboot_dialog(ds); break; + case 6: open_procmgr(ds); break; + case 7: open_mandelbrot(ds); break; + case 8: open_devexplorer(ds); break; + case 9: open_wiki(ds); break; + case 10: open_doom(ds); break; + case 11: open_settings(ds); break; + case 12: open_reboot_dialog(ds); break; } ds->app_menu_open = false; } @@ -1046,13 +1126,16 @@ void gui::desktop_handle_mouse(DesktopState* ds) { win->frame = {0, PANEL_HEIGHT, ds->screen_w, ds->screen_h - PANEL_HEIGHT}; win->state = WIN_MAXIMIZED; } - Rect cr = win->content_rect(); - if (cr.w != win->content_w || cr.h != win->content_h) { - if (win->content) zenith::free(win->content); - win->content_w = cr.w; - win->content_h = cr.h; - win->content = (uint32_t*)zenith::alloc(cr.w * cr.h * 4); - zenith::memset(win->content, 0xFF, cr.w * cr.h * 4); + // Reallocate content buffer for local windows only + if (!win->external) { + Rect cr = win->content_rect(); + if (cr.w != win->content_w || cr.h != win->content_h) { + if (win->content) zenith::free(win->content); + win->content_w = cr.w; + win->content_h = cr.h; + win->content = (uint32_t*)zenith::alloc(cr.w * cr.h * 4); + zenith::memset(win->content, 0xFF, cr.w * cr.h * 4); + } } desktop_raise_window(ds, i); return; @@ -1097,10 +1180,22 @@ void gui::desktop_handle_mouse(DesktopState* ds) { if (cr.contains(mx, my)) { desktop_raise_window(ds, i); int new_idx = ds->window_count - 1; - if (ds->windows[new_idx].on_mouse) { + Window* raised = &ds->windows[new_idx]; + if (raised->external) { + // Forward mouse event to external window + Zenith::WinEvent wev; + zenith::memset(&wev, 0, sizeof(wev)); + wev.type = 1; // mouse + wev.mouse.x = mx - cr.x; + wev.mouse.y = my - cr.y; + wev.mouse.scroll = ev.scroll; + wev.mouse.buttons = buttons; + wev.mouse.prev_buttons = prev; + zenith::win_sendevent(raised->ext_win_id, &wev); + } else if (raised->on_mouse) { ev.x = mx; ev.y = my; - ds->windows[new_idx].on_mouse(&ds->windows[new_idx], ev); + raised->on_mouse(raised, ev); } return; } @@ -1120,8 +1215,20 @@ void gui::desktop_handle_mouse(DesktopState* ds) { if (ev.scroll != 0 && ds->focused_window >= 0) { Window* win = &ds->windows[ds->focused_window]; Rect cr = win->content_rect(); - if (cr.contains(mx, my) && win->on_mouse) { - win->on_mouse(win, ev); + if (cr.contains(mx, my)) { + if (win->external) { + Zenith::WinEvent wev; + zenith::memset(&wev, 0, sizeof(wev)); + wev.type = 1; // mouse + wev.mouse.x = mx - cr.x; + wev.mouse.y = my - cr.y; + wev.mouse.scroll = ev.scroll; + wev.mouse.buttons = buttons; + wev.mouse.prev_buttons = prev; + zenith::win_sendevent(win->ext_win_id, &wev); + } else if (win->on_mouse) { + win->on_mouse(win, ev); + } } } @@ -1147,10 +1254,8 @@ void gui::desktop_handle_mouse(DesktopState* ds) { } void gui::desktop_handle_keyboard(DesktopState* ds, const Zenith::KeyEvent& key) { - if (!key.pressed) return; - - // Global shortcuts - if (key.ctrl && key.alt) { + // Global shortcuts (only on key press) + if (key.pressed && key.ctrl && key.alt) { if (key.ascii == 't' || key.ascii == 'T') { open_terminal(ds); return; @@ -1175,17 +1280,121 @@ void gui::desktop_handle_keyboard(DesktopState* ds, const Zenith::KeyEvent& key) open_klog(ds); return; } + if (key.ascii == 'd' || key.ascii == 'D') { + open_doom(ds); + return; + } } // Dispatch to focused window if (ds->focused_window >= 0 && ds->focused_window < ds->window_count) { Window* win = &ds->windows[ds->focused_window]; - if (win->on_key) { + if (win->external) { + // Forward key event to external window via syscall + Zenith::WinEvent ev; + zenith::memset(&ev, 0, sizeof(ev)); + ev.type = 0; // key + ev.key = key; + zenith::win_sendevent(win->ext_win_id, &ev); + } else if (win->on_key) { win->on_key(win, key); } } } +// ============================================================================ +// External Window Polling +// ============================================================================ + +void desktop_poll_external_windows(DesktopState* ds) { + Zenith::WinInfo extWins[8]; + int extCount = zenith::win_enumerate(extWins, 8); + + // Check for new external windows and map them + for (int e = 0; e < extCount; e++) { + int extId = extWins[e].id; + + // Check if we already have this window + bool found = false; + for (int i = 0; i < ds->window_count; i++) { + if (ds->windows[i].external && ds->windows[i].ext_win_id == extId) { + found = true; + // Update dirty flag + if (extWins[e].dirty) { + ds->windows[i].dirty = true; + } + break; + } + } + + if (!found && ds->window_count < MAX_WINDOWS) { + // Map the pixel buffer into our address space + uint64_t va = zenith::win_map(extId); + if (va == 0) continue; + + int idx = ds->window_count; + Window* win = &ds->windows[idx]; + zenith::memset(win, 0, sizeof(Window)); + + zenith::strncpy(win->title, extWins[e].title, MAX_TITLE_LEN); + int w = extWins[e].width; + int h = extWins[e].height; + // Position the window centered-ish + int wx = (ds->screen_w - w - 2 * BORDER_WIDTH) / 2 + idx * 30; + int wy = PANEL_HEIGHT + 20 + idx * 30; + win->frame = {wx, wy, w + 2 * BORDER_WIDTH, h + TITLEBAR_HEIGHT + BORDER_WIDTH}; + win->state = WIN_NORMAL; + win->z_order = idx; + win->focused = true; + win->dirty = true; + win->dragging = false; + win->resizing = false; + win->saved_frame = win->frame; + + // Point content to the shared pixel buffer + win->content = (uint32_t*)va; + win->content_w = w; + win->content_h = h; + + win->on_draw = nullptr; + win->on_mouse = nullptr; + win->on_key = nullptr; + win->on_close = nullptr; + win->on_poll = nullptr; + win->app_data = nullptr; + win->external = true; + win->ext_win_id = extId; + + // Unfocus previous window + if (ds->focused_window >= 0 && ds->focused_window < ds->window_count) { + ds->windows[ds->focused_window].focused = false; + } + ds->focused_window = idx; + ds->window_count++; + } + } + + // Check for removed external windows (process exited) + for (int i = ds->window_count - 1; i >= 0; i--) { + if (!ds->windows[i].external) continue; + + int extId = ds->windows[i].ext_win_id; + bool stillExists = false; + for (int e = 0; e < extCount; e++) { + if (extWins[e].id == extId) { + stillExists = true; + break; + } + } + + if (!stillExists) { + // Window gone — remove without freeing content (shared memory) + ds->windows[i].content = nullptr; // prevent free + gui::desktop_close_window(ds, i); + } + } +} + void gui::desktop_run(DesktopState* ds) { for (;;) { // Poll mouse state @@ -1199,6 +1408,9 @@ void gui::desktop_run(DesktopState* ds) { desktop_handle_keyboard(ds, key); } + // Poll external windows (discover new, remove dead, update dirty) + desktop_poll_external_windows(ds); + // Poll windows that have a poll callback for (int i = 0; i < ds->window_count; i++) { Window* win = &ds->windows[i]; diff --git a/programs/src/desktop/stb_truetype_impl.cpp b/programs/src/desktop/stb_truetype_impl.cpp new file mode 100644 index 0000000..c598111 --- /dev/null +++ b/programs/src/desktop/stb_truetype_impl.cpp @@ -0,0 +1,35 @@ +/* + * stb_truetype_impl.cpp + * Single compilation unit for stb_truetype in ZenithOS freestanding environment + * Copyright (c) 2026 Daniel Hammer +*/ + +#include +#include +#include +#include +#include + +// Override all stb_truetype dependencies before including the implementation + +#define STBTT_ifloor(x) ((int) stb_floor(x)) +#define STBTT_iceil(x) ((int) stb_ceil(x)) +#define STBTT_sqrt(x) stb_sqrt(x) +#define STBTT_pow(x,y) stb_pow(x,y) +#define STBTT_fmod(x,y) stb_fmod(x,y) +#define STBTT_cos(x) stb_cos(x) +#define STBTT_acos(x) stb_acos(x) +#define STBTT_fabs(x) stb_fabs(x) + +#define STBTT_malloc(x,u) ((void)(u), zenith::malloc(x)) +#define STBTT_free(x,u) ((void)(u), zenith::mfree(x)) + +#define STBTT_memcpy(d,s,n) zenith::memcpy(d,s,n) +#define STBTT_memset(d,v,n) zenith::memset(d,v,n) + +#define STBTT_strlen(x) zenith::slen(x) + +#define STBTT_assert(x) ((void)(x)) + +#define STB_TRUETYPE_IMPLEMENTATION +#include diff --git a/programs/src/doom/doomgeneric_zenith.c b/programs/src/doom/doomgeneric_zenith.c index 161c5b8..f386599 100644 --- a/programs/src/doom/doomgeneric_zenith.c +++ b/programs/src/doom/doomgeneric_zenith.c @@ -1,6 +1,6 @@ /* * doomgeneric_zenith.c - * DOOM platform implementation for ZenithOS + * DOOM platform implementation for ZenithOS (standalone window server client) * Copyright (c) 2025 Daniel Hammer */ @@ -30,41 +30,70 @@ static inline long _zos_syscall1(long nr, long a1) { return ret; } +static inline long _zos_syscall2(long nr, long a1, long a2) { + long ret; + __asm__ volatile( + "mov %[a1], %%rdi\n\t" + "mov %[a2], %%rsi\n\t" + "syscall" + : "=a"(ret) + : "a"(nr), [a1] "r"(a1), [a2] "r"(a2) + : "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory"); + return ret; +} + +static inline long _zos_syscall4(long nr, long a1, long a2, long a3, long a4) { + long ret; + __asm__ volatile( + "mov %[a1], %%rdi\n\t" + "mov %[a2], %%rsi\n\t" + "mov %[a3], %%rdx\n\t" + "mov %[a4], %%r10\n\t" + "syscall" + : "=a"(ret) + : "a"(nr), [a1] "r"(a1), [a2] "r"(a2), [a3] "r"(a3), [a4] "r"(a4) + : "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory"); + return ret; +} + /* Syscall numbers (must match kernel/src/Api/Syscall.hpp) */ #define SYS_EXIT 0 #define SYS_SLEEP_MS 2 #define SYS_PRINT 4 #define SYS_GETMILLISECONDS 14 -#define SYS_ISKEYAVAILABLE 16 -#define SYS_GETKEY 17 -#define SYS_FBINFO 21 -#define SYS_FBMAP 22 +#define SYS_WINCREATE 54 +#define SYS_WINDESTROY 55 +#define SYS_WINPRESENT 56 +#define SYS_WINPOLL 57 -/* FbInfo struct (must match kernel definition) */ -struct FbInfo { - unsigned long width; - unsigned long height; - unsigned long pitch; - unsigned long bpp; - unsigned long userAddr; +/* Window server structs (must match Zenith::WinCreateResult and Zenith::WinEvent) */ +struct WinCreateResult { + int id; /* -1 on failure */ + unsigned _pad; + unsigned long pixelVa; /* VA of pixel buffer in caller's address space */ }; -/* KeyEvent struct (must match kernel definition) */ -struct KeyEvent { - unsigned char scancode; - char ascii; - unsigned char pressed; - unsigned char shift; - unsigned char ctrl; - unsigned char alt; +struct WinEvent { + unsigned char type; /* 0=key, 1=mouse, 2=resize, 3=close */ + unsigned char _pad[3]; + union { + struct { + unsigned char scancode; + char ascii; + unsigned char pressed; + unsigned char shift; + unsigned char ctrl; + unsigned char alt; + } key; + struct { int x, y, scroll; unsigned char buttons, prev_buttons; } mouse; + struct { int w, h; } resize; + }; }; -/* ---- Framebuffer state ---- */ +/* ---- Window state ---- */ -static uint32_t* g_fbPtr = 0; -static uint32_t g_fbWidth = 0; -static uint32_t g_fbHeight = 0; -static uint32_t g_fbPitch = 0; /* bytes per scanline */ +static int g_winId = -1; +static uint32_t* g_pixBuf = 0; /* ---- Circular key queue ---- */ @@ -115,8 +144,8 @@ static unsigned char scancode_to_doomkey(unsigned char scancode, char ascii) { case 0x4D: return KEY_RIGHTARROW; case 0x1C: return KEY_ENTER; case 0x01: return KEY_ESCAPE; - case 0x39: return ' '; /* Space = use */ - case 0x1D: return KEY_RCTRL; /* LCtrl = fire */ + case 0x39: return KEY_USE; /* Space = use */ + case 0x1D: return KEY_FIRE; /* LCtrl = fire */ case 0x2A: return KEY_RSHIFT; /* LShift = run */ case 0x36: return KEY_RSHIFT; /* RShift = run */ case 0x38: return KEY_RALT; /* Alt = strafe */ @@ -146,22 +175,26 @@ static unsigned char scancode_to_doomkey(unsigned char scancode, char ascii) { } } -/* ---- Poll keyboard and enqueue events ---- */ +/* ---- Poll window events and enqueue key events ---- */ static void poll_keyboard(void) { - while (_zos_syscall0(SYS_ISKEYAVAILABLE)) { - struct KeyEvent evt; - _zos_syscall1(SYS_GETKEY, (long)&evt); + struct WinEvent evt; + while (_zos_syscall2(SYS_WINPOLL, (long)g_winId, (long)&evt) > 0) { + if (evt.type == 0) { + /* Key event */ + unsigned char baseSc = evt.key.scancode & 0x7F; - unsigned char baseSc = evt.scancode & 0x7F; /* strip break bit */ + char ascii = 0; + if (baseSc < 128) + ascii = scancode_to_ascii[baseSc]; - char ascii = 0; - if (baseSc < 128) - ascii = scancode_to_ascii[baseSc]; - - unsigned char dk = scancode_to_doomkey(baseSc, ascii); - if (dk != 0) { - key_queue_push(evt.pressed ? 1 : 0, dk); + unsigned char dk = scancode_to_doomkey(baseSc, ascii); + if (dk != 0) { + key_queue_push(evt.key.pressed ? 1 : 0, dk); + } + } else if (evt.type == 3) { + /* Close event — exit the process */ + _zos_syscall1(SYS_EXIT, 0); } } } @@ -169,38 +202,30 @@ static void poll_keyboard(void) { /* ---- DG platform functions ---- */ void DG_Init(void) { - struct FbInfo info; - _zos_syscall1(SYS_FBINFO, (long)&info); + struct WinCreateResult result; + _zos_syscall4(SYS_WINCREATE, (long)"DOOM", (long)DOOMGENERIC_RESX, + (long)DOOMGENERIC_RESY, (long)&result); - g_fbWidth = (uint32_t)info.width; - g_fbHeight = (uint32_t)info.height; - g_fbPitch = (uint32_t)info.pitch; + if (result.id < 0) { + _zos_syscall1(SYS_EXIT, 1); + } - g_fbPtr = (uint32_t*)(unsigned long)_zos_syscall0(SYS_FBMAP); - - printf("DOOM: framebuffer %ux%u pitch=%u mapped at %p\n", - g_fbWidth, g_fbHeight, g_fbPitch, (void*)g_fbPtr); + g_winId = result.id; + g_pixBuf = (uint32_t*)result.pixelVa; } void DG_DrawFrame(void) { /* Poll keyboard first */ poll_keyboard(); - /* Copy DG_ScreenBuffer (DOOMGENERIC_RESX x DOOMGENERIC_RESY) to framebuffer */ - if (g_fbPtr == 0 || DG_ScreenBuffer == 0) return; + /* Copy DG_ScreenBuffer into the shared pixel buffer */ + if (g_pixBuf == 0 || DG_ScreenBuffer == 0) return; - uint32_t copyW = DOOMGENERIC_RESX; - uint32_t copyH = DOOMGENERIC_RESY; - if (copyW > g_fbWidth) copyW = g_fbWidth; - if (copyH > g_fbHeight) copyH = g_fbHeight; + memcpy(g_pixBuf, DG_ScreenBuffer, + DOOMGENERIC_RESX * DOOMGENERIC_RESY * sizeof(uint32_t)); - uint32_t fbStride = g_fbPitch / 4; /* pixels per scanline */ - - for (uint32_t y = 0; y < copyH; y++) { - uint32_t* dst = g_fbPtr + y * fbStride; - uint32_t* src = DG_ScreenBuffer + y * DOOMGENERIC_RESX; - memcpy(dst, src, copyW * sizeof(uint32_t)); - } + /* Mark window as dirty so the compositor picks it up */ + _zos_syscall1(SYS_WINPRESENT, (long)g_winId); } void DG_SleepMs(uint32_t ms) { diff --git a/programs/src/doom/libc.c b/programs/src/doom/libc.c index 274e04a..db603d5 100644 --- a/programs/src/doom/libc.c +++ b/programs/src/doom/libc.c @@ -712,27 +712,22 @@ int sprintf(char *buf, const char *fmt, ...) { static char _printbuf[4096]; int vprintf(const char *fmt, va_list ap) { - int ret = vsnprintf(_printbuf, sizeof(_printbuf), fmt, ap); - _zos_syscall1(SYS_PRINT, (long)_printbuf); - return ret; + (void)fmt; (void)ap; + return 0; } int printf(const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - int ret = vprintf(fmt, ap); - va_end(ap); - return ret; + (void)fmt; + return 0; } int puts(const char *s) { - _zos_syscall1(SYS_PRINT, (long)s); - _zos_syscall1(SYS_PUTCHAR, (long)'\n'); + (void)s; return 0; } int putchar(int c) { - _zos_syscall1(SYS_PUTCHAR, (long)c); + (void)c; return c; } @@ -862,20 +857,8 @@ size_t fread(void *ptr, size_t size, size_t nmemb, FILE *fp) { size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *fp) { if (fp == NULL) return 0; - /* stdout/stderr: write to terminal */ + /* stdout/stderr: discard (no terminal in windowed mode) */ if (fp->is_std == 1 || fp->is_std == 2) { - /* Write as string to terminal */ - size_t total = size * nmemb; - const char *s = (const char *)ptr; - char buf[512]; - while (total > 0) { - size_t chunk = total > 511 ? 511 : total; - memcpy(buf, s, chunk); - buf[chunk] = '\0'; - _zos_syscall1(SYS_PRINT, (long)buf); - s += chunk; - total -= chunk; - } return nmemb; } @@ -970,31 +953,18 @@ char *fgets(char *s, int size, FILE *fp) { } int fputs(const char *s, FILE *fp) { - size_t len = strlen(s); - return fwrite(s, 1, len, fp) > 0 ? 0 : -1; + (void)s; (void)fp; + return 0; } int fprintf(FILE *fp, const char *fmt, ...) { - char buf[4096]; - va_list ap; - va_start(ap, fmt); - int ret = vsnprintf(buf, sizeof(buf), fmt, ap); - va_end(ap); - - if (fp == stdout || fp == stderr || (fp && fp->is_std)) { - _zos_syscall1(SYS_PRINT, (long)buf); - } - return ret; + (void)fp; (void)fmt; + return 0; } int vfprintf(FILE *fp, const char *fmt, va_list ap) { - char buf[4096]; - int ret = vsnprintf(buf, sizeof(buf), fmt, ap); - - if (fp == stdout || fp == stderr || (fp && fp->is_std)) { - _zos_syscall1(SYS_PRINT, (long)buf); - } - return ret; + (void)fp; (void)fmt; (void)ap; + return 0; } int sscanf(const char *str, const char *fmt, ...) { @@ -1069,11 +1039,7 @@ int sscanf(const char *str, const char *fmt, ...) { } void perror(const char *s) { - if (s && *s) { - _zos_syscall1(SYS_PRINT, (long)s); - _zos_syscall1(SYS_PRINT, (long)": "); - } - _zos_syscall1(SYS_PRINT, (long)"error\n"); + (void)s; } int rename(const char *old, const char *new_) { diff --git a/ramdisk.tar b/ramdisk.tar index 115da62..0d220df 100644 Binary files a/ramdisk.tar and b/ramdisk.tar differ diff --git a/scripts/copy_fonts.sh b/scripts/copy_fonts.sh new file mode 100755 index 0000000..1531bf7 --- /dev/null +++ b/scripts/copy_fonts.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# copy_fonts.sh - Copy TTF fonts for the desktop environment +# Usage: ./scripts/copy_fonts.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)" + +FONT_DST="$PROJECT_ROOT/programs/bin/fonts" + +mkdir -p "$FONT_DST" + +FONTS=( + "programs/gui/fonts/Roboto/static/Roboto-Regular.ttf" + "programs/gui/fonts/Roboto/static/Roboto-Bold.ttf" + "programs/gui/fonts/Roboto/static/Roboto-Medium.ttf" + "programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-Regular.ttf" + "programs/gui/fonts/JetBrains_Mono/static/JetBrainsMono-Bold.ttf" +) + +copied=0 +for font in "${FONTS[@]}"; do + src="$PROJECT_ROOT/$font" + name=$(basename "$font") + dst="$FONT_DST/$name" + if [ -f "$src" ]; then + cp "$src" "$dst" + copied=$((copied + 1)) + else + echo "copy_fonts: warning: $src not found" >&2 + fi +done + +echo "copy_fonts: copied $copied fonts to $FONT_DST" diff --git a/scripts/copy_icons.sh b/scripts/copy_icons.sh index 5dd5c0b..9f6d474 100755 --- a/scripts/copy_icons.sh +++ b/scripts/copy_icons.sh @@ -52,6 +52,11 @@ ICONS=( "mimetypes/scalable/application-x-executable.svg" "categories/scalable/help-about.svg" "categories/scalable/system-reboot.svg" + "apps/scalable/doom.svg" + "apps/scalable/system-monitor.svg" + "apps/scalable/applications-science.svg" + "apps/scalable/hardware.svg" + "apps/scalable/unsettings.svg" # settings icon; toolbox in flat remix ) copied=0