ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
reflow_svg_path.c
Go to the documentation of this file.
1
20
21#include "ra8_attributes.h"
22#include "reflow_svg_internal.h"
23
24/* ===========================================================================
25 * Path (`d` mini-language; curves approximated by their endpoint chords)
26 * ===========================================================================
27 */
28
60static int32_t internal_cmd_argc(char u)
61{
62 switch (u) {
63 case 'm':
64 case 'l':
65 case 't':
66 return (int32_t)k_svg_path_ep;
67 case 'h':
68 case 'v':
69 return 1;
70 case 'c':
71 return (int32_t)k_svg_argc_cube;
72 case 's':
73 case 'q':
74 return (int32_t)k_svg_argc_quad;
75 case 'a':
76 return (int32_t)k_svg_path_args;
77 case 'z':
78 return 0;
79 default:
80 return -1;
81 }
82}
83
113static void
114internal_path_step(char u, bool rel, const int32_t* args, int32_t na, int32_t* cx, int32_t* cy)
115{
116 if (u == 'h') {
117 *cx = rel ? (*cx + args[0]) : args[0];
118 return;
119 }
120 if (u == 'v') {
121 *cy = rel ? (*cy + args[0]) : args[0];
122 return;
123 }
124 const int32_t ex = args[na - (int32_t)k_svg_path_ep];
125 const int32_t ey = args[na - 1];
126 *cx = rel ? (*cx + ex) : ex;
127 *cy = rel ? (*cy + ey) : ey;
128}
129
160static float internal_bezier1(float tt, float c0, float c1, float c2, float c3)
161{
162 static const float k_cubic = 3.0F;
163 const float mt = 1.0F - tt;
164 return ((mt * mt * mt) * c0) + (k_cubic * (mt * mt) * tt * c1) + (k_cubic * mt * (tt * tt) * c2) +
165 ((tt * tt * tt) * c3);
166}
167
199 svg_pt_t p0,
200 svg_pt_t p1,
201 svg_pt_t p2,
202 svg_pt_t p3,
203 int32_t* xs,
204 int32_t* ys,
205 int32_t* n)
206{
207 /* Bounded: k_svg_curve_seg samples, capped by k_svg_poly_max. */
208 for (int32_t j = 1; (j <= (int32_t)k_svg_curve_seg) && (*n < (int32_t)k_svg_poly_max); ++j) {
209 const float tt = (float)j / (float)k_svg_curve_seg;
210 const float bx = internal_bezier1(tt, (float)p0.x, (float)p1.x, (float)p2.x, (float)p3.x);
211 const float by = internal_bezier1(tt, (float)p0.y, (float)p1.y, (float)p2.y, (float)p3.y);
212 priv_ra8_svgp_map_point(t, (int32_t)bx, (int32_t)by, &xs[*n], &ys[*n]);
213 ++(*n);
214 }
215}
216
248static svg_pt_t internal_arg_pt(bool rel, int32_t cx, int32_t cy, int32_t ax, int32_t ay)
249{
250 const svg_pt_t p = {.x = rel ? (cx + ax) : ax, .y = rel ? (cy + ay) : ay};
251 return p;
252}
253
261typedef struct {
262 int32_t cx;
263 int32_t cy;
265 char kind;
267
296static float internal_bezier_q1(float tt, float c0, float c1, float c2)
297{
298 static const float k_quadratic = 2.0F;
299 const float mt = 1.0F - tt;
300 return ((mt * mt) * c0) + (k_quadratic * mt * tt * c1) + ((tt * tt) * c2);
301}
302
333 svg_pt_t p0,
334 svg_pt_t p1,
335 svg_pt_t p2,
336 int32_t* xs,
337 int32_t* ys,
338 int32_t* n)
339{
340 /* Bounded: k_svg_curve_seg samples, capped by k_svg_poly_max. */
341 for (int32_t j = 1; (j <= (int32_t)k_svg_curve_seg) && (*n < (int32_t)k_svg_poly_max); ++j) {
342 const float tt = (float)j / (float)k_svg_curve_seg;
343 const float bx = internal_bezier_q1(tt, (float)p0.x, (float)p1.x, (float)p2.x);
344 const float by = internal_bezier_q1(tt, (float)p0.y, (float)p1.y, (float)p2.y);
345 priv_ra8_svgp_map_point(t, (int32_t)bx, (int32_t)by, &xs[*n], &ys[*n]);
346 ++(*n);
347 }
348}
349
377{
378 const svg_pt_t r = {.x = (2 * cur.x) - ctrl.x, .y = (2 * cur.y) - ctrl.y};
379 return r;
380}
381
411static svg_pt_t internal_smooth_ctrl(svg_pt_t p0, const path_state_t* st, char want)
412{
413 return (st->kind == want) ? internal_reflect(p0, st->ctrl) : p0;
414}
415
449static int32_t internal_emit_cubic(const svg_xform_t* t,
450 svg_pt_t p0,
451 svg_pt_t p1,
452 svg_pt_t p2,
453 svg_pt_t p3,
454 path_state_t* st,
455 int32_t* xs,
456 int32_t* ys,
457 int32_t n)
458{
459 int32_t m = n;
460 internal_flatten_cubic(t, p0, p1, p2, p3, xs, ys, &m);
461 st->ctrl = p2;
462 st->kind = 'c';
463 st->cx = p3.x;
464 st->cy = p3.y;
465 return m;
466}
467
500static int32_t internal_emit_quad(const svg_xform_t* t,
501 svg_pt_t p0,
502 svg_pt_t p1,
503 svg_pt_t p2,
504 path_state_t* st,
505 int32_t* xs,
506 int32_t* ys,
507 int32_t n)
508{
509 int32_t m = n;
510 internal_flatten_quad(t, p0, p1, p2, xs, ys, &m);
511 st->ctrl = p1;
512 st->kind = 'q';
513 st->cx = p2.x;
514 st->cy = p2.y;
515 return m;
516}
517
552static int32_t internal_path_curve(const svg_xform_t* t,
553 char u,
554 bool rel,
555 const int32_t* args,
556 path_state_t* st,
557 int32_t* xs,
558 int32_t* ys,
559 int32_t n)
560{
561 const svg_pt_t p0 = {.x = st->cx, .y = st->cy};
562 if ((u == 'q') || (u == 't')) {
563 const bool sm = (u == 't');
564 const svg_pt_t p1 = sm ? internal_smooth_ctrl(p0, st, 'q')
565 : internal_arg_pt(rel, st->cx, st->cy, args[0], args[1]);
566 const int32_t e = sm ? INT32_C(0) : INT32_C(2);
567 const svg_pt_t p2 = internal_arg_pt(rel, st->cx, st->cy, args[e], args[e + 1]);
568 return internal_emit_quad(t, p0, p1, p2, st, xs, ys, n);
569 }
570 if ((u == 'c') || (u == 's')) {
571 const bool sm = (u == 's');
572 const svg_pt_t p1 = sm ? internal_smooth_ctrl(p0, st, 'c')
573 : internal_arg_pt(rel, st->cx, st->cy, args[0], args[1]);
574 const int32_t b = sm ? INT32_C(0) : INT32_C(2);
575 const svg_pt_t p2 = internal_arg_pt(rel, st->cx, st->cy, args[b], args[b + 1]);
576 const svg_pt_t p3 = internal_arg_pt(rel, st->cx, st->cy, args[b + 2], args[b + 3]);
577 return internal_emit_cubic(t, p0, p1, p2, p3, st, xs, ys, n);
578 }
579 if (u == 'a') {
580 const svg_pt_t p_end =
581 internal_arg_pt(rel, st->cx, st->cy, args[k_svg_arc_ex], args[k_svg_arc_ey]);
582 int32_t m = n;
583 priv_ra8_svgp_flatten_arc(t, p0, args, p_end, xs, ys, &m);
584 st->kind = 0; /* an arc breaks the smooth-reflection chain */
585 st->cx = p_end.x;
586 st->cy = p_end.y;
587 return m;
588 }
589 return -1;
590}
591
622static char internal_next_cmd(const uint8_t* d, size_t dlen, size_t* i, char* last)
623{
624 while ((*i < dlen) && (priv_ra8_svgp_ws((char)d[*i]) || (d[*i] == (uint8_t)','))) {
625 ++(*i);
626 }
627 if (*i >= dlen) {
628 return 0;
629 }
630 const char c = (char)d[*i];
631 if (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z'))) {
632 *last = c;
633 ++(*i);
634 return c;
635 }
636 return *last;
637}
638
671static int32_t
672internal_parse_path(const uint8_t* d, size_t dlen, const svg_xform_t* t, int32_t* xs, int32_t* ys)
673{
674 int32_t n = 0;
675 path_state_t st = {.cx = 0, .cy = 0, .ctrl = {.x = 0, .y = 0}, .kind = 0};
676 char last = 0;
677 size_t i = 0U;
678 /* Bounded: <= k_svg_poly_max commands. Every non-'z' command emits at least
679 * one vertex (n advances) or hits the vertex cap, and the 'z' arm advances i
680 * on a malformed implicit repeat, so the loop always makes progress and
681 * cannot spin on untrusted input (NASA P10 Rule 2). */
682 while ((i < dlen) && (n < (int32_t)k_svg_poly_max)) {
683 const size_t i_before = i;
684 const char c = internal_next_cmd(d, dlen, &i, &last);
685 const char u = priv_ra8_svgp_lc(c);
686 /*
687 * internal_next_cmd() only ever yields c in {0} U ['A'..'Z'] U ['a'..'z'] (an
688 * ASCII path-command letter or the 0 end-marker). Whenever c >= 'a' holds,
689 * c is a lowercase letter (0x61..0x7A) and therefore always <= 'z'; no
690 * yielded value can make (c >= 'a') true while (c <= 'z') is false. The
691 * (c <= 'z') condition thus cannot be flipped independently.
692 */
693 /* mcdc-deactivated: c is a command letter or 0; (c<='z') is always true once (c>='a') holds. */
694 const bool rel = (c >= 'a') && (c <= 'z');
695 const int32_t na = (c != '\0') ? internal_cmd_argc(u) : -1;
696 if (na < 0) {
697 break; /* unknown / no current command */
698 }
699 if (u == 'z') {
700 /* Close: the fill closes implicitly. 'z' takes no args, so an implicit
701 * repeat after a non-command byte (e.g. the digit in d="Z2") leaves the
702 * cursor unmoved and would spin forever -- skip the byte to guarantee
703 * forward progress. */
704 if (i == i_before) {
705 ++i;
706 }
707 continue;
708 }
709 int32_t args[k_svg_path_args] = {};
710 for (int32_t a = 0; a < na; ++a) {
711 args[a] = priv_ra8_svgp_num(d, dlen, &i);
712 }
713 const int32_t cn = internal_path_curve(t, u, rel, args, &st, xs, ys, n);
714 if (cn >= 0) {
715 n = cn; /* C/S/Q/T flattened into segments */
716 } else {
717 internal_path_step(u, rel, args, na, &st.cx, &st.cy);
718 priv_ra8_svgp_map_point(t, st.cx, st.cy, &xs[n], &ys[n]);
719 ++n;
720 st.kind = 0; /* a non-curve breaks the smooth-reflection chain */
721 }
722 if (u == 'm') {
723 last = rel ? 'l' : 'L'; /* implicit coords after M are line-tos */
724 }
725 }
726 return n;
727}
728
753void priv_ra8_svgp_draw_path(const uint8_t* s, size_t len, const svg_xform_t* t)
754{
755 int32_t gi = -1;
756 const uint32_t fill = priv_ra8_svgp_resolve_fill(s, len, t, (uint32_t)k_svg_def_fill, &gi);
757 size_t off = 0U;
758 size_t vl = 0U;
759 if (((gi < 0) && (fill == (uint32_t)k_svg_no_paint)) ||
760 !priv_ra8_svgp_attr(s, len, "d", &off, &vl)) {
761 return;
762 }
763 int32_t xs[k_svg_poly_max] = {};
764 int32_t ys[k_svg_poly_max] = {};
765 const int32_t n = internal_parse_path(&s[off], vl, t, xs, ys);
766 if (gi >= 0) {
767 priv_ra8_svgp_fill_poly_grad(xs, ys, n, &t->grads->g[gi]);
768 } else {
769 priv_ra8_svgp_fill_poly(xs, ys, n, fill);
770 }
771}
Annotation-attribute framework macros for ra8-firmware.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
int32_t priv_ra8_svgp_num(const uint8_t *s, size_t len, size_t *i)
Parse the integer part of one SVG number at s[*i], skipping leading separators (whitespace / commas);...
Definition reflow_svg.c:255
bool priv_ra8_svgp_ws(char c)
Test whether a character is XML whitespace.
Definition reflow_svg.c:58
bool priv_ra8_svgp_attr(const uint8_t *s, size_t len, const char *name, size_t *voff, size_t *vlen)
Find attribute name in tag span s[0..len); return its value slice.
Definition reflow_svg.c:364
uint32_t priv_ra8_svgp_resolve_fill(const uint8_t *s, size_t len, const svg_xform_t *t, uint32_t def, int32_t *gi)
Resolve a shape's 'fill': a solid colour, or a gradient index via gi.
Definition reflow_svg.c:721
char priv_ra8_svgp_lc(char c)
ASCII-fold a character to lower case.
Definition reflow_svg.c:88
Cross-TU surface for the split minimal-SVG subset (#112).
void priv_ra8_svgp_fill_poly_grad(const int32_t *xs, const int32_t *ys, int32_t n, const svg_grad_t *g)
Per-pixel gradient scanline fill of polygon (xs,ys)[0..n).
void priv_ra8_svgp_fill_poly(const int32_t *xs, const int32_t *ys, int32_t n, uint32_t color)
Even-odd scanline fill of polygon (xs,ys)[0..n) with a solid colour.
void priv_ra8_svgp_map_point(const svg_xform_t *t, int32_t ux, int32_t uy, int32_t *fx, int32_t *fy)
Map a user-space point through the FULL affine, then viewBox->box.
void priv_ra8_svgp_flatten_arc(const svg_xform_t *t, svg_pt_t p0, const int32_t *args, svg_pt_t p_end, int32_t *xs, int32_t *ys, int32_t *n)
Flatten an SVG elliptical arc into the polygon vertex array.
@ k_svg_arc_ey
A arg index: endpoint Y.
@ k_svg_path_args
Max args of a path command (A).
@ k_svg_arc_ex
A arg index: endpoint X.
@ k_svg_def_fill
SVG default fill is black.
@ k_svg_no_paint
none / absent paint sentinel.
@ k_svg_poly_max
Max points / scanline crossings.
@ k_svg_path_ep
Endpoint pair size (x, y); M/L/T.
@ k_svg_argc_cube
Arg count of C.
@ k_svg_curve_seg
Segments per cubic-Bezier flatten.
@ k_svg_argc_quad
Arg count of S / Q.
static int32_t internal_parse_path(const uint8_t *d, size_t dlen, const svg_xform_t *t, int32_t *xs, int32_t *ys)
Parse a path 'd' value into framebuffer-space vertices; return count.
static void internal_flatten_cubic(const svg_xform_t *t, svg_pt_t p0, svg_pt_t p1, svg_pt_t p2, svg_pt_t p3, int32_t *xs, int32_t *ys, int32_t *n)
Flatten a cubic Bezier curve (P0..P3, user space) into the vertex array.
static int32_t internal_emit_cubic(const svg_xform_t *t, svg_pt_t p0, svg_pt_t p1, svg_pt_t p2, svg_pt_t p3, path_state_t *st, int32_t *xs, int32_t *ys, int32_t n)
Flatten a cubic Bezier (P0..P3) into the vertex list and advance path state.
static float internal_bezier1(float tt, float c0, float c1, float c2, float c3)
Evaluate one axis of a cubic Bernstein-Bezier polynomial at parameter tt.
static void internal_flatten_quad(const svg_xform_t *t, svg_pt_t p0, svg_pt_t p1, svg_pt_t p2, int32_t *xs, int32_t *ys, int32_t *n)
Flatten a quadratic Bezier curve (P0..P2, user space) into the vertex array.
static int32_t internal_emit_quad(const svg_xform_t *t, svg_pt_t p0, svg_pt_t p1, svg_pt_t p2, path_state_t *st, int32_t *xs, int32_t *ys, int32_t n)
Flatten a quadratic Bezier (P0..P2) into the vertex list and advance path state.
static svg_pt_t internal_arg_pt(bool rel, int32_t cx, int32_t cy, int32_t ax, int32_t ay)
Resolve an absolute or relative SVG point from an argument pair.
static svg_pt_t internal_reflect(svg_pt_t cur, svg_pt_t ctrl)
Reflect control point ctrl through current point cur.
static svg_pt_t internal_smooth_ctrl(svg_pt_t p0, const path_state_t *st, char want)
Derive the first control point for a smooth curve command.
static void internal_path_step(char u, bool rel, const int32_t *args, int32_t na, int32_t *cx, int32_t *cy)
Advance the current path point to the endpoint of a line-type command.
static int32_t internal_cmd_argc(char u)
Return the argument count for a lower-cased SVG path command letter.
static char internal_next_cmd(const uint8_t *d, size_t dlen, size_t *i, char *last)
Resolve the next path command at d[*i].
static float internal_bezier_q1(float tt, float c0, float c1, float c2)
Evaluate one axis of a quadratic Bernstein-Bezier polynomial at parameter tt.
void priv_ra8_svgp_draw_path(const uint8_t *s, size_t len, const svg_xform_t *t)
Draw one SVG 'path' element as a filled polygon.
static int32_t internal_path_curve(const svg_xform_t *t, char u, bool rel, const int32_t *args, path_state_t *st, int32_t *xs, int32_t *ys, int32_t n)
Flatten a cubic/quadratic path curve command into the vertex list.
Running cursor state while parsing a <path> d string.
int32_t cx
Current point X (user space).
svg_pt_t ctrl
Last control (abs): quad ctrl (Q/T) or cubic ctrl2 (C/S).
char kind
'q' if the last command was Q/T, 'c' if C/S, else 0.
int32_t cy
Current point Y (user space).
svg_grad_t g[k_svg_grad_max]
The gradient table.
A 2-D point in SVG user space (an x/y pair).
int32_t y
Y coordinate.
int32_t x
X coordinate.
SVG-user-space -> framebuffer-box coordinate transform.
const svg_grads_t * grads
Document gradient set, or NULL.