SDL_image: nanosvg.h: add basic style sheet support as adopted by mainstream.

From fe2bfe261a2c953399aa6806ebfcf7966be6cc7a Mon Sep 17 00:00:00 2001
From: Ozkan Sezer <[EMAIL REDACTED]>
Date: Mon, 18 May 2026 17:01:10 +0300
Subject: [PATCH] nanosvg.h: add basic style sheet support as adopted by
 mainstream.

imports mainstream commit c59ff2349dc40967bbc0d840f8be17be21e82c68 authored
by Petar Ivanov: Add support for class attribute with simple class selector
https://github.com/memononen/nanosvg/pull/284
---
 src/nanosvg.h | 152 ++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 148 insertions(+), 4 deletions(-)

diff --git a/src/nanosvg.h b/src/nanosvg.h
index 9c9e8554..f3909094 100644
--- a/src/nanosvg.h
+++ b/src/nanosvg.h
@@ -376,6 +376,7 @@ enum NSVGgradientUnits {
 };
 
 #define NSVG_MAX_DASHES 8
+#define NSVG_MAX_CLASSES 32
 
 enum NSVGunits {
 	NSVG_UNITS_USER,
@@ -448,6 +449,13 @@ typedef struct NSVGattrib
 	char visible;
 } NSVGattrib;
 
+typedef struct NSVGstyleDeclaration
+{
+	char* className;
+	char* propertiesText;
+	struct NSVGstyleDeclaration* next;
+} NSVGstyleDeclaration;
+
 typedef struct NSVGparser
 {
 	NSVGattrib attr[NSVG_MAX_ATTR];
@@ -457,6 +465,7 @@ typedef struct NSVGparser
 	int cpts;
 	NSVGpath* plist;
 	NSVGimage* image;
+	NSVGstyleDeclaration* styles;
 	NSVGgradientData* gradients;
 	NSVGshape* shapesTail;
 	float viewMinx, viewMiny, viewWidth, viewHeight;
@@ -464,6 +473,7 @@ typedef struct NSVGparser
 	float dpi;
 	char pathFlag;
 	char defsFlag;
+	char styleFlag;
 } NSVGparser;
 
 static void nsvg__xformIdentity(float* t)
@@ -660,6 +670,15 @@ static NSVGparser* nsvg__createParser(void)
 	}
 	return NULL;
 }
+static void nsvg__deleteStyles(NSVGstyleDeclaration* style) {
+	while (style) {
+		NSVGstyleDeclaration* next = style->next;
+		free(style->className);
+		free(style->propertiesText);
+		free(style);
+		style = next;
+	}
+}
 
 static void nsvg__deletePaths(NSVGpath* path)
 {
@@ -692,6 +711,7 @@ static void nsvg__deleteGradientData(NSVGgradientData* grad)
 static void nsvg__deleteParser(NSVGparser* p)
 {
 	if (p != NULL) {
+		nsvg__deleteStyles(p->styles);
 		nsvg__deletePaths(p->plist);
 		nsvg__deleteGradientData(p->gradients);
 		nsvgDelete(p->image);
@@ -1787,6 +1807,34 @@ static int nsvg__parseStrokeDashArray(NSVGparser* p, const char* str, float* str
 
 static void nsvg__parseStyle(NSVGparser* p, const char* str);
 
+// Apply any matching class styles for a "class" attribute value. We support only simple class
+// selectors. The class attribute may contain multiple space-separated class names.
+static void nsvg__applyClassStyles(NSVGparser* p, const char* value)
+{
+	const char* cur = value;
+	while (*cur) {
+		const char* classStart;
+		size_t classLen;
+		NSVGstyleDeclaration* style;
+
+		while (*cur && nsvg__isspace(*cur))
+			cur++;
+		if (!*cur)
+			break;
+
+		classStart = cur;
+		while (*cur && !nsvg__isspace(*cur))
+			cur++;
+		classLen = (size_t)(cur - classStart);
+
+		for (style = p->styles; style != NULL; style = style->next) {
+			if (strncmp(style->className, classStart, classLen) == 0 && style->className[classLen] == '\0') {
+				nsvg__parseStyle(p, style->propertiesText);
+			}
+		}
+	}
+}
+
 static int nsvg__parseAttr(NSVGparser* p, const char* name, const char* value)
 {
 	float xform[6];
@@ -1854,6 +1902,8 @@ static int nsvg__parseAttr(NSVGparser* p, const char* name, const char* value)
 	} else if (strcmp(name, "id") == 0) {
 		strncpy(attr->id, value, 63);
 		attr->id[63] = '\0';
+	} else if (strcmp(name, "class") == 0) {
+		nsvg__applyClassStyles(p, value);
 	} else {
 		return 0;
 	}
@@ -2764,13 +2814,15 @@ static void nsvg__startElement(void* ud, const char* el, const char** attr)
 	NSVGparser* p = (NSVGparser*)ud;
 
 	if (p->defsFlag) {
-		// Skip everything but gradients in defs
+		// Skip everything but gradients and styles in defs
 		if (strcmp(el, "linearGradient") == 0) {
 			nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT);
 		} else if (strcmp(el, "radialGradient") == 0) {
 			nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT);
 		} else if (strcmp(el, "stop") == 0) {
 			nsvg__parseGradientStop(p, attr);
+		} else if (strcmp(el, "style") == 0) {
+			p->styleFlag = 1;
 		}
 		return;
 	}
@@ -2818,6 +2870,8 @@ static void nsvg__startElement(void* ud, const char* el, const char** attr)
 		p->defsFlag = 1;
 	} else if (strcmp(el, "svg") == 0) {
 		nsvg__parseSVG(p, attr);
+	} else if (strcmp(el, "style") == 0) {
+		p->styleFlag = 1;
 	}
 }
 
