15#define MIN3(a, b, c) ((a) < (b) ? ((a) < (c) ? (a) : (c)) : ((b) < (c) ? (b) : (c)))
16#define MAX3(a, b, c) ((a) > (b) ? ((a) > (c) ? (a) : (c)) : ((b) > (c) ? (b) : (c)))
26 float r = rgb.
r / 255.0f;
27 float g = rgb.
g / 255.0f;
28 float b = rgb.
b / 255.0f;
30 float max =
MAX3(r, g, b);
31 float min =
MIN3(r, g, b);
32 hsl.
l = (max + min) / 2.0f;
39 hsl.
s = hsl.
l > 0.5f ? d / (2.0f - max - min) : d / (max + min);
42 hsl.
h = (g - b) / d + (g < b ? 6.0f : 0.0f);
43 }
else if (max == g) {
44 hsl.
h = (b - r) / d + 2.0f;
46 hsl.
h = (r - g) / d + 4.0f;
61static float hue2rgb(
float p,
float q,
float t) {
62 if (t < 0.0f) t += 1.0f;
63 if (t > 1.0f) t -= 1.0f;
64 if (t < 1.0f / 6.0f)
return p + (q - p) * 6.0f * t;
65 if (t < 1.0f / 2.0f)
return q;
66 if (t < 2.0f / 3.0f)
return p + (q - p) * (2.0f / 3.0f - t) * 6.0f;
79 float h = fmodf(hsl.
h, 360.0f) / 360.0f;
80 if (h < 0.0f) h += 1.0f;
86 if (s < 0.0f) s = 0.0f;
87 if (s > 1.0f) s = 1.0f;
88 if (l < 0.0f) l = 0.0f;
89 if (l > 1.0f) l = 1.0f;
91 rgb.
r = rgb.
g = rgb.
b = (
unsigned char)roundf(l * 255.0f);
93 float q = l < 0.5f ? l * (1.0f + s) : l + s - l * s;
94 float p = 2.0f * l - q;
95 rgb.
r = (
unsigned char)(roundf(hue2rgb(p, q, h + 1.0f / 3.0f) * 255.0f));
96 rgb.
g = (
unsigned char)(roundf(hue2rgb(p, q, h) * 255.0f));
97 rgb.
b = (
unsigned char)(roundf(hue2rgb(p, q, h - 1.0f / 3.0f) * 255.0f));
107static inline int hex_char_to_val(
char c) {
108 if (c >=
'0' && c <=
'9')
return c -
'0';
109 if (c >=
'a' && c <=
'f')
return c -
'a' + 10;
110 if (c >=
'A' && c <=
'F')
return c -
'A' + 10;
123 if (!hex)
return rgb;
124 if (hex[0] ==
'#') hex++;
125 size_t len = strlen(hex);
127 int r1 = hex_char_to_val(hex[0]);
128 int r2 = hex_char_to_val(hex[1]);
129 int g1 = hex_char_to_val(hex[2]);
130 int g2 = hex_char_to_val(hex[3]);
131 int b1 = hex_char_to_val(hex[4]);
132 int b2 = hex_char_to_val(hex[5]);
133 if (r1 >= 0 && r2 >= 0 && g1 >= 0 && g2 >= 0 && b1 >= 0 && b2 >= 0) {
134 rgb.
r = (
unsigned char)((r1 << 4) | r2);
135 rgb.
g = (
unsigned char)((g1 << 4) | g2);
136 rgb.
b = (
unsigned char)((b1 << 4) | b2);
138 }
else if (len == 3) {
139 int r = hex_char_to_val(hex[0]);
140 int g = hex_char_to_val(hex[1]);
141 int b = hex_char_to_val(hex[2]);
142 if (r >= 0 && g >= 0 && b >= 0) {
143 rgb.
r = (
unsigned char)((r << 4) | r);
144 rgb.
g = (
unsigned char)((g << 4) | g);
145 rgb.
b = (
unsigned char)((b << 4) | b);
158 snprintf(out_hex, 8,
"#%02x%02x%02x", rgb.
r, rgb.
g, rgb.
b);
180static void get_shifted_hex(
cwist_color_rgb base,
float light_shift,
char out[8]) {
182 hsl.
l += light_shift;
183 if (hsl.
l > 1.0f) hsl.
l = 1.0f;
184 if (hsl.
l < 0.0f) hsl.
l = 0.0f;
196 if (!cfg)
return NULL;
199 char p_hex[8], p_hover[8], p_active[8];
200 char s_hex[8], s_hover[8], s_active[8];
211 snprintf(buf,
sizeof(buf),
213 " --color-primary: %s;\n"
214 " --color-primary-hover: %s;\n"
215 " --color-primary-active: %s;\n"
216 " --color-secondary: %s;\n"
217 " --color-secondary-hover: %s;\n"
218 " --color-secondary-active: %s;\n"
220 " --bg-surface: %s;\n"
221 " --text-main: %s;\n"
222 " --text-muted: %s;\n"
223 " --radius-sm: %.1fpx;\n"
224 " --radius-md: %.1fpx;\n"
225 " --radius-lg: %.1fpx;\n"
226 " --space-1: %.1fpx;\n"
227 " --space-2: %.1fpx;\n"
228 " --space-4: %.1fpx;\n"
229 " --space-8: %.1fpx;\n"
231 p_hex, p_hover, p_active, s_hex, s_hover, s_active,
252 const char *utils =
"/* Typography */\n"
253 ".text-primary { color: var(--color-primary); }\n"
254 ".text-main { color: var(--text-main); }\n"
255 ".text-muted { color: var(--text-muted); }\n"
257 "/* Backgrounds & Surfaces */\n"
258 ".bg-body { background-color: var(--bg-body); }\n"
259 ".bg-surface { background-color: var(--bg-surface); }\n"
260 ".bg-primary { background-color: var(--color-primary); color: #fff; }\n"
261 ".bg-primary:hover { background-color: var(--color-primary-hover); }\n"
262 ".bg-primary:active { background-color: var(--color-primary-active); }\n"
266 " display: inline-flex;\n"
267 " align-items: center;\n"
268 " justify-content: center;\n"
269 " padding: var(--space-2) var(--space-4);\n"
270 " border-radius: var(--radius-md);\n"
271 " font-weight: 500;\n"
272 " transition: background-color 0.2s, color 0.2s;\n"
273 " cursor: pointer;\n"
278 " background-color: var(--bg-surface);\n"
279 " border-radius: var(--radius-lg);\n"
280 " padding: var(--space-4);\n"
281 " box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);\n"
295 if (!cfg)
return NULL;
310struct cwist_css_scope_entry {
323 bool ok = cwist_error_is_ok(&err);
334static bool is_css_ident(
const char *s) {
335 if (!s || !*s)
return false;
338 if (!(isalpha((
unsigned char)*p) || *p ==
'_'))
return false;
340 if (!(isalnum((
unsigned char)*p) || *p ==
'_' || *p ==
'-'))
return false;
351static struct cwist_css_scope_entry *scope_find(
const cwist_css_scope *scope,
352 const char *base_class) {
353 for (
size_t i = 0; i < scope->
count; i++) {
354 if (strcmp(scope->
entries[i].base_class, base_class) == 0)
return &scope->
entries[i];
363static struct cwist_css_scope_entry *scope_get_or_add(
cwist_css_scope *scope,
364 const char *base_class) {
365 struct cwist_css_scope_entry *entry = scope_find(scope, base_class);
366 if (entry)
return entry;
370 struct cwist_css_scope_entry *grown = (
struct cwist_css_scope_entry *)
cwist_realloc(
371 scope->
entries, new_capacity *
sizeof(*grown));
372 if (!grown)
return NULL;
379 if (!base_copy || !scoped || !append_ok(scoped, base_class) || !append_ok(scoped,
"-") ||
380 !append_ok(scoped, scope->
suffix)) {
387 entry->base_class = base_copy;
388 entry->scoped_class = scoped;
389 entry->declarations = NULL;
404 const char *name = component_name ? component_name :
"";
408 uint32_t hash = 2166136261u;
409 for (
const unsigned char *p = (
const unsigned char *)name; *p; p++) {
414 snprintf(scope->
suffix,
sizeof(scope->
suffix),
"%08x", (
unsigned int)hash);
428 if (!scope || !scope->
suffix[0] || !is_css_ident(base_class))
return NULL;
429 struct cwist_css_scope_entry *entry = scope_get_or_add(scope, base_class);
430 if (!entry)
return NULL;
432 return entry->scoped_class->data;
445 const char *declarations) {
446 if (!scope || !scope->
suffix[0] || !is_css_ident(base_class) || !declarations)
return -1;
447 if (strpbrk(declarations,
"{}<"))
return -1;
450 if (!copy)
return -1;
451 struct cwist_css_scope_entry *entry = scope_get_or_add(scope, base_class);
457 entry->declarations = copy;
469 if (!scope)
return NULL;
471 if (!css)
return NULL;
474 bool ok = cwist_error_is_ok(&err);
477 for (
size_t i = 0; ok && i < scope->
count; i++) {
478 const struct cwist_css_scope_entry *entry = &scope->
entries[i];
479 if (!entry->used || !entry->declarations)
continue;
480 ok = append_ok(css,
".") && append_ok(css, entry->scoped_class->data) &&
481 append_ok(css,
" { ") && append_ok(css, entry->declarations) && append_ok(css,
" }\n");
498 for (
size_t i = 0; i < scope->
count; i++) {
504 memset(scope, 0,
sizeof(*scope));
512static bool css_is_space(
char c) {
513 return c ==
' ' || c ==
'\t' || c ==
'\n' || c ==
'\r' || c ==
'\f';
521static bool css_is_word(
char c) {
522 return isalnum((
unsigned char)c) || c ==
'-' || c ==
'_' || c ==
'%' || c ==
'.' || c ==
'#' ||
523 c ==
'\\' || (
unsigned char)c >= 0x80;
532static bool css_starts_url(
const char *p) {
533 return (p[0] ==
'u' || p[0] ==
'U') && (p[1] ==
'r' || p[1] ==
'R') &&
534 (p[2] ==
'l' || p[2] ==
'L') && p[3] ==
'(';
548 if (!css)
return NULL;
549 size_t n = strlen(css);
552 if (!out)
return NULL;
555 size_t verbatim_end = 0;
556 bool pending_space =
false;
561 if (c ==
'/' && css[i + 1] ==
'*') {
562 const char *close = strstr(css + i + 2,
"*/");
563 size_t stop = close ? (size_t)(close - css) + 2 : n;
564 if (css[i + 2] ==
'!') {
565 if (pending_space && o > 0) out[o++] =
' ';
566 pending_space =
false;
567 memcpy(out + o, css + i, stop - i);
570 }
else if (o > 0 && stop < n && css_is_word(out[o - 1]) && css_is_word(css[stop])) {
572 pending_space =
true;
577 if (css_is_space(c)) {
578 pending_space =
true;
584 bool after_structural = o > 0 && o != verbatim_end && strchr(
"{};,:>(", out[o - 1]);
585 bool before_structural = strchr(
"{};,>)", c) != NULL;
586 if (o > 0 && !after_structural && !before_structural) out[o++] =
' ';
587 pending_space =
false;
590 if (c ==
'"' || c ==
'\'') {
592 while (j < n && css[j] != c) {
593 j += (css[j] ==
'\\' && j + 1 < n) ? 2 : 1;
595 size_t stop = j < n ? j + 1 : n;
596 memcpy(out + o, css + i, stop - i);
603 size_t stop = i + 1 < n ? i + 2 : n;
604 memcpy(out + o, css + i, stop - i);
610 if (css_starts_url(css + i) && (o == 0 || !css_is_word(out[o - 1]))) {
612 while (j < n && css_is_space(css[j])) j++;
613 if (j < n && css[j] !=
'"' && css[j] !=
'\'') {
615 const char *close = strchr(css + j,
')');
616 size_t stop = close ? (size_t)(close - css) + 1 : n;
617 memcpy(out + o, css + i, stop - i);
624 if (c ==
'}' && o > 0 && o != verbatim_end && out[o - 1] ==
';') o--;
632bool ok = result != NULL;
635 ok = cwist_error_is_ok(&err);
655 if (!parts &&
count > 0)
return NULL;
657 if (!bundle)
return NULL;
660 bool ok = cwist_error_is_ok(&err);
662 for (
size_t i = 0; ok && i <
count; i++) {
663 if (!parts[i])
continue;
664 ok = append_ok(bundle, parts[i]) && append_ok(bundle,
"\n");
670 if (!minify)
return bundle;
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
void * cwist_realloc(void *ptr, size_t new_size)
Resize an existing allocation (libttak tracked).
char * cwist_strdup(const char *src)
Duplicate a string using libttak-backed storage.
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
cwist_sstring * cwist_sstring_create(void)
Create a new sstring instance.
cwist_error_t cwist_sstring_append(cwist_sstring *str, const char *data)
Append a C string.
cwist_error_t cwist_sstring_assign_len(cwist_sstring *str, const char *data, size_t len)
Assign raw bytes with length.
cwist_error_t cwist_sstring_assign(cwist_sstring *str, const char *data)
Assign a C string.
void cwist_sstring_destroy(cwist_sstring *str)
Destroy an sstring instance.
cwist_sstring * cwist_css_bundle(const char *const *parts, size_t count, bool minify)
Concatenate CSS parts into one bundle, optionally minified.
cwist_sstring * cwist_css_generate_stylesheet(const cwist_css_config *cfg)
Generate a complete stylesheet (variables plus utility classes).
const char * cwist_css_scope_class(cwist_css_scope *scope, const char *base_class)
Map a base class to its scoped class name, marking it as used.
int cwist_css_scope_add_rule(cwist_css_scope *scope, const char *base_class, const char *declarations)
Attach CSS declarations to a scoped class.
cwist_sstring * cwist_css_generate_variables(const cwist_css_config *cfg)
Generate the CSS custom-property block (:root variables) for a config.
void cwist_css_config_init(cwist_css_config *cfg)
Initialize a CSS configuration with default values.
cwist_color_rgb cwist_color_hex_to_rgb(const char *hex)
Parse a hexadecimal color string into an RGB color.
cwist_sstring * cwist_css_scope_generate_stylesheet(const cwist_css_scope *scope)
Emit the stylesheet for all used scope entries that have declarations.
cwist_color_rgb cwist_color_hsl_to_rgb(cwist_color_hsl hsl)
Convert an HSL color to the RGB color space.
cwist_sstring * cwist_css_minify(const char *css)
Minify CSS source by stripping comments and collapsing whitespace.
void cwist_color_rgb_to_hex(cwist_color_rgb rgb, char out_hex[8])
Format an RGB color as a lowercase "#rrggbb" hex string.
void cwist_css_scope_init(cwist_css_scope *scope, const char *component_name)
Initialize a CSS scoping context, deriving a deterministic class suffix.
cwist_sstring * cwist_css_generate_utility_classes(const cwist_css_config *cfg)
Generate the utility/component CSS classes that reference the theme variables.
cwist_color_hsl cwist_color_rgb_to_hsl(cwist_color_rgb rgb)
Convert an RGB color to the HSL color space.
void cwist_css_scope_destroy(cwist_css_scope *scope)
Destroy a CSS scoping context and free all owned resources.
Dynamic CSS Synthesis and Color Mathematics.
HSL Color representation for dynamic manipulation.
float s
Saturation percentage (0.0 to 1.0)
float h
Hue angle in degrees (0.0 to 360.0)
float l
Lightness percentage (0.0 to 1.0)
RGB Color representation.
unsigned char g
Green channel (0-255)
unsigned char b
Blue channel (0-255)
unsigned char r
Red channel (0-255)
Dynamic CSS configuration parameters. Contains base metrics used to extrapolate an entire design syst...
float spacing_base_px
Base spacing unit in pixels (e.g. 4.0)
cwist_color_rgb secondary_color
Secondary/Accent color.
float roundness_px
Base border radius in pixels (e.g. 8.0)
bool is_dark_mode
Flag to trigger dark mode palette generation.
cwist_color_rgb primary_color
Base primary brand color.
Component-scoped class names and the rules attached to them.
size_t capacity
Allocated entries.
char suffix[9]
8 lowercase hex digits + NUL
size_t count
Number of known base classes.
struct cwist_css_scope_entry * entries
Per-class state, private.
Integrated error structure used throughout the system.
char * data
Access directly only when raw handling is necessary.
void cwist_error_dispose(cwist_error_t *err)
Release resources owned by an error value. Currently only CWIST_ERR_JSON owns memory (the cJSON paylo...