fix: sync template sysroot, add script

This commit is contained in:
2026-06-12 11:56:20 +02:00
parent 22691c5c17
commit 44f86a8b19
33 changed files with 5172 additions and 147 deletions
+251 -23
View File
@@ -123,6 +123,34 @@ struct SvgCssTable {
}
};
// ---------------------------------------------------------------------------
// Integer sqrt (binary method, no floating point).
// Returns floor(sqrt(n)) for any uint64.
// ---------------------------------------------------------------------------
inline uint64_t svg_int64_sqrt(uint64_t n) {
if (n == 0) return 0;
uint64_t r = 0;
uint64_t bit = (uint64_t)1 << 62;
while (bit > n) bit >>= 2;
while (bit != 0) {
if (n >= r + bit) {
n -= r + bit;
r = (r >> 1) + bit;
} else {
r >>= 1;
}
bit >>= 2;
}
return r;
}
// 16.16 fixed-point hypotenuse: sqrt(x*x + y*y).
inline fixed_t svg_fixed_hypot(fixed_t x, fixed_t y) {
int64_t sq = (int64_t)x * x + (int64_t)y * y;
if (sq < 0) sq = 0;
return (fixed_t)svg_int64_sqrt((uint64_t)sq);
}
// ---------------------------------------------------------------------------
// Fixed-point number parser (NO floating point)
// Parses strings like "3.25", "-0.5", ".1115", "16"
@@ -566,6 +594,47 @@ inline void svg_path_to_edges(SvgEdgeList& el, const char* d, int dLen,
*oy = fixed_mul(y - off_y, scale_y);
};
// Emit a circular arc on (cx, cy, r) from p1 to p2 by recursive
// chord-midpoint subdivision. Assumes the arc is <= 180deg; callers split
// large arcs at the far point first. Stack-based to avoid recursive lambdas.
auto emit_small_arc = [&](fixed_t cx, fixed_t cy, fixed_t r,
fixed_t p1x, fixed_t p1y,
fixed_t p2x, fixed_t p2y) {
constexpr int ARC_MAX_DEPTH = 4; // up to 16 chords per sub-arc
struct Seg { fixed_t ax, ay, bx, by; int depth; };
Seg stk[16];
int sp = 0;
stk[sp++] = { p1x, p1y, p2x, p2y, ARC_MAX_DEPTH };
while (sp > 0) {
Seg s = stk[--sp];
if (s.depth == 0) {
fixed_t sx0, sy0, sx1, sy1;
scale_pt(s.ax, s.ay, &sx0, &sy0);
scale_pt(s.bx, s.by, &sx1, &sy1);
el.add(sx0, sy0, sx1, sy1);
continue;
}
fixed_t mx = (s.ax + s.bx) / 2;
fixed_t my = (s.ay + s.by) / 2;
fixed_t vx = mx - cx;
fixed_t vy = my - cy;
fixed_t vlen = svg_fixed_hypot(vx, vy);
if (vlen == 0) {
// Degenerate: chord midpoint coincides with center.
fixed_t sx0, sy0, sx1, sy1;
scale_pt(s.ax, s.ay, &sx0, &sy0);
scale_pt(s.bx, s.by, &sx1, &sy1);
el.add(sx0, sy0, sx1, sy1);
continue;
}
fixed_t k = fixed_div(r, vlen);
fixed_t amx = cx + fixed_mul(vx, k);
fixed_t amy = cy + fixed_mul(vy, k);
stk[sp++] = { amx, amy, s.bx, s.by, s.depth - 1 };
stk[sp++] = { s.ax, s.ay, amx, amy, s.depth - 1 };
}
};
while (pp.has_more()) {
char cmd = '\0';
@@ -773,23 +842,90 @@ inline void svg_path_to_edges(SvgEdgeList& el, const char* d, int dLen,
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
// Elliptical-arc command. We treat the arc as circular using
// r = min(|rx|, |ry|), which is exact for symbolic icons (all use
// equal axes) and a reasonable approximation otherwise.
fixed_t rx_in = pp.read_number();
fixed_t ry_in = pp.read_number();
pp.read_number(); // x-axis rotation (ignored)
fixed_t fa_f = pp.read_number(); // large-arc-flag
fixed_t fs_f = 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);
fixed_t r = (rx_in < 0) ? -rx_in : rx_in;
fixed_t ry_abs = (ry_in < 0) ? -ry_in : ry_in;
if (ry_abs < r) r = ry_abs;
int fa = (fa_f != 0) ? 1 : 0;
int fs = (fs_f != 0) ? 1 : 0;
fixed_t dx = x - cur_x;
fixed_t dy = y - cur_y;
if ((dx == 0 && dy == 0) || r == 0) {
// Per spec: degenerate arc becomes a line.
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;
break;
}
// Endpoint-to-center: midpoint of chord, half-chord length,
// perpendicular height from chord to center.
fixed_t mx = cur_x + dx / 2;
fixed_t my = cur_y + dy / 2;
fixed_t half_chord = svg_fixed_hypot(dx / 2, dy / 2);
if (r < half_chord) r = half_chord; // SVG spec: snap radius up
int64_t r_sq = (int64_t)r * r;
int64_t hc_sq = (int64_t)half_chord * half_chord;
int64_t h_sq = r_sq - hc_sq;
if (h_sq < 0) h_sq = 0;
fixed_t h = (fixed_t)svg_int64_sqrt((uint64_t)h_sq);
// Center = mid + sign * (h / chord_len) * (-dy, dx)
// sign: spec F.6.5 says -1 when fa == fs, else +1.
fixed_t chord_len = 2 * half_chord;
if (chord_len == 0) chord_len = 1;
fixed_t h_over_chord = fixed_div(h, chord_len);
int sign = (fa == fs) ? -1 : 1;
fixed_t off_x_perp = fixed_mul(-dy, h_over_chord);
fixed_t off_y_perp = fixed_mul( dx, h_over_chord);
if (sign < 0) { off_x_perp = -off_x_perp; off_y_perp = -off_y_perp; }
fixed_t cx_ctr = mx + off_x_perp;
fixed_t cy_ctr = my + off_y_perp;
if (fa == 1) {
// Large arc: bisect at the far point (opposite side of center
// from chord midpoint), then walk each half as a small arc.
fixed_t vx = mx - cx_ctr;
fixed_t vy = my - cy_ctr;
fixed_t vlen = svg_fixed_hypot(vx, vy);
fixed_t am_x, am_y;
if (vlen == 0) {
// 180-degree arc: bisect using chord-perpendicular instead.
fixed_t k = fixed_div(r, chord_len);
fixed_t px = fixed_mul(-dy, k);
fixed_t py = fixed_mul( dx, k);
if (fs == 0) { px = -px; py = -py; }
am_x = cx_ctr + px;
am_y = cy_ctr + py;
} else {
fixed_t k = fixed_div(-r, vlen);
am_x = cx_ctr + fixed_mul(vx, k);
am_y = cy_ctr + fixed_mul(vy, k);
}
emit_small_arc(cx_ctr, cy_ctr, r, cur_x, cur_y, am_x, am_y);
emit_small_arc(cx_ctr, cy_ctr, r, am_x, am_y, x, y);
} else {
emit_small_arc(cx_ctr, cy_ctr, r, cur_x, cur_y, x, y);
}
cur_x = x; cur_y = y;
last_cmd = cmd;
(void)rx; (void)ry;
break;
}
case 'Z': case 'z': {
@@ -1027,6 +1163,42 @@ inline bool svg_element_has_filter(const char* elem, int elemLen) {
return svg_get_attr(elem, elemLen, " filter", buf, sizeof(buf)) > 0;
}
// ---------------------------------------------------------------------------
// Parse transform="scale(x)" or transform="scale(x,y)" or translate(x,y)
// Returns true if a scale was found; sx/sy are fixed-point scale factors.
// ---------------------------------------------------------------------------
inline bool svg_get_element_scale(const char* elem, int elemLen,
fixed_t* sx, fixed_t* sy) {
char buf[64];
if (svg_get_attr(elem, elemLen, " transform", buf, sizeof(buf)) <= 0) {
*sx = int_to_fixed(1);
*sy = int_to_fixed(1);
return false;
}
// Look for "scale("
const char* sp = buf;
while (*sp) {
if (sp[0]=='s' && sp[1]=='c' && sp[2]=='a' && sp[3]=='l' && sp[4]=='e' && sp[5]=='(') {
sp += 6;
svg_parse_fixed(sp, sx);
// Skip to comma or closing paren
while (*sp && *sp != ',' && *sp != ')') ++sp;
if (*sp == ',') {
++sp;
while (*sp == ' ') ++sp;
svg_parse_fixed(sp, sy);
} else {
*sy = *sx; // uniform scale
}
return true;
}
++sp;
}
*sx = int_to_fixed(1);
*sy = int_to_fixed(1);
return false;
}
// ---------------------------------------------------------------------------
// Scanline rasterizer with alpha blending (for multi-color SVGs)
// ---------------------------------------------------------------------------
@@ -1314,11 +1486,17 @@ inline SvgIcon svg_render(const char* svg_data, int svg_len, int target_w, int t
int alpha = svg_get_element_opacity(elem_start, elem_len);
// Per-element transform (scale)
fixed_t elem_sx, elem_sy;
svg_get_element_scale(elem_start, elem_len, &elem_sx, &elem_sy);
fixed_t eff_sx = fixed_mul(scale_x, elem_sx);
fixed_t eff_sy = fixed_mul(scale_y, elem_sy);
// Extract and rasterize path
int d_len = svg_get_attr(elem_start, elem_len, " d", d_buf, SVG_MAX_PATH_LEN);
if (d_len > 0) {
el.clear();
svg_path_to_edges(el, d_buf, d_len, scale_x, scale_y, vb_x, vb_y);
svg_path_to_edges(el, d_buf, d_len, eff_sx, eff_sy, vb_x, vb_y);
if (el.count > 0)
svg_rasterize_blend(el, icon.pixels, target_w, target_h, elem_color.to_pixel(), alpha);
}
@@ -1349,6 +1527,11 @@ inline SvgIcon svg_render(const char* svg_data, int svg_len, int target_w, int t
int alpha = svg_get_element_opacity(elem_start, elem_len);
fixed_t elem_sx, elem_sy;
svg_get_element_scale(elem_start, elem_len, &elem_sx, &elem_sy);
fixed_t eff_sx = fixed_mul(scale_x, elem_sx);
fixed_t eff_sy = fixed_mul(scale_y, elem_sy);
char attr_buf[32];
fixed_t cx = 0, cy = 0, r = 0;
if (svg_get_attr(elem_start, elem_len, " cx", attr_buf, sizeof(attr_buf)) > 0)
@@ -1358,10 +1541,10 @@ inline SvgIcon svg_render(const char* svg_data, int svg_len, int target_w, int t
if (svg_get_attr(elem_start, elem_len, " r", attr_buf, sizeof(attr_buf)) > 0)
svg_parse_fixed(attr_buf, &r);
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);
fixed_t scx = fixed_mul(cx - vb_x, eff_sx);
fixed_t scy = fixed_mul(cy - vb_y, eff_sy);
fixed_t srx = fixed_mul(r, eff_sx);
fixed_t sry = fixed_mul(r, eff_sy);
fixed_t sr = (srx + sry) >> 1;
el.clear();
@@ -1395,6 +1578,11 @@ inline SvgIcon svg_render(const char* svg_data, int svg_len, int target_w, int t
int alpha = svg_get_element_opacity(elem_start, elem_len);
fixed_t elem_sx, elem_sy;
svg_get_element_scale(elem_start, elem_len, &elem_sx, &elem_sy);
fixed_t eff_sx = fixed_mul(scale_x, elem_sx);
fixed_t eff_sy = fixed_mul(scale_y, elem_sy);
char attr_buf[32];
fixed_t rx_val = 0, ry_val = 0, rw = 0, rh = 0, rrx = 0, rry = 0;
@@ -1411,12 +1599,12 @@ inline SvgIcon svg_render(const char* svg_data, int svg_len, int target_w, int t
if (svg_get_attr(elem_start, elem_len, " ry", attr_buf, sizeof(attr_buf)) > 0)
svg_parse_fixed(attr_buf, &rry);
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);
fixed_t sx = fixed_mul(rx_val - vb_x, eff_sx);
fixed_t sy = fixed_mul(ry_val - vb_y, eff_sy);
fixed_t sw = fixed_mul(rw, eff_sx);
fixed_t sh = fixed_mul(rh, eff_sy);
fixed_t srx = fixed_mul(rrx, eff_sx);
fixed_t sry = fixed_mul(rry, eff_sy);
el.clear();
svg_rect_edges(el, sx, sy, sw, sh, srx, sry);
@@ -1438,6 +1626,46 @@ inline SvgIcon svg_render(const char* svg_data, int svg_len, int target_w, int t
// ---------------------------------------------------------------------------
// Load SVG from VFS and render
// ---------------------------------------------------------------------------
// Extract intrinsic dimensions from an SVG document. Prefers width/height
// attributes, falls back to viewBox, then to a 256x256 default. Used by the
// image viewer to render SVGs at their natural size rather than a fixed icon
// size.
inline void svg_get_natural_size(const char* svg_data, int svg_len,
int* out_w, int* out_h) {
int w = 0, h = 0;
const char* svg_tag = svg_strstr(svg_data, svg_len, "<svg");
if (svg_tag) {
int tag_offset = (int)(svg_tag - svg_data);
int tag_end = tag_offset;
while (tag_end < svg_len && svg_data[tag_end] != '>') ++tag_end;
int tag_len = tag_end - tag_offset + 1;
char attr_buf[64];
if (svg_get_attr(svg_tag, tag_len, " width", attr_buf, sizeof(attr_buf)) > 0)
w = svg_parse_int(attr_buf);
if (svg_get_attr(svg_tag, tag_len, " height", attr_buf, sizeof(attr_buf)) > 0)
h = svg_parse_int(attr_buf);
if ((w <= 0 || h <= 0) &&
svg_get_attr(svg_tag, tag_len, " viewBox", attr_buf, sizeof(attr_buf)) > 0) {
fixed_t vbx, vby, vbw, vbh;
const char* vp = attr_buf;
int c;
c = svg_parse_fixed(vp, &vbx); vp += c; while (*vp && svg_char_is_sep(*vp)) ++vp;
c = svg_parse_fixed(vp, &vby); vp += c; while (*vp && svg_char_is_sep(*vp)) ++vp;
c = svg_parse_fixed(vp, &vbw); vp += c; while (*vp && svg_char_is_sep(*vp)) ++vp;
svg_parse_fixed(vp, &vbh);
(void)vbx; (void)vby;
if (w <= 0) w = fixed_to_int(vbw);
if (h <= 0) h = fixed_to_int(vbh);
}
}
if (w <= 0) w = 256;
if (h <= 0) h = 256;
if (out_w) *out_w = w;
if (out_h) *out_h = h;
}
inline SvgIcon svg_load(const char* vfs_path, int target_w, int target_h, Color fill_color) {
int fd = montauk::open(vfs_path);
if (fd < 0) {