feat: refresh button in Files, svg.hpp arc handling

This commit is contained in:
2026-05-18 19:51:23 +02:00
parent 96abe9a5cd
commit 2ceefaf1ca
9 changed files with 198 additions and 39 deletions
+148 -12
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': {