@@ -2831,14 +2885,104 @@ static void nsvg__endElement(void* ud, const char* el)
 		p->pathFlag = 0;
 	} else if (strcmp(el, "defs") == 0) {
 		p->defsFlag = 0;
+	} else if (strcmp(el, "style") == 0) {
+		p->styleFlag = 0;
 	}
 }
 
+static char *nsvg__strndup(const char *s, size_t n)
+{
+	char *result = (char *)malloc(n + 1);
+	if (result == NULL)
+		return NULL;
+
+	memcpy(result, s, n);
+	result[n] = '\0';
+	return result;
+}
+
 static void nsvg__content(void* ud, const char* s)
 {
-	NSVG_NOTUSED(ud);
-	NSVG_NOTUSED(s);
-	// empty
+	NSVGparser* p = (NSVGparser*)ud;
+	if (!p->styleFlag)
+		return;
+
+	// Parse all the styles inside the style block. Each style's content will be later processed using nsvg__parseStyle().
+	// Note: We only support selector lists of simple class selectors (e.g. ".foo, .bar { ... }").
+	while (*s) {
+		NSVGstyleDeclaration* styles[NSVG_MAX_CLASSES];
+		int nstyles = 0;
+		const char* propsStart;
+		const char* propsEnd;
+		int i;
+
+		// 1) Parse the selector list up to '{'. For each simple class selector ('.name'),
+		//    allocate a new NSVGstyleDeclaration into the local staging array. Styles are
+		//    only committed to p->styles in step 3 below, once their propertiesText has
+		//    also been allocated successfully.
+		while (*s && *s != '{') {
+			const char* selStart;
+			const char* selEnd;
+			NSVGstyleDeclaration* style;
+
+			while (*s && (nsvg__isspace(*s) || *s == ','))
+				s++;
+			if (!*s || *s == '{')
+				break;
+
+			selStart = s;
+			while (*s && !nsvg__isspace(*s) && *s != ',' && *s != '{')
+				s++;
+			selEnd = s;
+
+			if (*selStart != '.' || nstyles >= NSVG_MAX_CLASSES)
+				continue; // unsupported selector, or staging array full
+			selStart++; // strip leading '.'
+
+			style = (NSVGstyleDeclaration*)malloc(sizeof(NSVGstyleDeclaration));
+			if (style == NULL)
+				continue;
+			style->className = nsvg__strndup(selStart, (size_t)(selEnd - selStart));
+			if (style->className == NULL) {
+				free(style);
+				continue;
+			}
+			style->propertiesText = NULL;
+			style->next = NULL;
+			styles[nstyles++] = style;
+		}
+		if (!*s) {
+			// No '{' found - discard pending styles and stop.
+			for (i = 0; i < nstyles; i++) {
+				free(styles[i]->className);
+				free(styles[i]);
+			}
+			break;
+		}
+		s++; // advance past '{'
+
+		// 2) Find the end of the properties block (up to '}').
+		propsStart = s;
+		while (*s && *s != '}')
+			s++;
+		propsEnd = s;
+
+		// 3) Allocate propertiesText for each pending style. Commit successful ones to
+		//    p->styles (head-insertion); free any whose allocation fails.
+		for (i = 0; i < nstyles; i++) {
+			styles[i]->propertiesText = nsvg__strndup(propsStart, (size_t)(propsEnd - propsStart));
+			if (styles[i]->propertiesText == NULL) {
+				free(styles[i]->className);
+				free(styles[i]);
+			} else {
+				styles[i]->next = p->styles;
+				p->styles = styles[i];
+			}
+		}
+
+		if (*s)
+			s++; // advance past '}'
+	}
 }
 
 static void nsvg__imageBounds(NSVGparser* p, float* bounds)