File Coverage

include/litavis_colour.h
Criterion Covered Total %
statement 170 182 93.4
branch 130 188 69.1
condition n/a
subroutine n/a
pod n/a
total 300 370 81.0


line stmt bran cond sub pod time code
1             #ifndef LITAVIS_COLOUR_H
2             #define LITAVIS_COLOUR_H
3              
4             #include "colouring.h"
5              
6             /* ── Colour function names ────────────────────────────────── */
7              
8 72           static int litavis_is_colour_func(const char *name) {
9 122           return (strcmp(name, "lighten") == 0 ||
10 50 100         strcmp(name, "darken") == 0 ||
11 38 100         strcmp(name, "saturate") == 0 ||
12 36 100         strcmp(name, "desaturate") == 0 ||
13 34 100         strcmp(name, "fade") == 0 ||
14 31 50         strcmp(name, "fadein") == 0 ||
15 31 50         strcmp(name, "fadeout") == 0 ||
16 31 100         strcmp(name, "mix") == 0 ||
17 26 100         strcmp(name, "tint") == 0 ||
18 142 100         strcmp(name, "shade") == 0 ||
    100          
19 20 100         strcmp(name, "greyscale") == 0);
20             }
21              
22             /* ── Argument parsing ─────────────────────────────────────── */
23              
24             typedef struct {
25             char **args;
26             int count;
27             } LitavisColourArgs;
28              
29             /* Split comma-separated args, handling nested parens */
30 56           static LitavisColourArgs litavis_parse_colour_args(const char *args_str) {
31             LitavisColourArgs result;
32 56           result.args = (char**)malloc(sizeof(char*) * 8);
33 56 50         if (!result.args) LITAVIS_FATAL("out of memory");
34 56           result.count = 0;
35              
36 56           const char *p = args_str;
37 169 100         while (*p) {
38             /* Skip leading whitespace */
39 170 100         while (*p == ' ' || *p == '\t') p++;
    50          
40 113 50         if (!*p) break;
41              
42 113           const char *start = p;
43 113           int paren_depth = 0;
44 609 100         while (*p) {
45 553 50         if (*p == '(') paren_depth++;
46 553 50         else if (*p == ')') paren_depth--;
47 553 100         else if (*p == ',' && paren_depth == 0) break;
    50          
48 496           p++;
49             }
50              
51             /* Trim trailing whitespace */
52 113           const char *end = p;
53 113 50         while (end > start && (end[-1] == ' ' || end[-1] == '\t'))
    50          
    50          
54 0           end--;
55              
56 113 50         if (end > start && result.count < 8) {
    50          
57 113           int len = (int)(end - start);
58 113           char *arg = (char*)malloc((size_t)(len + 1));
59 113 50         if (!arg) LITAVIS_FATAL("out of memory");
60 113           memcpy(arg, start, (size_t)len);
61 113           arg[len] = '\0';
62 113           result.args[result.count++] = arg;
63             }
64              
65 113 100         if (*p == ',') p++;
66             }
67 56           return result;
68             }
69              
70 56           static void litavis_colour_args_free(LitavisColourArgs *args) {
71             int i;
72 169 100         for (i = 0; i < args->count; i++)
73 113           free(args->args[i]);
74 56           free(args->args);
75 56           args->args = NULL;
76 56           args->count = 0;
77 56           }
78              
79             /* ── Forward declaration for recursion ────────────────────── */
80              
81             static char* litavis_eval_colour_value(const char *value);
82              
83             /* ── Evaluate a single colour function call ───────────────── */
84              
85 56           static char* litavis_eval_colour_func(const char *func_name, const char *args_str) {
86 56           LitavisColourArgs parsed = litavis_parse_colour_args(args_str);
87             colouring_rgba_t result;
88 56           int ok = 0;
89             char buf[32];
90              
91             /* Recursively resolve any nested colour functions in arguments */
92             int i;
93 169 100         for (i = 0; i < parsed.count; i++) {
94 113 50         if (strchr(parsed.args[i], '(')) {
95 0           char *resolved = litavis_eval_colour_value(parsed.args[i]);
96 0           free(parsed.args[i]);
97 0           parsed.args[i] = resolved;
98             }
99             }
100              
101 78 100         if (strcmp(func_name, "lighten") == 0 && parsed.count >= 2) {
    50          
102             colouring_rgba_t c;
103 22 100         if (colouring_parse(parsed.args[0], &c)) {
104 21           double amount = colouring_depercent(parsed.args[1]);
105 21           result = colouring_lighten(c.r, c.g, c.b, c.a, amount, 0);
106 21           ok = 1;
107             }
108 46 100         } else if (strcmp(func_name, "darken") == 0 && parsed.count >= 2) {
    50          
109             colouring_rgba_t c;
110 12 50         if (colouring_parse(parsed.args[0], &c)) {
111 12           double amount = colouring_depercent(parsed.args[1]);
112 12           result = colouring_darken(c.r, c.g, c.b, c.a, amount, 0);
113 12           ok = 1;
114             }
115 24 100         } else if (strcmp(func_name, "saturate") == 0 && parsed.count >= 2) {
    50          
116             colouring_rgba_t c;
117 2 50         if (colouring_parse(parsed.args[0], &c)) {
118 2           double amount = colouring_depercent(parsed.args[1]);
119 2           result = colouring_saturate(c.r, c.g, c.b, c.a, amount, 0);
120 2           ok = 1;
121             }
122 22 100         } else if (strcmp(func_name, "desaturate") == 0 && parsed.count >= 2) {
    50          
123             colouring_rgba_t c;
124 2 50         if (colouring_parse(parsed.args[0], &c)) {
125 2           double amount = colouring_depercent(parsed.args[1]);
126 2           result = colouring_desaturate(c.r, c.g, c.b, c.a, amount, 0);
127 2           ok = 1;
128             }
129 21 100         } else if (strcmp(func_name, "fade") == 0 && parsed.count >= 2) {
    50          
130             colouring_rgba_t c;
131 3 50         if (colouring_parse(parsed.args[0], &c)) {
132 3           double amount = colouring_depercent(parsed.args[1]);
133 3           result = colouring_fade(c.r, c.g, c.b, c.a, amount);
134 3           ok = 1;
135             }
136 15 50         } else if (strcmp(func_name, "fadein") == 0 && parsed.count >= 2) {
    0          
137             colouring_rgba_t c;
138 0 0         if (colouring_parse(parsed.args[0], &c)) {
139 0           double amount = colouring_depercent(parsed.args[1]);
140 0           result = colouring_fadein(c.r, c.g, c.b, c.a, amount, 0);
141 0           ok = 1;
142             }
143 15 50         } else if (strcmp(func_name, "fadeout") == 0 && parsed.count >= 2) {
    0          
144             colouring_rgba_t c;
145 0 0         if (colouring_parse(parsed.args[0], &c)) {
146 0           double amount = colouring_depercent(parsed.args[1]);
147 0           result = colouring_fadeout(c.r, c.g, c.b, c.a, amount, 0);
148 0           ok = 1;
149             }
150 19 100         } else if (strcmp(func_name, "greyscale") == 0 && parsed.count >= 1) {
    50          
151             colouring_rgba_t c;
152 4 50         if (colouring_parse(parsed.args[0], &c)) {
153 4           result = colouring_greyscale(c.r, c.g, c.b, c.a);
154 4           ok = 1;
155             }
156 16 100         } else if (strcmp(func_name, "mix") == 0 && parsed.count >= 2) {
    50          
157             colouring_rgba_t c1, c2;
158 5 50         if (colouring_parse(parsed.args[0], &c1) && colouring_parse(parsed.args[1], &c2)) {
    50          
159 5 50         int weight = parsed.count >= 3 ? atoi(parsed.args[2]) : 50;
160 5           result = colouring_mix(c1, c2, weight);
161 5           ok = 1;
162             }
163 9 100         } else if (strcmp(func_name, "tint") == 0 && parsed.count >= 2) {
    50          
164             colouring_rgba_t c;
165 3 50         if (colouring_parse(parsed.args[0], &c)) {
166 3           int weight = atoi(parsed.args[1]);
167 3           result = colouring_tint(c, weight);
168 3           ok = 1;
169             }
170 3 50         } else if (strcmp(func_name, "shade") == 0 && parsed.count >= 2) {
    50          
171             colouring_rgba_t c;
172 3 50         if (colouring_parse(parsed.args[0], &c)) {
173 3           int weight = atoi(parsed.args[1]);
174 3           result = colouring_shade(c, weight);
175 3           ok = 1;
176             }
177             }
178              
179 56           litavis_colour_args_free(&parsed);
180              
181 56 100         if (ok) {
182 55 100         if (result.a < 1.0) {
183 2           colouring_fmt_rgba(result, buf, sizeof(buf));
184             } else {
185 53           colouring_fmt_hex(result, buf, sizeof(buf), 0);
186             }
187 55           return litavis_strdup(buf);
188             }
189              
190             /* Not a valid colour function call — return original as-is */
191 1           return NULL;
192             }
193              
194             /* ── Evaluate colour functions within a value string ──────── */
195              
196 70           static char* litavis_eval_colour_value(const char *value) {
197             char buf[8192];
198 70           int bpos = 0;
199 70           const char *p = value;
200              
201 272 100         while (*p && bpos < 8100) {
    50          
202             /* Check for a colour function: identifier( */
203 202 100         if (isalpha((unsigned char)*p)) {
204 97           const char *name_start = p;
205 628 50         while (*p && (isalpha((unsigned char)*p) || *p == '_'))
    100          
    50          
206 531           p++;
207 97           int name_len = (int)(p - name_start);
208              
209 97 100         if (*p == '(') {
210             char func_name[64];
211 72 50         if (name_len > 63) name_len = 63;
212 72           memcpy(func_name, name_start, (size_t)name_len);
213 72           func_name[name_len] = '\0';
214              
215 72 100         if (litavis_is_colour_func(func_name)) {
216             /* Find matching close paren */
217 56           const char *args_start = p + 1;
218 56           int depth = 1;
219 56           p++;
220 722 50         while (*p && depth > 0) {
    100          
221 666 50         if (*p == '(') depth++;
222 666 100         else if (*p == ')') depth--;
223 666 100         if (depth > 0) p++;
224             }
225             /* p points at closing ')' */
226 56           int args_len = (int)(p - args_start);
227 56           char *args_str = (char*)malloc((size_t)(args_len + 1));
228 56 50         if (!args_str) LITAVIS_FATAL("out of memory");
229 56           memcpy(args_str, args_start, (size_t)args_len);
230 56           args_str[args_len] = '\0';
231              
232 56           char *result = litavis_eval_colour_func(func_name, args_str);
233 56           free(args_str);
234              
235 56 100         if (result) {
236 55           int rlen = (int)strlen(result);
237 55 50         if (bpos + rlen < 8192) {
238 55           memcpy(buf + bpos, result, (size_t)rlen);
239 55           bpos += rlen;
240             }
241 55           free(result);
242 55 50         if (*p == ')') p++;
243 55           continue;
244             }
245             /* Not a valid colour call — copy original text */
246 1 50         if (bpos + name_len + 1 + args_len + 1 < 8192) {
247 1           memcpy(buf + bpos, name_start, (size_t)name_len);
248 1           bpos += name_len;
249 1           buf[bpos++] = '(';
250 1           memcpy(buf + bpos, args_start, (size_t)args_len);
251 1           bpos += args_len;
252 1           buf[bpos++] = ')';
253             }
254 1 50         if (*p == ')') p++;
255 1           continue;
256             }
257             /* Not a colour function — copy name + continue (don't consume parens) */
258 16 50         if (bpos + name_len < 8192) {
259 16           memcpy(buf + bpos, name_start, (size_t)name_len);
260 16           bpos += name_len;
261             }
262             /* p still points at '(' — let it be copied normally */
263 16           continue;
264             }
265             /* Bare identifier (no parens) — copy it */
266 25 50         if (bpos + name_len < 8192) {
267 25           memcpy(buf + bpos, name_start, (size_t)name_len);
268 25           bpos += name_len;
269             }
270 25           continue;
271             }
272 105           buf[bpos++] = *p++;
273             }
274 70           buf[bpos] = '\0';
275 70           return litavis_strdup(buf);
276             }
277              
278             /* ── Main entry point: resolve all colour functions in AST ── */
279              
280 219           static void litavis_resolve_colours(LitavisAST *ast) {
281             int i, j;
282 793 100         for (i = 0; i < ast->count; i++) {
283 574           LitavisRule *rule = &ast->rules[i];
284 1258 100         for (j = 0; j < rule->prop_count; j++) {
285             /* Only process values that might contain colour functions */
286 684 100         if (strchr(rule->props[j].value, '(')) {
287 70           char *resolved = litavis_eval_colour_value(rule->props[j].value);
288 70 100         if (strcmp(resolved, rule->props[j].value) != 0) {
289 54           free(rule->props[j].value);
290 54           rule->props[j].value = resolved;
291             } else {
292 16           free(resolved);
293             }
294             }
295             }
296             }
297 219           }
298              
299             #endif /* LITAVIS_COLOUR_H */