/* * svg.hpp * ZenithOS SVG icon parser and scanline rasterizer * Handles the Flat-Remix symbolic icon subset (path, circle, rect) * All math uses 16.16 fixed-point -- NO floating point. * Copyright (c) 2025 Daniel Hammer */ #pragma once #include "gui/gui.hpp" #include namespace gui { // --------------------------------------------------------------------------- // SVG icon result // --------------------------------------------------------------------------- struct SvgIcon { uint32_t* pixels; // ARGB pixel data (heap-allocated) int width; int height; }; // --------------------------------------------------------------------------- // Edge used by the scanline rasterizer // --------------------------------------------------------------------------- struct SvgEdge { fixed_t x0, y0, x1, y1; }; // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- static constexpr int SVG_MAX_EDGES = 4096; static constexpr int SVG_MAX_PATH_LEN = 4096; static constexpr int SVG_MAX_FILE_SIZE = 32768; static constexpr int SVG_BEZIER_STEPS = 8; // --------------------------------------------------------------------------- // Fixed-point number parser (NO floating point) // Parses strings like "3.25", "-0.5", ".1115", "16" // Returns the number of characters consumed. // --------------------------------------------------------------------------- inline int svg_parse_fixed(const char* s, fixed_t* out) { const char* p = s; bool neg = false; if (*p == '-') { neg = true; ++p; } else if (*p == '+') { ++p; } // Integer part int32_t integer = 0; while (*p >= '0' && *p <= '9') { integer = integer * 10 + (*p - '0'); ++p; } // Fractional part int32_t frac = 0; int32_t frac_div = 1; if (*p == '.') { ++p; while (*p >= '0' && *p <= '9') { if (frac_div < 100000) { // prevent overflow frac = frac * 10 + (*p - '0'); frac_div *= 10; } ++p; } } fixed_t val = int_to_fixed(integer); if (frac_div > 1) { val += (int32_t)(((int64_t)frac << 16) / frac_div); } if (neg) val = -val; *out = val; return (int)(p - s); } // --------------------------------------------------------------------------- // String helpers (no stdlib) // --------------------------------------------------------------------------- inline bool svg_char_is_ws(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; } inline bool svg_char_is_sep(char c) { return svg_char_is_ws(c) || c == ','; } inline bool svg_char_is_num_start(char c) { return (c >= '0' && c <= '9') || c == '-' || c == '+' || c == '.'; } inline bool svg_char_is_cmd(char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); } inline int svg_strlen(const char* s) { int n = 0; while (s[n]) ++n; return n; } inline bool svg_strncmp(const char* a, const char* b, int n) { for (int i = 0; i < n; ++i) if (a[i] != b[i]) return false; return true; } inline void svg_memset(void* dst, uint8_t val, int n) { auto* d = (uint8_t*)dst; for (int i = 0; i < n; ++i) d[i] = val; } inline void svg_memcpy(void* dst, const void* src, int n) { auto* d = (uint8_t*)dst; auto* s = (const uint8_t*)src; for (int i = 0; i < n; ++i) d[i] = s[i]; } // --------------------------------------------------------------------------- // Mini XML attribute extraction // --------------------------------------------------------------------------- // Find the next occurrence of needle in haystack (haystack has length hLen). // Returns pointer to start of match, or nullptr. inline const char* svg_strstr(const char* haystack, int hLen, const char* needle) { int nLen = svg_strlen(needle); if (nLen == 0 || nLen > hLen) return nullptr; for (int i = 0; i <= hLen - nLen; ++i) { if (svg_strncmp(haystack + i, needle, nLen)) return haystack + i; } return nullptr; } // Check if a character can be part of an XML attribute name inline bool svg_char_is_attrname(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == ':'; } // Extract the value of an XML attribute: attr="value" // The attr string should include a leading space (e.g., " cx") to distinguish // from substrings of other attribute names. // Writes into buf (up to maxLen-1 chars), returns length or -1 if not found. inline int svg_get_attr(const char* tag, int tagLen, const char* attr, char* buf, int maxLen) { int attrLen = svg_strlen(attr); const char* search_start = tag; int search_len = tagLen; while (search_len > 0) { const char* p = svg_strstr(search_start, search_len, attr); if (!p) return -1; // Ensure this is the exact attribute name, not a prefix of another // (e.g., " r" should not match " rx" or " ry") const char* after_name = p + attrLen; if (after_name < tag + tagLen && svg_char_is_attrname(*after_name)) { // This is a prefix of a longer attribute name, skip and search again search_start = after_name; search_len = tagLen - (int)(search_start - tag); continue; } p = after_name; // skip whitespace around '=' while (p < tag + tagLen && svg_char_is_ws(*p)) ++p; if (p >= tag + tagLen || *p != '=') return -1; ++p; while (p < tag + tagLen && svg_char_is_ws(*p)) ++p; if (p >= tag + tagLen) return -1; char quote = *p; if (quote != '"' && quote != '\'') return -1; ++p; int len = 0; while (p < tag + tagLen && *p != quote && len < maxLen - 1) { buf[len++] = *p++; } buf[len] = '\0'; return len; } return -1; } // Parse an integer from a string (no sign, simple decimal). inline int svg_parse_int(const char* s) { int v = 0; while (*s >= '0' && *s <= '9') { v = v * 10 + (*s - '0'); ++s; } return v; } // Parse a hex color like "#5c616c" into a Color. inline Color svg_parse_hex_color(const char* s) { if (*s == '#') ++s; auto hexval = [](char c) -> uint8_t { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return 10 + (c - 'a'); if (c >= 'A' && c <= 'F') return 10 + (c - 'A'); return 0; }; uint8_t r = (hexval(s[0]) << 4) | hexval(s[1]); uint8_t g = (hexval(s[2]) << 4) | hexval(s[3]); uint8_t b = (hexval(s[4]) << 4) | hexval(s[5]); return Color::from_rgb(r, g, b); } // --------------------------------------------------------------------------- // Edge list builder // --------------------------------------------------------------------------- struct SvgEdgeList { SvgEdge* edges; int count; int capacity; void init(int cap) { edges = (SvgEdge*)zenith::alloc(cap * sizeof(SvgEdge)); count = 0; capacity = cap; } void add(fixed_t x0, fixed_t y0, fixed_t x1, fixed_t y1) { if (count >= capacity) return; // skip horizontal edges (they don't contribute to scanline crossings) if (y0 == y1) return; edges[count++] = {x0, y0, x1, y1}; } }; // --------------------------------------------------------------------------- // Bezier flattening (fixed-point) // --------------------------------------------------------------------------- // Cubic bezier: add line segments approximating B(t) for t in [0,1] inline void svg_flatten_cubic(SvgEdgeList& el, fixed_t x0, fixed_t y0, fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2, fixed_t x3, fixed_t y3) { constexpr int N = SVG_BEZIER_STEPS; fixed_t px = x0, py = y0; for (int i = 1; i <= N; ++i) { // t = i/N in 16.16: (i << 16) / N fixed_t t = (int32_t)(((int64_t)i << 16) / N); fixed_t omt = int_to_fixed(1) - t; // 1 - t // (1-t)^2 and t^2 fixed_t omt2 = fixed_mul(omt, omt); fixed_t t2 = fixed_mul(t, t); // (1-t)^3 and t^3 fixed_t omt3 = fixed_mul(omt2, omt); fixed_t t3 = fixed_mul(t2, t); // 3*(1-t)^2*t and 3*(1-t)*t^2 fixed_t c1 = fixed_mul(omt2, t) * 3; fixed_t c2 = fixed_mul(omt, t2) * 3; fixed_t nx = fixed_mul(omt3, x0) + fixed_mul(c1, x1) + fixed_mul(c2, x2) + fixed_mul(t3, x3); fixed_t ny = fixed_mul(omt3, y0) + fixed_mul(c1, y1) + fixed_mul(c2, y2) + fixed_mul(t3, y3); el.add(px, py, nx, ny); px = nx; py = ny; } } // Quadratic bezier: add line segments approximating B(t) for t in [0,1] inline void svg_flatten_quad(SvgEdgeList& el, fixed_t x0, fixed_t y0, fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2) { constexpr int N = SVG_BEZIER_STEPS; fixed_t px = x0, py = y0; for (int i = 1; i <= N; ++i) { fixed_t t = (int32_t)(((int64_t)i << 16) / N); fixed_t omt = int_to_fixed(1) - t; // (1-t)^2*P0 + 2*(1-t)*t*P1 + t^2*P2 fixed_t omt2 = fixed_mul(omt, omt); fixed_t t2 = fixed_mul(t, t); fixed_t c1 = fixed_mul(omt, t) * 2; fixed_t nx = fixed_mul(omt2, x0) + fixed_mul(c1, x1) + fixed_mul(t2, x2); fixed_t ny = fixed_mul(omt2, y0) + fixed_mul(c1, y1) + fixed_mul(t2, y2); el.add(px, py, nx, ny); px = nx; py = ny; } } // --------------------------------------------------------------------------- // Circle to edges: approximate a circle as N line segments // --------------------------------------------------------------------------- inline void svg_circle_edges(SvgEdgeList& el, fixed_t cx, fixed_t cy, fixed_t r) { // Approximate circle with 16 segments using a precomputed sin/cos table // for angles 0, 22.5, 45, ... 337.5 degrees. // sin/cos in 16.16 fixed-point for 16 evenly-spaced angles: static const fixed_t cos16[16] = { 65536, 60547, 46341, 25080, 0, -25080, -46341, -60547, -65536, -60547, -46341, -25080, 0, 25080, 46341, 60547 }; static const fixed_t sin16[16] = { 0, 25080, 46341, 60547, 65536, 60547, 46341, 25080, 0, -25080, -46341, -60547, -65536, -60547, -46341, -25080 }; fixed_t px = cx + fixed_mul(r, cos16[0]); fixed_t py = cy + fixed_mul(r, sin16[0]); for (int i = 1; i <= 16; ++i) { int idx = i & 15; fixed_t nx = cx + fixed_mul(r, cos16[idx]); fixed_t ny = cy + fixed_mul(r, sin16[idx]); el.add(px, py, nx, ny); px = nx; py = ny; } } // --------------------------------------------------------------------------- // Rounded rect to edges // --------------------------------------------------------------------------- inline void svg_rect_edges(SvgEdgeList& el, fixed_t x, fixed_t y, fixed_t w, fixed_t h, fixed_t rx, fixed_t ry) { if (rx <= 0 && ry <= 0) { // Simple rectangle: 4 edges fixed_t x2 = x + w; fixed_t y2 = y + h; el.add(x, y, x2, y); // top el.add(x2, y, x2, y2); // right el.add(x2, y2, x, y2); // bottom el.add(x, y2, x, y); // left return; } // Clamp radii fixed_t half_w = w >> 1; fixed_t half_h = h >> 1; if (rx > half_w) rx = half_w; if (ry > half_h) ry = half_h; // Quarter-circle corner with 4 segments per corner. // cos/sin for 0, 22.5, 45, 67.5, 90 degrees (5 points, 4 segments): static const fixed_t qcos[5] = { 65536, 60547, 46341, 25080, 0 }; static const fixed_t qsin[5] = { 0, 25080, 46341, 60547, 65536 }; // Corners: top-right, bottom-right, bottom-left, top-left // Each corner has a center and a quadrant direction for cos/sin application struct Corner { fixed_t cx, cy; int sx, sy; }; Corner corners[4] = { { x + w - rx, y + ry, 1, -1 }, // top-right { x + w - rx, y + h - ry, 1, 1 }, // bottom-right { x + rx, y + h - ry, -1, 1 }, // bottom-left { x + rx, y + ry, -1, -1 }, // top-left }; for (int c = 0; c < 4; ++c) { Corner& cn = corners[c]; fixed_t px = cn.cx + fixed_mul(rx, qcos[0]) * cn.sx; fixed_t py = cn.cy + fixed_mul(ry, qsin[0]) * cn.sy; for (int i = 1; i <= 4; ++i) { fixed_t nx = cn.cx + fixed_mul(rx, qcos[i]) * cn.sx; fixed_t ny = cn.cy + fixed_mul(ry, qsin[i]) * cn.sy; el.add(px, py, nx, ny); px = nx; py = ny; } } // Straight edges between corners // Top edge: top-left corner end -> top-right corner start el.add(x + rx, y, x + w - rx, y); // Right edge: top-right corner end -> bottom-right corner start el.add(x + w, y + ry, x + w, y + h - ry); // Bottom edge: bottom-right corner end -> bottom-left corner start el.add(x + w - rx, y + h, x + rx, y + h); // Left edge: bottom-left corner end -> top-left corner start el.add(x, y + h - ry, x, y + ry); } // --------------------------------------------------------------------------- // Path command parser: tokenize the 'd' attribute // --------------------------------------------------------------------------- struct SvgPathParser { const char* data; int len; int pos; void init(const char* d, int l) { data = d; len = l; pos = 0; } void skip_separators() { while (pos < len && svg_char_is_sep(data[pos])) ++pos; } bool has_more() const { return pos < len; } // Peek at what's next: command letter, number, or end bool next_is_number() { skip_separators(); if (pos >= len) return false; return svg_char_is_num_start(data[pos]); } char read_command() { skip_separators(); if (pos >= len) return '\0'; if (svg_char_is_cmd(data[pos]) && data[pos] != 'e' && data[pos] != 'E') { return data[pos++]; } return '\0'; } fixed_t read_number() { skip_separators(); if (pos >= len) return 0; fixed_t val = 0; int consumed = svg_parse_fixed(data + pos, &val); pos += consumed; return val; } }; // --------------------------------------------------------------------------- // Process an SVG path 'd' attribute into edges // --------------------------------------------------------------------------- inline void svg_path_to_edges(SvgEdgeList& el, const char* d, int dLen, fixed_t scale_x, fixed_t scale_y, fixed_t off_x, fixed_t off_y) { SvgPathParser pp; pp.init(d, dLen); fixed_t cur_x = 0, cur_y = 0; // current point fixed_t start_x = 0, start_y = 0; // subpath start fixed_t last_cx = 0, last_cy = 0; // last control point (for S/T) char last_cmd = '\0'; auto scale_pt = [&](fixed_t x, fixed_t y, fixed_t* ox, fixed_t* oy) { *ox = fixed_mul(x - off_x, scale_x); *oy = fixed_mul(y - off_y, scale_y); }; while (pp.has_more()) { char cmd = '\0'; // Try reading a command letter pp.skip_separators(); if (pp.pos < pp.len && svg_char_is_cmd(pp.data[pp.pos]) && pp.data[pp.pos] != 'e' && pp.data[pp.pos] != 'E') { cmd = pp.data[pp.pos++]; } else if (pp.next_is_number()) { // Implicit repeat of last command // After M, implicit repeat is L; after m, implicit repeat is l if (last_cmd == 'M') cmd = 'L'; else if (last_cmd == 'm') cmd = 'l'; else cmd = last_cmd; } else { // Skip unknown character if (pp.pos < pp.len) pp.pos++; continue; } if (cmd == '\0') break; switch (cmd) { case 'M': { fixed_t x = pp.read_number(); fixed_t y = pp.read_number(); cur_x = x; cur_y = y; start_x = x; start_y = y; last_cmd = 'M'; break; } case 'm': { fixed_t dx = pp.read_number(); fixed_t dy = pp.read_number(); cur_x += dx; cur_y += dy; start_x = cur_x; start_y = cur_y; last_cmd = 'm'; break; } case 'L': { fixed_t x = pp.read_number(); fixed_t y = pp.read_number(); fixed_t sx0, sy0, sx1, sy1; scale_pt(cur_x, cur_y, &sx0, &sy0); scale_pt(x, y, &sx1, &sy1); el.add(sx0, sy0, sx1, sy1); cur_x = x; cur_y = y; last_cmd = 'L'; break; } case 'l': { fixed_t dx = pp.read_number(); fixed_t dy = pp.read_number(); fixed_t nx = cur_x + dx, ny = cur_y + dy; fixed_t sx0, sy0, sx1, sy1; scale_pt(cur_x, cur_y, &sx0, &sy0); scale_pt(nx, ny, &sx1, &sy1); el.add(sx0, sy0, sx1, sy1); cur_x = nx; cur_y = ny; last_cmd = 'l'; break; } case 'H': { fixed_t x = pp.read_number(); fixed_t sx0, sy0, sx1, sy1; scale_pt(cur_x, cur_y, &sx0, &sy0); scale_pt(x, cur_y, &sx1, &sy1); el.add(sx0, sy0, sx1, sy1); cur_x = x; last_cmd = 'H'; break; } case 'h': { fixed_t dx = pp.read_number(); fixed_t nx = cur_x + dx; fixed_t sx0, sy0, sx1, sy1; scale_pt(cur_x, cur_y, &sx0, &sy0); scale_pt(nx, cur_y, &sx1, &sy1); el.add(sx0, sy0, sx1, sy1); cur_x = nx; last_cmd = 'h'; break; } case 'V': { fixed_t y = pp.read_number(); fixed_t sx0, sy0, sx1, sy1; scale_pt(cur_x, cur_y, &sx0, &sy0); scale_pt(cur_x, y, &sx1, &sy1); el.add(sx0, sy0, sx1, sy1); cur_y = y; last_cmd = 'V'; break; } case 'v': { fixed_t dy = pp.read_number(); fixed_t ny = cur_y + dy; fixed_t sx0, sy0, sx1, sy1; scale_pt(cur_x, cur_y, &sx0, &sy0); scale_pt(cur_x, ny, &sx1, &sy1); el.add(sx0, sy0, sx1, sy1); cur_y = ny; last_cmd = 'v'; break; } case 'C': { fixed_t x1 = pp.read_number(), y1 = pp.read_number(); fixed_t x2 = pp.read_number(), y2 = pp.read_number(); fixed_t x3 = pp.read_number(), y3 = pp.read_number(); fixed_t sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3; scale_pt(cur_x, cur_y, &sx0, &sy0); scale_pt(x1, y1, &sx1, &sy1); scale_pt(x2, y2, &sx2, &sy2); scale_pt(x3, y3, &sx3, &sy3); svg_flatten_cubic(el, sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3); last_cx = x2; last_cy = y2; cur_x = x3; cur_y = y3; last_cmd = 'C'; break; } case 'c': { fixed_t dx1 = pp.read_number(), dy1 = pp.read_number(); fixed_t dx2 = pp.read_number(), dy2 = pp.read_number(); fixed_t dx3 = pp.read_number(), dy3 = pp.read_number(); fixed_t x1 = cur_x + dx1, y1 = cur_y + dy1; fixed_t x2 = cur_x + dx2, y2 = cur_y + dy2; fixed_t x3 = cur_x + dx3, y3 = cur_y + dy3; fixed_t sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3; scale_pt(cur_x, cur_y, &sx0, &sy0); scale_pt(x1, y1, &sx1, &sy1); scale_pt(x2, y2, &sx2, &sy2); scale_pt(x3, y3, &sx3, &sy3); svg_flatten_cubic(el, sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3); last_cx = x2; last_cy = y2; cur_x = x3; cur_y = y3; last_cmd = 'c'; break; } case 'S': { // Smooth cubic: reflect last control point fixed_t rcx = cur_x * 2 - last_cx; fixed_t rcy = cur_y * 2 - last_cy; if (last_cmd != 'C' && last_cmd != 'c' && last_cmd != 'S' && last_cmd != 's') { rcx = cur_x; rcy = cur_y; } fixed_t x2 = pp.read_number(), y2 = pp.read_number(); fixed_t x3 = pp.read_number(), y3 = pp.read_number(); fixed_t sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3; scale_pt(cur_x, cur_y, &sx0, &sy0); scale_pt(rcx, rcy, &sx1, &sy1); scale_pt(x2, y2, &sx2, &sy2); scale_pt(x3, y3, &sx3, &sy3); svg_flatten_cubic(el, sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3); last_cx = x2; last_cy = y2; cur_x = x3; cur_y = y3; last_cmd = 'S'; break; } case 's': { fixed_t rcx = cur_x * 2 - last_cx; fixed_t rcy = cur_y * 2 - last_cy; if (last_cmd != 'C' && last_cmd != 'c' && last_cmd != 'S' && last_cmd != 's') { rcx = cur_x; rcy = cur_y; } fixed_t dx2 = pp.read_number(), dy2 = pp.read_number(); fixed_t dx3 = pp.read_number(), dy3 = pp.read_number(); fixed_t x2 = cur_x + dx2, y2 = cur_y + dy2; fixed_t x3 = cur_x + dx3, y3 = cur_y + dy3; fixed_t sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3; scale_pt(cur_x, cur_y, &sx0, &sy0); scale_pt(rcx, rcy, &sx1, &sy1); scale_pt(x2, y2, &sx2, &sy2); scale_pt(x3, y3, &sx3, &sy3); svg_flatten_cubic(el, sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3); last_cx = x2; last_cy = y2; cur_x = x3; cur_y = y3; last_cmd = 's'; break; } case 'Q': { fixed_t x1 = pp.read_number(), y1 = pp.read_number(); fixed_t x2 = pp.read_number(), y2 = pp.read_number(); fixed_t sx0, sy0, sx1, sy1, sx2, sy2; scale_pt(cur_x, cur_y, &sx0, &sy0); scale_pt(x1, y1, &sx1, &sy1); scale_pt(x2, y2, &sx2, &sy2); svg_flatten_quad(el, sx0, sy0, sx1, sy1, sx2, sy2); last_cx = x1; last_cy = y1; cur_x = x2; cur_y = y2; last_cmd = 'Q'; break; } case 'q': { fixed_t dx1 = pp.read_number(), dy1 = pp.read_number(); fixed_t dx2 = pp.read_number(), dy2 = pp.read_number(); fixed_t x1 = cur_x + dx1, y1 = cur_y + dy1; fixed_t x2 = cur_x + dx2, y2 = cur_y + dy2; fixed_t sx0, sy0, sx1, sy1, sx2, sy2; scale_pt(cur_x, cur_y, &sx0, &sy0); scale_pt(x1, y1, &sx1, &sy1); scale_pt(x2, y2, &sx2, &sy2); svg_flatten_quad(el, sx0, sy0, sx1, sy1, sx2, sy2); last_cx = x1; last_cy = y1; cur_x = x2; cur_y = y2; last_cmd = 'q'; break; } case 'A': case 'a': { // Arc command: consume parameters but approximate as a line // (arcs are rare in these icons) fixed_t rx = pp.read_number(); fixed_t ry = pp.read_number(); pp.read_number(); // x-rotation pp.read_number(); // large-arc-flag pp.read_number(); // sweep-flag fixed_t x = pp.read_number(); fixed_t y = pp.read_number(); if (cmd == 'a') { x += cur_x; y += cur_y; } fixed_t sx0, sy0, sx1, sy1; scale_pt(cur_x, cur_y, &sx0, &sy0); scale_pt(x, y, &sx1, &sy1); el.add(sx0, sy0, sx1, sy1); cur_x = x; cur_y = y; last_cmd = cmd; (void)rx; (void)ry; break; } case 'Z': case 'z': { if (cur_x != start_x || cur_y != start_y) { fixed_t sx0, sy0, sx1, sy1; scale_pt(cur_x, cur_y, &sx0, &sy0); scale_pt(start_x, start_y, &sx1, &sy1); el.add(sx0, sy0, sx1, sy1); } cur_x = start_x; cur_y = start_y; last_cmd = 'Z'; break; } default: // Unknown command, skip break; } } } // --------------------------------------------------------------------------- // Scanline rasterizer (even-odd fill rule) // --------------------------------------------------------------------------- inline void svg_rasterize(const SvgEdgeList& el, uint32_t* pixels, int w, int h, uint32_t fill) { // Temporary array for x-intersections on each scanline // Allocate enough for all edges (each edge can intersect at most once per scanline) int maxIsect = el.count + 16; fixed_t* isect = (fixed_t*)zenith::alloc(maxIsect * sizeof(fixed_t)); for (int y = 0; y < h; ++y) { // Scanline center in fixed-point fixed_t scanY = int_to_fixed(y) + (1 << 15); // y + 0.5 int isectCount = 0; // Find intersections with all edges for (int i = 0; i < el.count; ++i) { const SvgEdge& e = el.edges[i]; fixed_t ey0 = e.y0, ey1 = e.y1; // Ensure ey0 <= ey1 for the range check fixed_t emin = ey0 < ey1 ? ey0 : ey1; fixed_t emax = ey0 > ey1 ? ey0 : ey1; // Does this edge cross scanY? if (scanY < emin || scanY >= emax) continue; // Compute x at intersection: x = x0 + (scanY - y0) * (x1 - x0) / (y1 - y0) fixed_t dy = ey1 - ey0; if (dy == 0) continue; // horizontal, skip fixed_t dx = e.x1 - e.x0; fixed_t t_num = scanY - ey0; // x_intersect = x0 + dx * t_num / dy fixed_t x_int = e.x0 + (int32_t)(((int64_t)dx * t_num) / dy); if (isectCount < maxIsect) isect[isectCount++] = x_int; } // Sort intersections (simple insertion sort -- usually very few) for (int i = 1; i < isectCount; ++i) { fixed_t key = isect[i]; int j = i - 1; while (j >= 0 && isect[j] > key) { isect[j + 1] = isect[j]; --j; } isect[j + 1] = key; } // Fill between pairs (even-odd rule) for (int i = 0; i + 1 < isectCount; i += 2) { int x0 = fixed_to_int(isect[i]); int x1 = fixed_to_int(isect[i + 1]); // Clamp to pixel bounds if (x0 < 0) x0 = 0; if (x1 > w) x1 = w; for (int x = x0; x < x1; ++x) { pixels[y * w + x] = fill; } } } zenith::free(isect); } // --------------------------------------------------------------------------- // SVG document parser: extract paths, circles, rects and rasterize // --------------------------------------------------------------------------- inline SvgIcon svg_render(const char* svg_data, int svg_len, int target_w, int target_h, Color fill_color) { SvgIcon icon; icon.width = target_w; icon.height = target_h; icon.pixels = (uint32_t*)zenith::alloc(target_w * target_h * sizeof(uint32_t)); // Clear to transparent svg_memset(icon.pixels, 0, target_w * target_h * sizeof(uint32_t)); uint32_t fill_px = fill_color.to_pixel(); // Parse SVG dimensions: width and height int svg_w = 16, svg_h = 16; fixed_t vb_x = 0, vb_y = 0, vb_w = 0, vb_h = 0; bool has_viewbox = false; // Find tag int tag_offset = (int)(svg_tag - svg_data); int tag_end = tag_offset; while (tag_end < svg_len && svg_data[tag_end] != '>') ++tag_end; int tag_len = tag_end - tag_offset + 1; char attr_buf[64]; // width if (svg_get_attr(svg_tag, tag_len, " width", attr_buf, sizeof(attr_buf)) > 0) { svg_w = svg_parse_int(attr_buf); } // height if (svg_get_attr(svg_tag, tag_len, " height", attr_buf, sizeof(attr_buf)) > 0) { svg_h = svg_parse_int(attr_buf); } // viewBox if (svg_get_attr(svg_tag, tag_len, " viewBox", attr_buf, sizeof(attr_buf)) > 0) { has_viewbox = true; const char* vp = attr_buf; int c; c = svg_parse_fixed(vp, &vb_x); vp += c; while (*vp && svg_char_is_sep(*vp)) ++vp; c = svg_parse_fixed(vp, &vb_y); vp += c; while (*vp && svg_char_is_sep(*vp)) ++vp; c = svg_parse_fixed(vp, &vb_w); vp += c; while (*vp && svg_char_is_sep(*vp)) ++vp; svg_parse_fixed(vp, &vb_h); } } if (!has_viewbox) { vb_x = 0; vb_y = 0; vb_w = int_to_fixed(svg_w); vb_h = int_to_fixed(svg_h); } // Compute scale: pixel = (svg_coord - vb_origin) * target_size / vb_size fixed_t scale_x = vb_w > 0 ? fixed_div(int_to_fixed(target_w), vb_w) : int_to_fixed(1); fixed_t scale_y = vb_h > 0 ? fixed_div(int_to_fixed(target_h), vb_h) : int_to_fixed(1); // Edge list SvgEdgeList el; el.init(SVG_MAX_EDGES); // Scan for = end) break; int remaining = (int)(end - p); // Check for 5 && svg_strncmp(p, "' int elem_len = (int)(elem_end - elem_start); // Extract d attribute char d_buf[SVG_MAX_PATH_LEN]; int d_len = svg_get_attr(elem_start, elem_len, " d", d_buf, SVG_MAX_PATH_LEN); if (d_len > 0) { svg_path_to_edges(el, d_buf, d_len, scale_x, scale_y, vb_x, vb_y); } p = elem_end; continue; } // Check for 7 && svg_strncmp(p, " 0) svg_parse_fixed(attr_buf, &cx); if (svg_get_attr(elem_start, elem_len, " cy", attr_buf, sizeof(attr_buf)) > 0) svg_parse_fixed(attr_buf, &cy); if (svg_get_attr(elem_start, elem_len, " r", attr_buf, sizeof(attr_buf)) > 0) svg_parse_fixed(attr_buf, &r); // Scale to target coordinates fixed_t scx = fixed_mul(cx - vb_x, scale_x); fixed_t scy = fixed_mul(cy - vb_y, scale_y); fixed_t srx = fixed_mul(r, scale_x); fixed_t sry = fixed_mul(r, scale_y); // Use average of scaled radii fixed_t sr = (srx + sry) >> 1; svg_circle_edges(el, scx, scy, sr); p = elem_end; continue; } // Check for 5 && svg_strncmp(p, " 0) svg_parse_fixed(attr_buf, &rx_val); if (svg_get_attr(elem_start, elem_len, " y", attr_buf, sizeof(attr_buf)) > 0) svg_parse_fixed(attr_buf, &ry_val); if (svg_get_attr(elem_start, elem_len, " width", attr_buf, sizeof(attr_buf)) > 0) svg_parse_fixed(attr_buf, &rw); if (svg_get_attr(elem_start, elem_len, " height", attr_buf, sizeof(attr_buf)) > 0) svg_parse_fixed(attr_buf, &rh); if (svg_get_attr(elem_start, elem_len, " rx", attr_buf, sizeof(attr_buf)) > 0) svg_parse_fixed(attr_buf, &rrx); if (svg_get_attr(elem_start, elem_len, " ry", attr_buf, sizeof(attr_buf)) > 0) svg_parse_fixed(attr_buf, &rry); // Scale all to target pixel space fixed_t sx = fixed_mul(rx_val - vb_x, scale_x); fixed_t sy = fixed_mul(ry_val - vb_y, scale_y); fixed_t sw = fixed_mul(rw, scale_x); fixed_t sh = fixed_mul(rh, scale_y); fixed_t srx = fixed_mul(rrx, scale_x); fixed_t sry = fixed_mul(rry, scale_y); svg_rect_edges(el, sx, sy, sw, sh, srx, sry); p = elem_end; continue; } ++p; } // Rasterize all accumulated edges if (el.count > 0) { svg_rasterize(el, icon.pixels, target_w, target_h, fill_px); } zenith::free(el.edges); return icon; } // --------------------------------------------------------------------------- // Load SVG from VFS and render // --------------------------------------------------------------------------- inline SvgIcon svg_load(const char* vfs_path, int target_w, int target_h, Color fill_color) { int fd = zenith::open(vfs_path); if (fd < 0) { return {nullptr, 0, 0}; } uint64_t size = zenith::getsize(fd); if (size == 0 || size > SVG_MAX_FILE_SIZE) { zenith::close(fd); return {nullptr, 0, 0}; } char* buf = (char*)zenith::alloc(size + 1); zenith::read(fd, (uint8_t*)buf, 0, size); zenith::close(fd); buf[size] = '\0'; SvgIcon icon = svg_render(buf, (int)size, target_w, target_h, fill_color); zenith::free(buf); return icon; } // --------------------------------------------------------------------------- // Free icon pixel data // --------------------------------------------------------------------------- inline void svg_free(SvgIcon& icon) { if (icon.pixels) zenith::free(icon.pixels); icon.pixels = nullptr; icon.width = 0; icon.height = 0; } } // namespace gui