feat(svg): support inline styles (#9423)

This commit is contained in:
André Costa
2026-01-19 08:42:56 +01:00
committed by GitHub
parent 28dbd07412
commit d8ed1d0cc1
5 changed files with 262 additions and 132 deletions
+1
View File
@@ -83,6 +83,7 @@ enum _lv_svg_attr_type_t {
LV_SVG_ATTR_D, LV_SVG_ATTR_D,
LV_SVG_ATTR_PATH_LENGTH, LV_SVG_ATTR_PATH_LENGTH,
LV_SVG_ATTR_XLINK_HREF, LV_SVG_ATTR_XLINK_HREF,
LV_SVG_ATTR_STYLE,
LV_SVG_ATTR_FILL, LV_SVG_ATTR_FILL,
LV_SVG_ATTR_FILL_RULE, LV_SVG_ATTR_FILL_RULE,
LV_SVG_ATTR_FILL_OPACITY, LV_SVG_ATTR_FILL_OPACITY,
+98 -6
View File
@@ -100,6 +100,7 @@ static const struct _lv_svg_attr_map {
{"d", 1, LV_SVG_ATTR_D}, {"d", 1, LV_SVG_ATTR_D},
{"pathLength", 10, LV_SVG_ATTR_PATH_LENGTH}, {"pathLength", 10, LV_SVG_ATTR_PATH_LENGTH},
{"xlink:href", 10, LV_SVG_ATTR_XLINK_HREF}, {"xlink:href", 10, LV_SVG_ATTR_XLINK_HREF},
{"style", 5, LV_SVG_ATTR_STYLE},
{"fill", 4, LV_SVG_ATTR_FILL}, {"fill", 4, LV_SVG_ATTR_FILL},
{"fill-rule", 9, LV_SVG_ATTR_FILL_RULE}, {"fill-rule", 9, LV_SVG_ATTR_FILL_RULE},
{"fill-opacity", 12, LV_SVG_ATTR_FILL_OPACITY}, {"fill-opacity", 12, LV_SVG_ATTR_FILL_OPACITY},
@@ -420,6 +421,22 @@ static bool _is_number_begin(char ch)
return ch != 0 && strchr("0123456789+-.", ch) != NULL; return ch != 0 && strchr("0123456789+-.", ch) != NULL;
} }
static const char * _next_semicolon(const char * str, const char * str_end)
{
while((str < str_end) && *str != ';') {
++str;
}
return str;
}
static const char * _next_colon(const char * str, const char * str_end)
{
while((str < str_end) && *str != ':') {
++str;
}
return str;
}
static const char * _skip_space(const char * str, const char * str_end) static const char * _skip_space(const char * str, const char * str_end)
{ {
while((str < str_end) && isspace((unsigned char) * str)) { while((str < str_end) && isspace((unsigned char) * str)) {
@@ -2091,17 +2108,63 @@ static void _process_anim_attr_values(lv_svg_node_t * node, lv_svg_attr_type_t t
#endif #endif
static void _process_attrs_tag(_lv_svg_parser_t * parser, lv_svg_node_t * node, const _lv_svg_token_t * token) static void create_tokens_from_style_attr(lv_array_t * result, _lv_svg_token_attr_t * tok_attr)
{ {
uint32_t len = lv_array_size(&token->attrs);
for(uint32_t i = 0; i < len; i++) {
_lv_svg_token_attr_t * tok_attr = lv_array_at(&token->attrs, i);
lv_svg_attr_type_t type = _get_svg_attr_type(tok_attr->name_start, tok_attr->name_end); lv_svg_attr_type_t type = _get_svg_attr_type(tok_attr->name_start, tok_attr->name_end);
LV_ASSERT(type == LV_SVG_ATTR_STYLE);
tok_attr->value_start = _skip_space(tok_attr->value_start, tok_attr->value_end);
/*Generate extra tokens from a style attribute (eg: style="fill:none;stroke-width:6;")*/
while(tok_attr->value_end - tok_attr->value_start > 0) {
const char * name_start = tok_attr->value_start;
/*colon separates attribute name from value*/
const char * colon = _next_colon(tok_attr->value_start, tok_attr->value_end);
if(colon == tok_attr->value_end) {
/* No colon found, invalid style property */
break;
}
/* semicolon marks the end of the value*/
const char * semicolon = _next_semicolon(colon, tok_attr->value_end);
const char * value_start = colon + 1;
if(value_start >= semicolon) {
/* Empty value like "fill:;" or "fill:" at end*/
tok_attr->value_start = _skip_space(semicolon + 1, tok_attr->value_end);
continue;
}
_lv_svg_token_attr_t new_attr = {
.name_start = name_start,
.name_end = colon,
.value_start = value_start,
.value_end = semicolon,
};
tok_attr->value_start = _skip_space(semicolon + 1, tok_attr->value_end);
#if LV_USE_SVG_DEBUG
LV_LOG_INFO("'%.*s': '%.*s'\n",
(int)(colon - name_start), name_start,
(int)(semicolon - value_start), value_start);
#endif
lv_array_push_back(result, &new_attr);
}
}
static void _process_attr_tag(_lv_svg_parser_t * parser, lv_svg_node_t * node, _lv_svg_token_attr_t * tok_attr)
{
lv_svg_attr_type_t type = _get_svg_attr_type(tok_attr->name_start, tok_attr->name_end);
/* Style attributes are processed separately and expanded into individual
* property attributes (e.g., style="fill:red;stroke:blue" becomes separate
* fill and stroke attributes). Skip processing the style attribute itself
* since its constituent properties have already been added to the token array */
if(type == LV_SVG_ATTR_STYLE) {
return;
}
tok_attr->value_start = _skip_space(tok_attr->value_start, tok_attr->value_end); tok_attr->value_start = _skip_space(tok_attr->value_start, tok_attr->value_end);
uint32_t value_len = tok_attr->value_end - tok_attr->value_start; uint32_t value_len = tok_attr->value_end - tok_attr->value_start;
if(value_len == 0) { if(value_len == 0) {
continue; // skip empty value attribute return; // skip empty value attribute
} }
if(type == LV_SVG_ATTR_XML_ID || type == LV_SVG_ATTR_ID) { // get xml:id if(type == LV_SVG_ATTR_XML_ID || type == LV_SVG_ATTR_ID) { // get xml:id
@@ -2110,7 +2173,7 @@ static void _process_attrs_tag(_lv_svg_parser_t * parser, lv_svg_node_t * node,
lv_memcpy(str, tok_attr->value_start, value_len); lv_memcpy(str, tok_attr->value_start, value_len);
str[value_len] = '\0'; str[value_len] = '\0';
node->xml_id = str; node->xml_id = str;
continue; return;
} }
switch(type) { switch(type) {
@@ -2229,10 +2292,39 @@ static void _process_attrs_tag(_lv_svg_parser_t * parser, lv_svg_node_t * node,
case LV_SVG_ATTR_DISPLAY: case LV_SVG_ATTR_DISPLAY:
case LV_SVG_ATTR_VISIBILITY: case LV_SVG_ATTR_VISIBILITY:
case LV_SVG_ATTR_TEXT_ANCHOR: case LV_SVG_ATTR_TEXT_ANCHOR:
LV_LOG_USER("Attribute not supported %.*s", (int)(tok_attr->name_end - tok_attr->name_start), tok_attr->name_start);
// not support yet // not support yet
break; break;
} }
}
static void _process_attrs_tag(_lv_svg_parser_t * parser, lv_svg_node_t * node, const _lv_svg_token_t * token)
{
uint32_t len = lv_array_size(&token->attrs);
lv_array_t inline_style_tokens;
lv_array_init(&inline_style_tokens, 0, sizeof(_lv_svg_token_attr_t));
for(uint32_t i = 0; i < len; i++) {
_lv_svg_token_attr_t * tok_attr = lv_array_at(&token->attrs, i);
lv_svg_attr_type_t type = _get_svg_attr_type(tok_attr->name_start, tok_attr->name_end);
/* Expand style attributes into individual property attributes */
if(type == LV_SVG_ATTR_STYLE) {
create_tokens_from_style_attr(&inline_style_tokens, tok_attr);
continue;
} }
_process_attr_tag(parser, node, tok_attr);
}
len = lv_array_size(&inline_style_tokens);
/* Process style-derived attributes last to ensure inline
* style properties override regular attributes*/
for(uint32_t i = 0; i < len; i++) {
_lv_svg_token_attr_t * tok_attr = lv_array_at(&inline_style_tokens, i);
_process_attr_tag(parser, node, tok_attr);
}
lv_array_deinit(&inline_style_tokens);
} }
static bool _process_begin_tag(_lv_svg_parser_t * parser, lv_svg_tag_t tag, const _lv_svg_token_t * token) static bool _process_begin_tag(_lv_svg_parser_t * parser, lv_svg_tag_t tag, const _lv_svg_token_t * token)
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

+37
View File
@@ -219,6 +219,43 @@ void testSvgElement(void)
TEST_ASSERT_EQUAL(lv_array_size(&svg_node_ar->attrs), 1); TEST_ASSERT_EQUAL(lv_array_size(&svg_node_ar->attrs), 1);
lv_svg_node_delete(svg_node_ar); lv_svg_node_delete(svg_node_ar);
} }
void test_inline_styles(void)
{
const char * svg_inline_style =
"<svg width=\"65px\" height=\"65px\" viewBox=\"0 0 65 65\">"
"<path "
"style=\"fill:none;"
"stroke-width:6;"
"stroke-linecap:round;"
"stroke-linejoin:miter;"
"stroke:rgb(0%,0%,0%);"
"stroke-opacity:1;"
"stroke-miterlimit:10;\" "
"d=\"M 12.603125 39.39375 "
"L 21.725 30.271875 "
"M 30.903125 44.3 "
"L 27.565625 31.8375 "
"M 44.303125 30.9 "
"L 31.840625 27.5625 "
"M 39.396875 12.6 "
"L 30.275 21.721875 "
"M 21.096875 7.69375 "
"L 24.434375 20.15625 "
"M 7.696875 21.09375 "
"L 20.15625 24.43125\"/>"
"</svg>";
static lv_image_dsc_t svg_dsc;
svg_dsc.header.magic = LV_IMAGE_HEADER_MAGIC;
svg_dsc.header.w = 65;
svg_dsc.header.h = 65;
svg_dsc.data_size = lv_strlen(svg_inline_style);
svg_dsc.data = (const uint8_t *) svg_inline_style;
lv_obj_t * svg = lv_image_create(lv_screen_active());
lv_image_set_src(svg, &svg_dsc);
lv_obj_center(svg);
TEST_ASSERT_EQUAL_SCREENSHOT("svg_1.png")
}
void testPolylineElement(void) void testPolylineElement(void)
{ {