std1.0.0-beta-7 SafeC docs

Source gui/gui_path.sc

View in the repository

// SafeC Standard Library — GUI: paths and affine transforms (see gui_path.h).
#pragma once
#include <std/gui/gui_path.h>
#include <std/math.h>
#include <std/collections/vec.h>

namespace std {

#define GUI_PI_F 3.14159265358979f
#define GUI_KAPPA_F 0.5522847498f

// ── rects ─────────────────────────────────────────────────────────────────

struct GuiRect GuiRect::new(float x, float y, float w, float h) {
    struct GuiRect r;
    r.x = x; r.y = y; r.w = w; r.h = h;
    return r;
}

int GuiRect::contains(float px, float py) const {
    return (px >= self.x && py >= self.y && px < self.x + self.w && py < self.y + self.h) ? 1 : 0;
}

struct GuiRect GuiRect::intersect(struct GuiRect o) const {
    float x0 = self.x > o.x ? self.x : o.x;
    float y0 = self.y > o.y ? self.y : o.y;
    float x1 = self.x + self.w < o.x + o.w ? self.x + self.w : o.x + o.w;
    float y1 = self.y + self.h < o.y + o.h ? self.y + self.h : o.y + o.h;
    if (x1 < x0) { x1 = x0; }
    if (y1 < y0) { y1 = y0; }
    return GuiRect::new(x0, y0, x1 - x0, y1 - y0);
}

struct GuiRect GuiRect::unite(struct GuiRect o) const {
    if (self.w <= 0.0f || self.h <= 0.0f) { return o; }
    if (o.w <= 0.0f || o.h <= 0.0f) { return GuiRect::new(self.x, self.y, self.w, self.h); }
    float x0 = self.x < o.x ? self.x : o.x;
    float y0 = self.y < o.y ? self.y : o.y;
    float x1 = self.x + self.w > o.x + o.w ? self.x + self.w : o.x + o.w;
    float y1 = self.y + self.h > o.y + o.h ? self.y + self.h : o.y + o.h;
    return GuiRect::new(x0, y0, x1 - x0, y1 - y0);
}

int GuiRect::is_empty() const {
    return (self.w <= 0.0f || self.h <= 0.0f) ? 1 : 0;
}

// ── matrices ──────────────────────────────────────────────────────────────

struct GuiMatrix GuiMatrix::new(float a, float b, float c, float d, float e, float f) {
    struct GuiMatrix m;
    m.a = a; m.b = b; m.c = c; m.d = d; m.e = e; m.f = f;
    return m;
}

struct GuiMatrix GuiMatrix::identity() {
    return GuiMatrix::new(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
}

struct GuiMatrix GuiMatrix::translation(float tx, float ty) {
    return GuiMatrix::new(1.0f, 0.0f, 0.0f, 1.0f, tx, ty);
}

struct GuiMatrix GuiMatrix::scaling(float sx, float sy) {
    return GuiMatrix::new(sx, 0.0f, 0.0f, sy, 0.0f, 0.0f);
}

struct GuiMatrix GuiMatrix::rotation(float radians) {
    float s = sin_f(radians);
    float c = cos_f(radians);
    return GuiMatrix::new(c, s, -s, c, 0.0f, 0.0f);
}

struct GuiMatrix GuiMatrix::mul(struct GuiMatrix n) const {
    // self * n
    return GuiMatrix::new(self.a * n.a + self.c * n.b,
                          self.b * n.a + self.d * n.b,
                          self.a * n.c + self.c * n.d,
                          self.b * n.c + self.d * n.d,
                          self.a * n.e + self.c * n.f + self.e,
                          self.b * n.e + self.d * n.f + self.f);
}

struct GuiMatrix GuiMatrix::translate(float tx, float ty) const {
    return self.mul(GuiMatrix::translation(tx, ty));
}

struct GuiMatrix GuiMatrix::scale(float sx, float sy) const {
    return self.mul(GuiMatrix::scaling(sx, sy));
}

struct GuiMatrix GuiMatrix::rotate(float radians) const {
    return self.mul(GuiMatrix::rotation(radians));
}

int GuiMatrix::invert(&GuiMatrix out) const {
    double det = (double)self.a * (double)self.d - (double)self.b * (double)self.c;
    if (det == 0.0 || det != det) { return 0; }
    double id = 1.0 / det;
    double a = (double)self.d * id;
    double b = -(double)self.b * id;
    double c = -(double)self.c * id;
    double d = (double)self.a * id;
    double e = -((double)self.e * a + (double)self.f * c);
    double f = -((double)self.e * b + (double)self.f * d);
    *out = GuiMatrix::new((float)a, (float)b, (float)c, (float)d, (float)e, (float)f);
    return 1;
}

float GuiMatrix::apply_x(float x, float y) const { return self.a * x + self.c * y + self.e; }
float GuiMatrix::apply_y(float x, float y) const { return self.b * x + self.d * y + self.f; }

struct GuiRect GuiMatrix::apply_rect(struct GuiRect r) const {
    float xs[4]; float ys[4];
    xs[0] = self.apply_x(r.x, r.y);             ys[0] = self.apply_y(r.x, r.y);
    xs[1] = self.apply_x(r.x + r.w, r.y);       ys[1] = self.apply_y(r.x + r.w, r.y);
    xs[2] = self.apply_x(r.x, r.y + r.h);       ys[2] = self.apply_y(r.x, r.y + r.h);
    xs[3] = self.apply_x(r.x + r.w, r.y + r.h); ys[3] = self.apply_y(r.x + r.w, r.y + r.h);
    float x0 = xs[0]; float x1 = xs[0]; float y0 = ys[0]; float y1 = ys[0];
    int i = 1;
    while (i < 4) {
        if (xs[i] < x0) { x0 = xs[i]; }
        if (xs[i] > x1) { x1 = xs[i]; }
        if (ys[i] < y0) { y0 = ys[i]; }
        if (ys[i] > y1) { y1 = ys[i]; }
        i = i + 1;
    }
    return GuiRect::new(x0, y0, x1 - x0, y1 - y0);
}

float GuiMatrix::scale_factor() const {
    float det = self.a * self.d - self.b * self.c;
    if (det < 0.0f) { det = -det; }
    return sqrt_f(det);
}

int GuiMatrix::is_identity() const {
    return (self.a == 1.0f && self.b == 0.0f && self.c == 0.0f && self.d == 1.0f &&
            self.e == 0.0f && self.f == 0.0f) ? 1 : 0;
}

int GuiMatrix::is_translation() const {
    return (self.a == 1.0f && self.b == 0.0f && self.c == 0.0f && self.d == 1.0f) ? 1 : 0;
}

int GuiMatrix::is_axis_aligned() const {
    return (self.b == 0.0f && self.c == 0.0f) ? 1 : 0;
}

// ── paths ─────────────────────────────────────────────────────────────────

struct GuiPath GuiPath::new() {
    struct GuiPath p;
    p.segs = Vec::new();
    p.curX = 0.0f; p.curY = 0.0f;
    p.startX = 0.0f; p.startY = 0.0f;
    p.hasCurrent = 0;
    return p;
}

static void __gui_path_push(&GuiPath p, int verb, float x1, float y1, float x2, float y2, float x3, float y3) {
    struct GuiPathSeg s;
    s.verb = verb;
    s.x1 = x1; s.y1 = y1; s.x2 = x2; s.y2 = y2; s.x3 = x3; s.y3 = y3;
    p.segs.push(s);
}

void GuiPath::move_to(float x, float y) {
    __gui_path_push(self, GUI_PATH_MOVE, x, y, 0.0f, 0.0f, 0.0f, 0.0f);
    self.curX = x; self.curY = y;
    self.startX = x; self.startY = y;
    self.hasCurrent = 1;
}

void GuiPath::line_to(float x, float y) {
    if (!self.hasCurrent) { self.move_to(x, y); return; }
    __gui_path_push(self, GUI_PATH_LINE, x, y, 0.0f, 0.0f, 0.0f, 0.0f);
    self.curX = x; self.curY = y;
}

void GuiPath::quad_to(float cx, float cy, float x, float y) {
    if (!self.hasCurrent) { self.move_to(cx, cy); }
    __gui_path_push(self, GUI_PATH_QUAD, cx, cy, x, y, 0.0f, 0.0f);
    self.curX = x; self.curY = y;
}

void GuiPath::cubic_to(float c1x, float c1y, float c2x, float c2y, float x, float y) {
    if (!self.hasCurrent) { self.move_to(c1x, c1y); }
    __gui_path_push(self, GUI_PATH_CUBIC, c1x, c1y, c2x, c2y, x, y);
    self.curX = x; self.curY = y;
}

void GuiPath::close() {
    if (!self.hasCurrent) { return; }
    __gui_path_push(self, GUI_PATH_CLOSE, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
    self.curX = self.startX;
    self.curY = self.startY;
}

// Emits the arc of the ellipse (center cx,cy, radii rx,ry, rotated by
// 'rot') from angle a0 through the signed angle 'sweep' as cubics, starting
// with a line (or move) to its first point.
static void __gui_path_arc_sweep(&GuiPath p, float cx, float cy, float rx, float ry, float rot,
                                 float a0, float sweep, int connect) {
    float cr = cos_f(rot);
    float sr = sin_f(rot);
    float x0 = cos_f(a0);
    float y0 = sin_f(a0);
    float sx = cx + cr * rx * x0 - sr * ry * y0;
    float sy = cy + sr * rx * x0 + cr * ry * y0;
    if (connect && p.hasCurrent) { p.line_to(sx, sy); } else { p.move_to(sx, sy); }
    float total = sweep < 0.0f ? -sweep : sweep;
    if (total < 1e-6f) { return; }
    int n = (int)ceil_f(total / (GUI_PI_F * 0.5f) - 1e-4f);
    if (n < 1) { n = 1; }
    float step = sweep / (float)n;
    float k = 4.0f / 3.0f * tan_f(step / 4.0f);
    float a = a0;
    int i = 0;
    while (i < n) {
        float b = a + step;
        float ca = cos_f(a); float sa = sin_f(a);
        float cb = cos_f(b); float sb = sin_f(b);
        // unit-circle control points
        float p1x = ca - k * sa; float p1y = sa + k * ca;
        float p2x = cb + k * sb; float p2y = sb - k * cb;
        float c1x = cx + cr * rx * p1x - sr * ry * p1y;
        float c1y = cy + sr * rx * p1x + cr * ry * p1y;
        float c2x = cx + cr * rx * p2x - sr * ry * p2y;
        float c2y = cy + sr * rx * p2x + cr * ry * p2y;
        float ex = cx + cr * rx * cb - sr * ry * sb;
        float ey = cy + sr * rx * cb + cr * ry * sb;
        p.cubic_to(c1x, c1y, c2x, c2y, ex, ey);
        a = b;
        i = i + 1;
    }
}

// The signed sweep from a0 to a1 in the HTML canvas' rules.
static float __gui_arc_sweep(float a0, float a1, int ccw) {
    float twoPi = 2.0f * GUI_PI_F;
    if (!ccw) {
        float d = a1 - a0;
        if (d >= twoPi) { return twoPi; }
        while (d < 0.0f) { d = d + twoPi; }
        while (d >= twoPi) { d = d - twoPi; }
        return d;
    }
    float d = a0 - a1;
    if (d >= twoPi) { return -twoPi; }
    while (d < 0.0f) { d = d + twoPi; }
    while (d >= twoPi) { d = d - twoPi; }
    return -d;
}

void GuiPath::arc(float cx, float cy, float r, float a0, float a1, int ccw) {
    if (r < 0.0f) { r = -r; }
    __gui_path_arc_sweep(self, cx, cy, r, r, 0.0f, a0, __gui_arc_sweep(a0, a1, ccw), 1);
}

void GuiPath::ellipse_arc(float cx, float cy, float rx, float ry, float rotation, float a0, float a1, int ccw) {
    if (rx < 0.0f) { rx = -rx; }
    if (ry < 0.0f) { ry = -ry; }
    __gui_path_arc_sweep(self, cx, cy, rx, ry, rotation, a0, __gui_arc_sweep(a0, a1, ccw), 1);
}

void GuiPath::arc_to(float x1, float y1, float x2, float y2, float r) {
    if (!self.hasCurrent) { self.move_to(x1, y1); }
    float x0 = self.curX;
    float y0 = self.curY;
    float v1x = x0 - x1; float v1y = y0 - y1;
    float v2x = x2 - x1; float v2y = y2 - y1;
    float l1 = sqrt_f(v1x * v1x + v1y * v1y);
    float l2 = sqrt_f(v2x * v2x + v2y * v2y);
    if (r <= 0.0f || l1 < 1e-6f || l2 < 1e-6f) { self.line_to(x1, y1); return; }
    v1x = v1x / l1; v1y = v1y / l1;
    v2x = v2x / l2; v2y = v2y / l2;
    float cross = v1x * v2y - v1y * v2x;
    float dot = v1x * v2x + v1y * v2y;
    if (cross > -1e-6f && cross < 1e-6f) { self.line_to(x1, y1); return; }   // collinear
    float theta = acos_f(dot < -1.0f ? -1.0f : (dot > 1.0f ? 1.0f : dot));  // angle between the legs
    float d = r / tan_f(theta * 0.5f);
    float t1x = x1 + v1x * d; float t1y = y1 + v1y * d;
    float t2x = x1 + v2x * d; float t2y = y1 + v2y * d;
    float bx = v1x + v2x; float by = v1y + v2y;
    float bl = sqrt_f(bx * bx + by * by);
    float h = r / sin_f(theta * 0.5f);
    float cx = x1 + bx / bl * h;
    float cy = y1 + by / bl * h;
    float a0 = atan2_f(t1y - cy, t1x - cx);
    float a1 = atan2_f(t2y - cy, t2x - cx);
    float sw = a1 - a0;
    while (sw > GUI_PI_F) { sw = sw - 2.0f * GUI_PI_F; }
    while (sw < -GUI_PI_F) { sw = sw + 2.0f * GUI_PI_F; }
    self.line_to(t1x, t1y);
    __gui_path_arc_sweep(self, cx, cy, r, r, 0.0f, a0, sw, 1);
}

void GuiPath::svg_arc_to(float rx, float ry, float phiDeg, int largeArc, int sweepFlag, float x, float y) {
    // SVG 1.1 implementation notes F.6.5/F.6.6: endpoint to center form.
    if (!self.hasCurrent) { self.move_to(x, y); return; }
    float x1 = self.curX;
    float y1 = self.curY;
    if (x1 == x && y1 == y) { return; }
    if (rx < 0.0f) { rx = -rx; }
    if (ry < 0.0f) { ry = -ry; }
    if (rx < 1e-6f || ry < 1e-6f) { self.line_to(x, y); return; }
    float phi = phiDeg * GUI_PI_F / 180.0f;
    float cp = cos_f(phi);
    float sp = sin_f(phi);
    float dx2 = (x1 - x) * 0.5f;
    float dy2 = (y1 - y) * 0.5f;
    float x1p = cp * dx2 + sp * dy2;
    float y1p = -sp * dx2 + cp * dy2;
    float lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);
    if (lambda > 1.0f) {
        float s = sqrt_f(lambda);
        rx = rx * s;
        ry = ry * s;
    }
    float num = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;
    float den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;
    float coef = 0.0f;
    if (den > 0.0f && num > 0.0f) { coef = sqrt_f(num / den); }
    if (largeArc == sweepFlag) { coef = -coef; }
    float cxp = coef * rx * y1p / ry;
    float cyp = -coef * ry * x1p / rx;
    float cx = cp * cxp - sp * cyp + (x1 + x) * 0.5f;
    float cy = sp * cxp + cp * cyp + (y1 + y) * 0.5f;
    float ux = (x1p - cxp) / rx; float uy = (y1p - cyp) / ry;
    float vx = (-x1p - cxp) / rx; float vy = (-y1p - cyp) / ry;
    float a0 = atan2_f(uy, ux);
    float dtheta = atan2_f(ux * vy - uy * vx, ux * vx + uy * vy);
    if (!sweepFlag && dtheta > 0.0f) { dtheta = dtheta - 2.0f * GUI_PI_F; }
    if (sweepFlag && dtheta < 0.0f) { dtheta = dtheta + 2.0f * GUI_PI_F; }
    __gui_path_arc_sweep(self, cx, cy, rx, ry, phi, a0, dtheta, 1);
    // Land exactly on the end point.
    self.curX = x;
    self.curY = y;
    unsigned long n = self.segs.len;
    if (n > 0UL) {
        &GuiPathSeg last = self.segs.at(n - 1UL);
        if (last.verb == GUI_PATH_CUBIC) { last.x3 = x; last.y3 = y; }
    }
}

void GuiPath::rect(float x, float y, float w, float h) {
    self.move_to(x, y);
    self.line_to(x + w, y);
    self.line_to(x + w, y + h);
    self.line_to(x, y + h);
    self.close();
}

void GuiPath::rounded_rect4(float x, float y, float w, float h, float tl, float tr, float br, float bl) {
    if (w < 0.0f) { x = x + w; w = -w; }
    if (h < 0.0f) { y = y + h; h = -h; }
    float lim = (w < h ? w : h) * 0.5f;
    if (tl > lim) { tl = lim; }
    if (tr > lim) { tr = lim; }
    if (br > lim) { br = lim; }
    if (bl > lim) { bl = lim; }
    if (tl < 0.0f) { tl = 0.0f; }
    if (tr < 0.0f) { tr = 0.0f; }
    if (br < 0.0f) { br = 0.0f; }
    if (bl < 0.0f) { bl = 0.0f; }
    float k = 1.0f - GUI_KAPPA_F;
    self.move_to(x + tl, y);
    self.line_to(x + w - tr, y);
    if (tr > 0.0f) { self.cubic_to(x + w - tr * k, y, x + w, y + tr * k, x + w, y + tr); }
    self.line_to(x + w, y + h - br);
    if (br > 0.0f) { self.cubic_to(x + w, y + h - br * k, x + w - br * k, y + h, x + w - br, y + h); }
    self.line_to(x + bl, y + h);
    if (bl > 0.0f) { self.cubic_to(x + bl * k, y + h, x, y + h - bl * k, x, y + h - bl); }
    self.line_to(x, y + tl);
    if (tl > 0.0f) { self.cubic_to(x, y + tl * k, x + tl * k, y, x + tl, y); }
    self.close();
}

void GuiPath::rounded_rect(float x, float y, float w, float h, float r) {
    self.rounded_rect4(x, y, w, h, r, r, r, r);
}

void GuiPath::ellipse(float cx, float cy, float rx, float ry) {
    float kx = rx * GUI_KAPPA_F;
    float ky = ry * GUI_KAPPA_F;
    self.move_to(cx + rx, cy);
    self.cubic_to(cx + rx, cy + ky, cx + kx, cy + ry, cx, cy + ry);
    self.cubic_to(cx - kx, cy + ry, cx - rx, cy + ky, cx - rx, cy);
    self.cubic_to(cx - rx, cy - ky, cx - kx, cy - ry, cx, cy - ry);
    self.cubic_to(cx + kx, cy - ry, cx + rx, cy - ky, cx + rx, cy);
    self.close();
}

void GuiPath::circle(float cx, float cy, float r) {
    self.ellipse(cx, cy, r, r);
}

void GuiPath::polygon_n_(const &float pts, int n) {
    if (n <= 0) { return; }
    unsafe {
        self.move_to(pts[0], pts[1]);
        int i = 1;
        while (i < n) {
            self.line_to(pts[i * 2], pts[i * 2 + 1]);
            i = i + 1;
        }
    }
    self.close();
}

void GuiPath::polygon([]const float pts_s) {
    unsigned long np = (unsigned long)pts_s.len;
    float pts_d_[1];
    const &float pts = &pts_d_[0];
    if (pts_s.len > 0UL) { unsafe { pts = (const &float)pts_s.ptr; } }
    int n = (int)(np / 2UL);
    self.polygon_n_(pts, n);
}

void GuiPath::append(const &GuiPath other, struct GuiMatrix m) {
    unsigned long i = 0UL;
    while (i < other.segs.len) {
        struct GuiPathSeg s = *other.segs.at(i);
        float x1 = m.apply_x(s.x1, s.y1); float y1 = m.apply_y(s.x1, s.y1);
        float x2 = m.apply_x(s.x2, s.y2); float y2 = m.apply_y(s.x2, s.y2);
        float x3 = m.apply_x(s.x3, s.y3); float y3 = m.apply_y(s.x3, s.y3);
        if (s.verb == GUI_PATH_MOVE) { self.move_to(x1, y1); }
        else if (s.verb == GUI_PATH_LINE) { self.line_to(x1, y1); }
        else if (s.verb == GUI_PATH_QUAD) { self.quad_to(x1, y1, x2, y2); }
        else if (s.verb == GUI_PATH_CUBIC) { self.cubic_to(x1, y1, x2, y2, x3, y3); }
        else { self.close(); }
        i = i + 1UL;
    }
}

void GuiPath::transform(struct GuiMatrix m) {
    unsigned long i = 0UL;
    while (i < self.segs.len) {
        &GuiPathSeg s = self.segs.at(i);
        float x1 = m.apply_x(s.x1, s.y1); float y1 = m.apply_y(s.x1, s.y1);
        float x2 = m.apply_x(s.x2, s.y2); float y2 = m.apply_y(s.x2, s.y2);
        float x3 = m.apply_x(s.x3, s.y3); float y3 = m.apply_y(s.x3, s.y3);
        s.x1 = x1; s.y1 = y1; s.x2 = x2; s.y2 = y2; s.x3 = x3; s.y3 = y3;
        i = i + 1UL;
    }
    float cx = m.apply_x(self.curX, self.curY);
    float cy = m.apply_y(self.curX, self.curY);
    float sx = m.apply_x(self.startX, self.startY);
    float sy = m.apply_y(self.startX, self.startY);
    self.curX = cx; self.curY = cy;
    self.startX = sx; self.startY = sy;
}

struct GuiRect GuiPath::bounds() const {
    int any = 0;
    float x0 = 0.0f; float y0 = 0.0f; float x1 = 0.0f; float y1 = 0.0f;
    unsigned long i = 0UL;
    while (i < self.segs.len) {
        struct GuiPathSeg s = *self.segs.at(i);
        int np = 0;
        if (s.verb == GUI_PATH_MOVE || s.verb == GUI_PATH_LINE) { np = 1; }
        else if (s.verb == GUI_PATH_QUAD) { np = 2; }
        else if (s.verb == GUI_PATH_CUBIC) { np = 3; }
        int k = 0;
        while (k < np) {
            float px = k == 0 ? s.x1 : (k == 1 ? s.x2 : s.x3);
            float py = k == 0 ? s.y1 : (k == 1 ? s.y2 : s.y3);
            if (!any) { x0 = px; x1 = px; y0 = py; y1 = py; any = 1; }
            if (px < x0) { x0 = px; }
            if (px > x1) { x1 = px; }
            if (py < y0) { y0 = py; }
            if (py > y1) { y1 = py; }
            k = k + 1;
        }
        i = i + 1UL;
    }
    return GuiRect::new(x0, y0, x1 - x0, y1 - y0);
}

int GuiPath::is_empty() const {
    unsigned long i = 0UL;
    while (i < self.segs.len) {
        int v = self.segs.at(i).verb;
        if (v != GUI_PATH_MOVE && v != GUI_PATH_CLOSE) { return 0; }
        i = i + 1UL;
    }
    return 1;
}

unsigned long GuiPath::length() const { return self.segs.len; }

void GuiPath::clear() {
    self.segs.clear();
    self.hasCurrent = 0;
    self.curX = 0.0f; self.curY = 0.0f;
    self.startX = 0.0f; self.startY = 0.0f;
}

void GuiPath::free() { self.drop(); }

void GuiPath::drop() {
    self.segs.free();
    self.hasCurrent = 0;
}

// ── SVG path data ─────────────────────────────────────────────────────────

static int __gui_svg_ws(char c) {
    return (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',') ? 1 : 0;
}

static void __gui_svg_skip(const &char d, &unsigned long i) {
    unsafe { while (d[*i] != '\0' && __gui_svg_ws(d[*i])) { *i = *i + 1UL; } }
}

// A number at d[*i] (after separators); 1 and the value, or 0.
static int __gui_svg_num(const &char d, &unsigned long i, &float out) {
    __gui_svg_skip(d, i);
    unsafe {
        unsigned long p = *i;
        double sign = 1.0;
        if (d[p] == '+' || d[p] == '-') { if (d[p] == '-') { sign = -1.0; } p = p + 1UL; }
        double v = 0.0;
        int digits = 0;
        while (d[p] >= '0' && d[p] <= '9') { v = v * 10.0 + (double)(d[p] - '0'); p = p + 1UL; digits = digits + 1; }
        if (d[p] == '.') {
            p = p + 1UL;
            double scale = 0.1;
            while (d[p] >= '0' && d[p] <= '9') { v = v + (double)(d[p] - '0') * scale; scale = scale * 0.1; p = p + 1UL; digits = digits + 1; }
        }
        if (digits == 0) { return 0; }
        if (d[p] == 'e' || d[p] == 'E') {
            unsigned long q = p + 1UL;
            double esign = 1.0;
            if (d[q] == '+' || d[q] == '-') { if (d[q] == '-') { esign = -1.0; } q = q + 1UL; }
            if (d[q] >= '0' && d[q] <= '9') {
                int ex = 0;
                while (d[q] >= '0' && d[q] <= '9') { ex = ex * 10 + (int)(d[q] - '0'); q = q + 1UL; }
                double m = 1.0;
                int k = 0;
                while (k < ex && k < 64) { m = m * 10.0; k = k + 1; }
                v = esign > 0.0 ? v * m : v / m;
                p = q;
            }
        }
        *i = p;
        *out = (float)(v * sign);
        return 1;
    }
}

// An arc flag: a single '0' or '1' (may be followed directly by the next number).
static int __gui_svg_flag(const &char d, &unsigned long i, &int out) {
    __gui_svg_skip(d, i);
    unsafe {
        char c = d[*i];
        if (c != '0' && c != '1') { return 0; }
        *out = c == '1' ? 1 : 0;
        *i = *i + 1UL;
        return 1;
    }
}

static int __gui_svg_has_num(const &char d, &unsigned long i) {
    __gui_svg_skip(d, i);
    unsafe {
        char c = d[*i];
        return ((c >= '0' && c <= '9') || c == '-' || c == '+' || c == '.') ? 1 : 0;
    }
}

int GuiPath::add_svg(const &char d) {
    unsigned long i = 0UL;
    char cmd = ' ';
    float lastCx = 0.0f; float lastCy = 0.0f;   // last control point for S/T
    char lastCmd = ' ';
    while (1) {
        __gui_svg_skip(d, &i);
        char c;
        unsafe { c = d[i]; }
        if (c == '\0') { break; }
        if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
            cmd = c;
            i = i + 1UL;
        } else if (cmd == ' ') {
            return 0;
        }
        int rel = (cmd >= 'a' && cmd <= 'z') ? 1 : 0;
        char up = rel ? (char)(cmd - 32) : cmd;
        float ox = rel ? self.curX : 0.0f;
        float oy = rel ? self.curY : 0.0f;
        if (up == 'Z') {
            self.close();
            lastCmd = 'Z';
            cmd = ' ';
            continue;
        }
        float a = 0.0f; float b = 0.0f; float c2 = 0.0f; float d2 = 0.0f; float e = 0.0f; float f = 0.0f;
        if (up == 'M') {
            if (!__gui_svg_num(d, &i, &a) || !__gui_svg_num(d, &i, &b)) { return 0; }
            self.move_to(ox + a, oy + b);
            cmd = rel ? 'l' : 'L';   // further pairs are line-tos
        } else if (up == 'L') {
            if (!__gui_svg_num(d, &i, &a) || !__gui_svg_num(d, &i, &b)) { return 0; }
            self.line_to(ox + a, oy + b);
        } else if (up == 'H') {
            if (!__gui_svg_num(d, &i, &a)) { return 0; }
            self.line_to(ox + a, self.curY);
        } else if (up == 'V') {
            if (!__gui_svg_num(d, &i, &a)) { return 0; }
            self.line_to(self.curX, oy + a);
        } else if (up == 'C') {
            if (!__gui_svg_num(d, &i, &a) || !__gui_svg_num(d, &i, &b) || !__gui_svg_num(d, &i, &c2) ||
                !__gui_svg_num(d, &i, &d2) || !__gui_svg_num(d, &i, &e) || !__gui_svg_num(d, &i, &f)) { return 0; }
            self.cubic_to(ox + a, oy + b, ox + c2, oy + d2, ox + e, oy + f);
            lastCx = ox + c2; lastCy = oy + d2;
        } else if (up == 'S') {
            if (!__gui_svg_num(d, &i, &c2) || !__gui_svg_num(d, &i, &d2) ||
                !__gui_svg_num(d, &i, &e) || !__gui_svg_num(d, &i, &f)) { return 0; }
            float r1x = self.curX; float r1y = self.curY;
            if (lastCmd == 'C' || lastCmd == 'S') { r1x = 2.0f * self.curX - lastCx; r1y = 2.0f * self.curY - lastCy; }
            self.cubic_to(r1x, r1y, ox + c2, oy + d2, ox + e, oy + f);
            lastCx = ox + c2; lastCy = oy + d2;
        } else if (up == 'Q') {
            if (!__gui_svg_num(d, &i, &a) || !__gui_svg_num(d, &i, &b) ||
                !__gui_svg_num(d, &i, &e) || !__gui_svg_num(d, &i, &f)) { return 0; }
            self.quad_to(ox + a, oy + b, ox + e, oy + f);
            lastCx = ox + a; lastCy = oy + b;
        } else if (up == 'T') {
            if (!__gui_svg_num(d, &i, &e) || !__gui_svg_num(d, &i, &f)) { return 0; }
            float qx = self.curX; float qy = self.curY;
            if (lastCmd == 'Q' || lastCmd == 'T') { qx = 2.0f * self.curX - lastCx; qy = 2.0f * self.curY - lastCy; }
            self.quad_to(qx, qy, ox + e, oy + f);
            lastCx = qx; lastCy = qy;
        } else if (up == 'A') {
            int large = 0; int sweep = 0;
            if (!__gui_svg_num(d, &i, &a) || !__gui_svg_num(d, &i, &b) || !__gui_svg_num(d, &i, &c2) ||
                !__gui_svg_flag(d, &i, &large) || !__gui_svg_flag(d, &i, &sweep) ||
                !__gui_svg_num(d, &i, &e) || !__gui_svg_num(d, &i, &f)) { return 0; }
            self.svg_arc_to(a, b, c2, large, sweep, ox + e, oy + f);
        } else {
            return 0;
        }
        lastCmd = up;
        if (!__gui_svg_has_num(d, &i)) {
            // The next thing must be a command letter (or the end).
            unsafe {
                char n = d[i];
                if (n != '\0' && !((n >= 'A' && n <= 'Z') || (n >= 'a' && n <= 'z'))) { return 0; }
            }
        }
    }
    return 1;
}

} // namespace std