ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
reflow_svg_doc.c
Go to the documentation of this file.
1
20
21#include <string.h>
22
23#include "ra8_attributes.h"
24#include "reflow_svg.h"
25#include "reflow_svg_internal.h"
26
28static const float s_svg_unit = 1.0F;
29
30/* ===========================================================================
31 * Document parsing + render walk
32 * ===========================================================================
33 */
34
65static bool internal_elem_at(const uint8_t* s, size_t len, size_t at, const char* name)
66{
67 if (!priv_ra8_svgp_starts_ci(s, len, at, name)) {
68 return false;
69 }
70 const size_t after = at + strlen(name);
71 if (after >= len) {
72 return true;
73 }
74 const char c = (char)s[after];
75 return priv_ra8_svgp_ws(c) || (c == '>') || (c == '/');
76}
77
109static bool internal_tag_span(const uint8_t* s,
110 size_t len,
111 size_t from,
112 const char* name,
113 size_t* open,
114 size_t* close)
115{
116 const size_t at = priv_ra8_svgp_find_ci(s, len, from, name);
117 if (at >= len) {
118 return false;
119 }
120 size_t j = at;
121 while ((j < len) && (s[j] != (uint8_t)'>')) {
122 ++j;
123 }
124 *open = at;
125 *close = j;
126 return true;
127}
128
154static void internal_read_viewbox(const uint8_t* s, size_t len, svg_xform_t* t)
155{
156 t->vx = 0;
157 t->vy = 0;
158 t->vw = t->bw;
159 t->vh = t->bh;
160 size_t open = 0U;
161 size_t close = 0U;
162 if (!internal_tag_span(s, len, 0U, "<svg", &open, &close)) {
163 return;
164 }
165 const uint8_t* tag = &s[open];
166 const size_t tlen = close - open;
167 size_t voff = 0U;
168 size_t vlen = 0U;
169 if (priv_ra8_svgp_attr(tag, tlen, "viewBox", &voff, &vlen)) {
170 int32_t nums[k_svg_viewbox_n] = {};
171 size_t k = 0U;
172 for (size_t n = 0U; n < (size_t)k_svg_viewbox_n; ++n) {
173 nums[n] = priv_ra8_svgp_num(&tag[voff], vlen, &k);
174 }
175 if ((nums[2] > 0) && (nums[3] > 0)) {
176 t->vx = nums[0];
177 t->vy = nums[1];
178 t->vw = nums[2];
179 t->vh = nums[3];
180 }
181 } else {
182 const int32_t wv = priv_ra8_svgp_attr_num(tag, tlen, "width", t->bw);
183 const int32_t hv = priv_ra8_svgp_attr_num(tag, tlen, "height", t->bh);
184 if ((wv > 0) && (hv > 0)) {
185 t->vw = wv;
186 t->vh = hv;
187 }
188 }
189}
190
217static void
218internal_dispatch_shape(const uint8_t* s, size_t len, size_t at, size_t close, const svg_xform_t* t)
219{
220 const uint8_t* tag = &s[at];
221 const size_t tlen = (close > at) ? (close - at) : 0U;
222 /* Compose this element's own `transform=` onto the inherited group transform. */
223 svg_xform_t lt = *t;
224 priv_ra8_svgp_apply_xform(&lt, tag, tlen);
225 if (internal_elem_at(s, len, at, "<rect")) {
226 priv_ra8_svgp_draw_rect(tag, tlen, &lt);
227 } else if (internal_elem_at(s, len, at, "<circle")) {
228 priv_ra8_svgp_draw_circle(tag, tlen, &lt);
229 } else if (internal_elem_at(s, len, at, "<polygon")) {
230 priv_ra8_svgp_draw_polygon(tag, tlen, &lt);
231 } else if (internal_elem_at(s, len, at, "<polyline")) {
232 priv_ra8_svgp_draw_polyline(tag, tlen, &lt);
233 } else if (internal_elem_at(s, len, at, "<path")) {
234 priv_ra8_svgp_draw_path(tag, tlen, &lt);
235 } else if (internal_elem_at(s, len, at, "<line")) {
236 priv_ra8_svgp_draw_line(tag, tlen, &lt);
237 } else {
238 /* Unsupported element -> skip. */
239 }
240}
241
267{
268 *out = *t;
269 out->ua = g.a;
270 out->ub = g.b;
271 out->uc = g.c;
272 out->ud = g.d;
273 out->ue = g.e;
274 out->uf = g.f;
275}
276
310static int32_t internal_group_open(const uint8_t* s,
311 size_t i,
312 size_t close,
313 const svg_xform_t* t,
314 svg_utf_t* gstk,
315 int32_t gsp)
316{
317 svg_xform_t gt = {};
318 internal_xform_with_group(t, gstk[gsp], &gt);
319 priv_ra8_svgp_apply_xform(&gt, &s[i], (close > i) ? (close - i) : 0U);
320 /*
321 * internal_group_open is dispatched only for a well-formed element tag whose name
322 * occupies at least one byte between '<' (scan position `i`) and '>'
323 * (position `close`), so close > i is structurally invariant on every
324 * reachable call. The close <= i arm defends against an empty "<>" tag that
325 * the upstream element scanner never yields, so the first condition cannot be
326 * flipped independently.
327 */
328 /* mcdc-deactivated: close>i is invariant for a named tag; the empty-"<>" (close<=i) arm is unreachable via public API. */
329 const bool self_close = (close > i) && (s[close - 1U] == (uint8_t)'/');
330 if (self_close || (gsp >= (int32_t)k_svg_g_depth_max)) {
331 return gsp;
332 }
333 gstk[gsp + 1] =
334 (svg_utf_t){.a = gt.ua, .b = gt.ub, .c = gt.uc, .d = gt.ud, .e = gt.ue, .f = gt.uf};
335 return gsp + 1;
336}
337
364static void internal_draw_shapes(const uint8_t* s, size_t len, const svg_xform_t* t)
365{
366 svg_utf_t gstk[k_svg_g_depth_max + 1U];
367 int32_t gsp = 0;
368 gstk[0] = (svg_utf_t){.a = t->ua, .b = t->ub, .c = t->uc, .d = t->ud, .e = t->ue, .f = t->uf};
369 size_t i = 0U;
370 /* Bounded: each iteration advances past one '<...>' element or breaks at EOF. */
371 while (i < len) {
372 while ((i < len) && (s[i] != (uint8_t)'<')) {
373 ++i;
374 }
375 if (i >= len) {
376 break;
377 }
378 size_t close = i;
379 while ((close < len) && (s[close] != (uint8_t)'>')) {
380 ++close;
381 }
382 if (internal_elem_at(s, len, i, "</g")) {
383 gsp = (gsp > 0) ? (gsp - 1) : 0;
384 } else if (internal_elem_at(s, len, i, "<g")) {
385 gsp = internal_group_open(s, i, close, t, gstk, gsp);
386 } else {
387 svg_xform_t ct = {};
388 internal_xform_with_group(t, gstk[gsp], &ct);
389 internal_dispatch_shape(s, len, i, close, &ct);
390 }
391 i = (close < len) ? (close + 1U) : len;
392 }
393}
394
395/* ===========================================================================
396 * Gradient definitions (<linearGradient> / <radialGradient> + <stop>)
397 * ===========================================================================
398 */
399
429static float internal_attr_numf(const uint8_t* s, size_t len, const char* name, float def)
430{
431 size_t off = 0U;
432 size_t vl = 0U;
433 if (!priv_ra8_svgp_attr(s, len, name, &off, &vl)) {
434 return def;
435 }
436 size_t k = 0U;
437 return priv_ra8_svgp_numf(&s[off], vl, &k);
438}
439
465static void internal_copy_attr_id(const uint8_t* s, size_t len, char* dst)
466{
467 dst[0] = '\0';
468 size_t off = 0U;
469 size_t vl = 0U;
470 if (!priv_ra8_svgp_attr(s, len, "id", &off, &vl)) {
471 return;
472 }
473 const size_t n = (vl < (size_t)(k_svg_grad_id - 1U)) ? vl : (size_t)(k_svg_grad_id - 1U);
474 for (size_t i = 0U; i < n; ++i) {
475 dst[i] = (char)s[off + i];
476 }
477 dst[n] = '\0';
478}
479
507static float internal_clamp01(float v)
508{
509 if (v < 0.0F) {
510 return 0.0F;
511 }
512 return (v > s_svg_unit) ? s_svg_unit : v;
513}
514
543static float internal_stop_offset(const uint8_t* tag, size_t tlen)
544{
545 static const float k_percent = 100.0F;
546 size_t ooff = 0U;
547 size_t ovl = 0U;
548 if (!priv_ra8_svgp_attr(tag, tlen, "offset", &ooff, &ovl)) {
549 return 0.0F;
550 }
551 size_t k = 0U;
552 const float off = priv_ra8_svgp_numf(&tag[ooff], ovl, &k);
553 for (size_t z = 0U; z < ovl; ++z) {
554 if (tag[ooff + z] == (uint8_t)'%') {
555 return off / k_percent;
556 }
557 }
558 return off;
559}
560
588static uint32_t internal_stop_color(const uint8_t* tag, size_t tlen)
589{
590 size_t coff = 0U;
591 size_t cvl = 0U;
592 if (!priv_ra8_svgp_attr(tag, tlen, "stop-color", &coff, &cvl)) {
593 return (uint32_t)k_svg_def_fill;
594 }
595 const uint32_t pc = priv_ra8_svgp_paint(&tag[coff], cvl);
596 return (pc != (uint32_t)k_svg_no_paint) ? pc : (uint32_t)k_svg_def_fill;
597}
598
625static void internal_parse_stops(const uint8_t* s, size_t from, size_t end, svg_grad_t* g)
626{
627 size_t at = from;
628 /* Bounded: <= k_svg_grad_stops stops; `at` advances past each `<stop>`. */
629 while (g->nstops < (uint8_t)k_svg_grad_stops) {
630 const size_t sp = priv_ra8_svgp_find_ci(s, end, at, "<stop");
631 if (sp >= end) {
632 break;
633 }
634 size_t close = sp;
635 while ((close < end) && (s[close] != (uint8_t)'>')) {
636 ++close;
637 }
638 const uint8_t* tag = &s[sp];
639 const size_t tlen = (close > sp) ? (close - sp) : 0U;
641 g->stops[g->nstops].col = internal_stop_color(tag, tlen);
642 g->nstops += 1U;
643 at = (close < end) ? (close + 1U) : end;
644 }
645}
646
673static void internal_scan_grads(const uint8_t* s, size_t len, svg_grads_t* gs)
674{
675 static const float k_half = 0.5F;
676 gs->n = 0;
677 size_t from = 0U;
678 /* Bounded: <= k_svg_grad_max gradients; `from` strictly advances each pass. */
679 while (gs->n < (int32_t)k_svg_grad_max) {
680 const size_t lin = priv_ra8_svgp_find_ci(s, len, from, "<linearGradient");
681 const size_t rad = priv_ra8_svgp_find_ci(s, len, from, "<radialGradient");
682 const bool is_rad = (rad < lin);
683 const size_t at = is_rad ? rad : lin;
684 if (at >= len) {
685 break;
686 }
687 size_t close = at;
688 while ((close < len) && (s[close] != (uint8_t)'>')) {
689 ++close;
690 }
691 const uint8_t* tag = &s[at];
692 const size_t tlen = (close > at) ? (close - at) : 0U;
693 svg_grad_t* g = &gs->g[gs->n];
694 *g = (svg_grad_t){};
695 g->kind = is_rad ? (uint8_t)k_svg_grad_radial : (uint8_t)k_svg_grad_linear;
696 internal_copy_attr_id(tag, tlen, g->id);
697 if (is_rad) {
698 g->x1 = internal_attr_numf(tag, tlen, "cx", k_half);
699 g->y1 = internal_attr_numf(tag, tlen, "cy", k_half);
700 g->x2 = internal_attr_numf(tag, tlen, "r", k_half);
701 } else {
702 g->x1 = internal_attr_numf(tag, tlen, "x1", 0.0F);
703 g->y1 = internal_attr_numf(tag, tlen, "y1", 0.0F);
704 g->x2 = internal_attr_numf(tag, tlen, "x2", s_svg_unit);
705 g->y2 = internal_attr_numf(tag, tlen, "y2", 0.0F);
706 }
707 const char* endtag = is_rad ? "</radialGradient" : "</linearGradient";
708 const size_t endpos = priv_ra8_svgp_find_ci(s, len, close, endtag);
709 const size_t stopend = (endpos < len) ? endpos : len;
710 internal_parse_stops(s, close, stopend, g);
711 if (g->nstops > 0U) {
712 gs->n += 1;
713 }
714 from = (endpos < len) ? (endpos + 1U) : len;
715 }
716}
717
718/* ===========================================================================
719 * Public API
720 * ===========================================================================
721 */
722
723bool ra8_svg_is_svg(const uint8_t* bytes, size_t len)
724{
725 if ((bytes == nullptr) || (len == 0U)) {
726 return false;
727 }
728 size_t i = 0U;
729 /* Skip a UTF-8 BOM if present. */
730 if ((len >= (size_t)k_svg_bom_len) && (bytes[0] == (uint8_t)k_svg_bom0) &&
731 (bytes[1] == (uint8_t)k_svg_bom1) && (bytes[2] == (uint8_t)k_svg_bom2)) {
732 i = (size_t)k_svg_bom_len;
733 }
734 while ((i < len) && priv_ra8_svgp_ws((char)bytes[i])) {
735 ++i;
736 }
737 return priv_ra8_svgp_starts_ci(bytes, len, i, "<?xml") ||
738 priv_ra8_svgp_starts_ci(bytes, len, i, "<svg");
739}
740
741ra8_err_t ra8_svg_image_href(const uint8_t* svg, size_t len, size_t* out_off, size_t* out_len)
742{
743 if ((svg == nullptr) || (out_off == nullptr) || (out_len == nullptr)) {
744 return k_ra8_err_null_ptr;
745 }
746 size_t open = 0U;
747 size_t close = 0U;
748 if (!internal_tag_span(svg, len, 0U, "<image", &open, &close)) {
749 return k_ra8_err_not_found;
750 }
751 const uint8_t* tag = &svg[open];
752 const size_t tlen = close - open;
753 size_t voff = 0U;
754 size_t vlen = 0U;
755 if (!priv_ra8_svgp_attr(tag, tlen, "xlink:href", &voff, &vlen) &&
756 !priv_ra8_svgp_attr(tag, tlen, "href", &voff, &vlen)) {
757 return k_ra8_err_not_found;
758 }
759 *out_off = open + voff;
760 *out_len = vlen;
761 return k_ra8_ok;
762}
763
764ra8_err_t ra8_svg_size(const uint8_t* svg, size_t len, int32_t* out_w, int32_t* out_h)
765{
766 if ((svg == nullptr) || (out_w == nullptr) || (out_h == nullptr)) {
767 return k_ra8_err_null_ptr;
768 }
769 size_t open = 0U;
770 size_t close = 0U;
771 if (!internal_tag_span(svg, len, 0U, "<svg", &open, &close)) {
772 return k_ra8_err_not_found;
773 }
774 const uint8_t* tag = &svg[open];
775 const size_t tlen = close - open;
776 int32_t w = priv_ra8_svgp_attr_num(tag, tlen, "width", 0);
777 int32_t h = priv_ra8_svgp_attr_num(tag, tlen, "height", 0);
778 if ((w <= 0) || (h <= 0)) {
779 size_t voff = 0U;
780 size_t vlen = 0U;
781 if (priv_ra8_svgp_attr(tag, tlen, "viewBox", &voff, &vlen)) {
782 int32_t nums[k_svg_viewbox_n] = {};
783 size_t k = 0U;
784 for (size_t n = 0U; n < (size_t)k_svg_viewbox_n; ++n) {
785 nums[n] = priv_ra8_svgp_num(&tag[voff], vlen, &k);
786 }
787 w = nums[2];
788 h = nums[3];
789 }
790 }
791 if ((w <= 0) || (h <= 0)) {
792 return k_ra8_err_not_found;
793 }
794 *out_w = w;
795 *out_h = h;
796 return k_ra8_ok;
797}
798
799ra8_err_t ra8_svg_render(const uint8_t* svg, size_t len, int32_t x, int32_t y, int32_t w, int32_t h)
800{
801 if (svg == nullptr) {
802 return k_ra8_err_null_ptr;
803 }
804 if ((w <= 0) || (h <= 0)) {
806 }
807 svg_xform_t t = {.bx = x,
808 .by = y,
809 .bw = w,
810 .bh = h,
811 .ua = 1.0F,
812 .ub = 0.0F,
813 .uc = 0.0F,
814 .ud = 1.0F,
815 .ue = 0.0F,
816 .uf = 0.0F};
817 svg_grads_t grads = {};
818 internal_scan_grads(svg, len, &grads);
819 t.grads = &grads;
820 internal_read_viewbox(svg, len, &t);
821 internal_draw_shapes(svg, len, &t);
822 return k_ra8_ok;
823}
Annotation-attribute framework macros for ra8-firmware.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
@ k_ra8_err_invalid_arg
Invalid function argument.
Definition ra8_err.h:152
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
@ k_ra8_err_null_ptr
Pointer was NULL where a valid pointer was required.
Definition ra8_err.h:478
@ k_ra8_err_not_found
Requested item not found (lookup / search missed).
Definition ra8_err.h:173
ra8_err_codes_t ra8_err_t
Canonical error-return type used by every ra8-firmware API.
Definition ra8_err.h:546
size_t strlen(const char *s)
Calculate string length.
uint32_t priv_ra8_svgp_paint(const uint8_t *s, size_t len)
Parse an SVG paint value ('rgb'/'rrggbb'/name/'none') to 0x00RRGGBB.
Definition reflow_svg.c:614
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
size_t priv_ra8_svgp_find_ci(const uint8_t *s, size_t len, size_t from, const char *lit)
Find the first case-insensitive occurrence of a literal in a byte span.
Definition reflow_svg.c:166
bool priv_ra8_svgp_starts_ci(const uint8_t *s, size_t len, size_t at, const char *lit)
Test whether a byte span starts with a literal at a given offset, case-insensitively.
Definition reflow_svg.c:123
int32_t priv_ra8_svgp_attr_num(const uint8_t *s, size_t len, const char *name, int32_t def)
Read an SVG attribute as a signed integer, returning a default when absent.
Definition reflow_svg.c:424
Minimal SVG handling for the reflow ereader (#112).
ra8_err_t ra8_svg_image_href(const uint8_t *svg, size_t len, size_t *out_off, size_t *out_len)
Extract the href of a cover-wrapper <svg><image .../></svg>.
static uint32_t internal_stop_color(const uint8_t *tag, size_t tlen)
Parse a gradient 'stop' element's 'stop-color' attribute.
ra8_err_t ra8_svg_render(const uint8_t *svg, size_t len, int32_t x, int32_t y, int32_t w, int32_t h)
Render an SVG's vector shapes into a box.
static float internal_clamp01(float v)
Clamp a float value to the unit interval [0, 1].
static void internal_xform_with_group(const svg_xform_t *t, svg_utf_t g, svg_xform_t *out)
Copy the base transform t into out and replace the user affine with g.
static void internal_parse_stops(const uint8_t *s, size_t from, size_t end, svg_grad_t *g)
Parse the 'stop' child elements of a gradient definition into g.
static bool internal_elem_at(const uint8_t *s, size_t len, size_t at, const char *name)
Test whether an element keyword begins at s[at] with a valid delimiter.
static int32_t internal_group_open(const uint8_t *s, size_t i, size_t close, const svg_xform_t *t, svg_utf_t *gstk, int32_t gsp)
Handle a 'g' open tag: compose its 'transform=' over the stack top and push.
static void internal_copy_attr_id(const uint8_t *s, size_t len, char *dst)
Copy the 'id' attribute value into dst as a NUL-terminated string.
bool ra8_svg_is_svg(const uint8_t *bytes, size_t len)
Heuristically decide whether bytes are an SVG document.
static bool internal_tag_span(const uint8_t *s, size_t len, size_t from, const char *name, size_t *open, size_t *close)
Locate the tag span [*open, *close) of element name at or after from.
ra8_err_t ra8_svg_size(const uint8_t *svg, size_t len, int32_t *out_w, int32_t *out_h)
Report an SVG's intrinsic size (width/height, else viewBox dimensions).
static void internal_draw_shapes(const uint8_t *s, size_t len, const svg_xform_t *t)
Walk every element in document order and draw the supported shapes.
static float internal_stop_offset(const uint8_t *tag, size_t tlen)
Parse a gradient 'stop' element's 'offset' attribute.
static float internal_attr_numf(const uint8_t *s, size_t len, const char *name, float def)
Read an SVG attribute as a floating-point number, returning a default when absent.
static void internal_read_viewbox(const uint8_t *s, size_t len, svg_xform_t *t)
Fill t's viewBox fields from the SVG root element.
static const float s_svg_unit
Gradient parameter / offset upper clamp.
static void internal_scan_grads(const uint8_t *s, size_t len, svg_grads_t *gs)
Scan the document for gradient definitions and populate gs.
static void internal_dispatch_shape(const uint8_t *s, size_t len, size_t at, size_t close, const svg_xform_t *t)
Dispatch one element tag span to the appropriate shape-drawing function.
Cross-TU surface for the split minimal-SVG subset (#112).
struct svg_grads svg_grads_t
Forward decl: the document's gradient set (defined after svg_xform_t).
void priv_ra8_svgp_draw_circle(const uint8_t *s, size_t len, const svg_xform_t *t)
Draw one SVG 'circle' element using its fill colour or gradient.
void priv_ra8_svgp_draw_polygon(const uint8_t *s, size_t len, const svg_xform_t *t)
Draw one SVG 'polygon' element as a filled polygon.
void priv_ra8_svgp_draw_polyline(const uint8_t *s, size_t len, const svg_xform_t *t)
Draw one SVG 'polyline' element as connected stroke line segments.
float priv_ra8_svgp_numf(const uint8_t *s, size_t len, size_t *i)
Parse one SVG floating-point number at s[*i], skipping separators.
void priv_ra8_svgp_draw_rect(const uint8_t *s, size_t len, const svg_xform_t *t)
Draw one SVG 'rect' element using its fill colour or gradient.
void priv_ra8_svgp_apply_xform(svg_xform_t *t, const uint8_t *tag, size_t tlen)
Compose the 'transform=' attribute from tag onto t's user affine.
void priv_ra8_svgp_draw_line(const uint8_t *s, size_t len, const svg_xform_t *t)
Draw one SVG 'line' element using its stroke colour.
@ k_svg_bom2
UTF-8 BOM byte 2.
@ k_svg_grad_linear
<linearGradient> kind.
@ k_svg_grad_radial
<radialGradient> kind.
@ k_svg_def_fill
SVG default fill is black.
@ k_svg_grad_max
Max gradients defined per document.
@ k_svg_bom0
UTF-8 BOM byte 0.
@ k_svg_no_paint
none / absent paint sentinel.
@ k_svg_viewbox_n
viewBox number count.
@ k_svg_g_depth_max
Max nested <g> transform depth.
@ k_svg_bom_len
UTF-8 BOM length.
@ k_svg_grad_id
Max gradient id length, bytes.
@ k_svg_bom1
UTF-8 BOM byte 1.
@ k_svg_grad_stops
Max colour stops per gradient.
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.
A parsed linear/radial gradient in objectBoundingBox units.
float x2
Linear end x / radial radius.
char id[k_svg_grad_id]
NUL-terminated gradient id (for url()).
uint8_t kind
k_svg_grad_linear / k_svg_grad_radial.
float y1
Linear start y / radial centre y.
float y2
Linear end y (unused for radial).
uint8_t nstops
Number of valid stops.
float x1
Linear start x / radial centre x.
svg_stop_t stops[k_svg_grad_stops]
Colour stops, in document order.
svg_grad_t g[k_svg_grad_max]
The gradient table.
int32_t n
Number of valid gradients (<= k_svg_grad_max).
float off
Stop offset, clamped to [0,1].
uint32_t col
Stop colour, 0x00RRGGBB.
A user transform= as a full 2x3 affine matrix.
float c
Row0 col1 (x shear).
float e
X translate.
float f
Y translate.
float a
Row0 col0 (x scale).
float b
Row1 col0 (y shear).
float d
Row1 col1 (y scale).
SVG-user-space -> framebuffer-box coordinate transform.
float ub
Affine b (y shear; 0 = axis-aligned).
int32_t bw
Box width.
int32_t vx
viewBox min-x.
float ue
Affine e (x translate, user units).
float ud
Affine d (y scale; 1 = identity).
int32_t vy
viewBox min-y.
int32_t vw
viewBox width.
float uf
Affine f (y translate, user units).
int32_t vh
viewBox height.
float uc
Affine c (x shear; 0 = axis-aligned).
float ua
Affine a (x scale; 1 = identity).
int32_t bh
Box height.
const svg_grads_t * grads
Document gradient set, or NULL.