File Coverage

src/pdfmake_color_mgmt.c
Criterion Covered Total %
statement 112 285 39.3
branch 24 96 25.0
condition n/a
subroutine n/a
pod n/a
total 136 381 35.7


line stmt bran cond sub pod time code
1             /*
2             * pdfmake_color_mgmt.c — Color management.
3             *
4             * §8.6 Color Spaces, §14.11.5 Output Intents
5             */
6              
7             #include "pdfmake_color_mgmt.h"
8             #include "pdfmake_page.h"
9             #include "pdfmake_filter.h"
10             #include
11             #include
12             #include
13             #include
14              
15             /* ── Device color space singletons ─────────────────────── */
16              
17             static pdfmake_colorspace_t _cs_gray = {
18             .family = PDFMAKE_CS_DEVICE_GRAY,
19             .components = 1,
20             .name = "DeviceGray",
21             .obj_num = 0,
22             };
23              
24             static pdfmake_colorspace_t _cs_rgb = {
25             .family = PDFMAKE_CS_DEVICE_RGB,
26             .components = 3,
27             .name = "DeviceRGB",
28             .obj_num = 0,
29             };
30              
31             static pdfmake_colorspace_t _cs_cmyk = {
32             .family = PDFMAKE_CS_DEVICE_CMYK,
33             .components = 4,
34             .name = "DeviceCMYK",
35             .obj_num = 0,
36             };
37              
38 0           pdfmake_colorspace_t *pdfmake_cs_device_gray(void) { return &_cs_gray; }
39 0           pdfmake_colorspace_t *pdfmake_cs_device_rgb(void) { return &_cs_rgb; }
40 0           pdfmake_colorspace_t *pdfmake_cs_device_cmyk(void) { return &_cs_cmyk; }
41              
42             /* ── CIE-based ─────────────────────────────────────────── */
43              
44 0           pdfmake_colorspace_t *pdfmake_cs_cal_gray(
45             pdfmake_arena_t *arena, const double wp[3],
46             const double *bp, double gamma_val)
47             {
48 0           pdfmake_colorspace_t *cs = calloc(1, sizeof(*cs));
49 0 0         if (!cs) return NULL;
50             (void)arena;
51 0           cs->family = PDFMAKE_CS_CAL_GRAY;
52 0           cs->components = 1;
53 0           memcpy(cs->white_point, wp, 3 * sizeof(double));
54 0 0         if (bp) memcpy(cs->black_point, bp, 3 * sizeof(double));
55 0           cs->gamma[0] = gamma_val;
56 0           return cs;
57             }
58              
59 7           pdfmake_colorspace_t *pdfmake_cs_cal_rgb(
60             pdfmake_arena_t *arena, const double wp[3],
61             const double *bp, const double gamma[3], const double matrix[9])
62             {
63 7           pdfmake_colorspace_t *cs = calloc(1, sizeof(*cs));
64 7 50         if (!cs) return NULL;
65             (void)arena;
66 7           cs->family = PDFMAKE_CS_CAL_RGB;
67 7           cs->components = 3;
68 7           memcpy(cs->white_point, wp, 3 * sizeof(double));
69 7 50         if (bp) memcpy(cs->black_point, bp, 3 * sizeof(double));
70 7           memcpy(cs->gamma, gamma, 3 * sizeof(double));
71 7           memcpy(cs->matrix, matrix, 9 * sizeof(double));
72 7           return cs;
73             }
74              
75 0           pdfmake_colorspace_t *pdfmake_cs_lab(
76             pdfmake_arena_t *arena, const double wp[3],
77             const double *bp, const double range[4])
78             {
79 0           pdfmake_colorspace_t *cs = calloc(1, sizeof(*cs));
80 0 0         if (!cs) return NULL;
81             (void)arena;
82 0           cs->family = PDFMAKE_CS_LAB;
83 0           cs->components = 3;
84 0           memcpy(cs->white_point, wp, 3 * sizeof(double));
85 0 0         if (bp) memcpy(cs->black_point, bp, 3 * sizeof(double));
86 0           memcpy(cs->range, range, 4 * sizeof(double));
87 0           return cs;
88             }
89              
90             /* ── ICC-based ─────────────────────────────────────────── */
91              
92 0           pdfmake_colorspace_t *pdfmake_cs_icc_from_data(
93             pdfmake_arena_t *arena, const uint8_t *data, size_t len, int components)
94             {
95             pdfmake_colorspace_t *cs;
96 0 0         if (!data || len == 0) return NULL;
    0          
97             (void)arena;
98 0           cs = calloc(1, sizeof(*cs));
99 0 0         if (!cs) return NULL;
100 0           cs->family = PDFMAKE_CS_ICC_BASED;
101 0           cs->components = components;
102 0           cs->icc_data = malloc(len);
103 0 0         if (!cs->icc_data) { free(cs); return NULL; }
104 0           memcpy(cs->icc_data, data, len);
105 0           cs->icc_data_len = len;
106 0           switch (components) {
107 0           case 1: strncpy(cs->icc_alt, "DeviceGray", sizeof(cs->icc_alt)); break;
108 0           case 3: strncpy(cs->icc_alt, "DeviceRGB", sizeof(cs->icc_alt)); break;
109 0           case 4: strncpy(cs->icc_alt, "DeviceCMYK", sizeof(cs->icc_alt)); break;
110             }
111 0           return cs;
112             }
113              
114 0           pdfmake_colorspace_t *pdfmake_cs_icc_from_path(
115             pdfmake_arena_t *arena, const char *path, int components)
116             {
117 0           FILE *fp = fopen(path, "rb");
118             long len;
119             uint8_t *buf;
120             pdfmake_colorspace_t *cs;
121 0 0         if (!fp) return NULL;
122 0           fseek(fp, 0, SEEK_END);
123 0           len = ftell(fp);
124 0 0         if (len < 0) { fclose(fp); return NULL; }
125 0           rewind(fp);
126 0           buf = malloc((size_t)len);
127 0 0         if (!buf) { fclose(fp); return NULL; }
128 0           fread(buf, 1, (size_t)len, fp);
129 0           fclose(fp);
130 0           cs = pdfmake_cs_icc_from_data(arena, buf, (size_t)len, components);
131 0           free(buf);
132 0           return cs;
133             }
134              
135             /* Minimal sRGB ICC profile (D65 white point, 2.2 gamma).
136             * We use CalRGB as a fallback since embedding a real ICC profile
137             * would require a 3KB+ binary blob. */
138 7           pdfmake_colorspace_t *pdfmake_cs_srgb(pdfmake_arena_t *arena) {
139 7           double wp[3] = {0.9505, 1.0, 1.089};
140 7           double gamma[3] = {2.2, 2.2, 2.2};
141 7           double matrix[9] = {
142             0.4124, 0.2126, 0.0193,
143             0.3576, 0.7152, 0.1192,
144             0.1805, 0.0722, 0.9505
145             };
146 7           return pdfmake_cs_cal_rgb(arena, wp, NULL, gamma, matrix);
147             }
148              
149             /* ── Separation ────────────────────────────────────────── */
150              
151 6           pdfmake_colorspace_t *pdfmake_cs_separation(
152             pdfmake_arena_t *arena, const char *spot_name,
153             double c, double m, double y, double k)
154             {
155             pdfmake_colorspace_t *cs;
156             (void)arena;
157 6           cs = calloc(1, sizeof(*cs));
158 6 50         if (!cs) return NULL;
159 6           cs->family = PDFMAKE_CS_SEPARATION;
160 6           cs->components = 1;
161 6           strncpy(cs->name, spot_name, sizeof(cs->name) - 1);
162 6           cs->tint_cmyk[0] = c;
163 6           cs->tint_cmyk[1] = m;
164 6           cs->tint_cmyk[2] = y;
165 6           cs->tint_cmyk[3] = k;
166 6           return cs;
167             }
168              
169             /* ── Indexed ───────────────────────────────────────────── */
170              
171 0           pdfmake_colorspace_t *pdfmake_cs_indexed(
172             pdfmake_arena_t *arena, pdfmake_colorspace_t *base,
173             int max_index, const uint8_t *palette, size_t palette_len)
174             {
175             pdfmake_colorspace_t *cs;
176             (void)arena;
177 0           cs = calloc(1, sizeof(*cs));
178 0 0         if (!cs) return NULL;
179 0           cs->family = PDFMAKE_CS_INDEXED;
180 0           cs->components = 1;
181 0           cs->base = base;
182 0           cs->max_index = max_index;
183 0           cs->palette = malloc(palette_len);
184 0 0         if (!cs->palette) { free(cs); return NULL; }
185 0           memcpy(cs->palette, palette, palette_len);
186 0           cs->palette_len = palette_len;
187 0           return cs;
188             }
189              
190             /* ── Writing ───────────────────────────────────────────── */
191              
192 7           uint32_t pdfmake_cs_write(pdfmake_colorspace_t *cs, pdfmake_doc_t *doc) {
193             pdfmake_arena_t *arena;
194             uint32_t k;
195              
196 7 50         if (!cs || !doc) return 0;
    50          
197 7 50         if (cs->obj_num) return cs->obj_num;
198              
199 7           arena = pdfmake_doc_arena(doc);
200              
201 7           switch (cs->family) {
202 0           case PDFMAKE_CS_DEVICE_GRAY:
203             case PDFMAKE_CS_DEVICE_RGB:
204             case PDFMAKE_CS_DEVICE_CMYK:
205             /* Device spaces are names, not objects */
206 0           return 0;
207              
208 0           case PDFMAKE_CS_CAL_GRAY: {
209 0           pdfmake_obj_t arr = pdfmake_array_new(arena);
210             pdfmake_obj_t dict;
211             pdfmake_obj_t wp;
212             int i;
213 0           pdfmake_array_push(arena, &arr, pdfmake_name_cstr(arena, "CalGray"));
214 0           dict = pdfmake_dict_new(arena);
215 0           wp = pdfmake_array_new(arena);
216 0 0         for (i = 0; i < 3; i++)
217 0           pdfmake_array_push(arena, &wp, pdfmake_real(cs->white_point[i]));
218 0           k = pdfmake_arena_intern_name(arena, "WhitePoint", 10);
219 0           pdfmake_dict_set(arena, &dict, k, wp);
220 0           k = pdfmake_arena_intern_name(arena, "Gamma", 5);
221 0           pdfmake_dict_set(arena, &dict, k, pdfmake_real(cs->gamma[0]));
222 0           pdfmake_array_push(arena, &arr, dict);
223 0           cs->obj_num = pdfmake_doc_add(doc, arr);
224 0           return cs->obj_num;
225             }
226              
227 4           case PDFMAKE_CS_CAL_RGB: {
228 4           pdfmake_obj_t arr = pdfmake_array_new(arena);
229             pdfmake_obj_t dict;
230             pdfmake_obj_t wp;
231             pdfmake_obj_t gm;
232             pdfmake_obj_t mx;
233             int i;
234 4           pdfmake_array_push(arena, &arr, pdfmake_name_cstr(arena, "CalRGB"));
235 4           dict = pdfmake_dict_new(arena);
236 4           wp = pdfmake_array_new(arena);
237 4           gm = pdfmake_array_new(arena);
238 4           mx = pdfmake_array_new(arena);
239 16 100         for (i = 0; i < 3; i++) {
240 12           pdfmake_array_push(arena, &wp, pdfmake_real(cs->white_point[i]));
241 12           pdfmake_array_push(arena, &gm, pdfmake_real(cs->gamma[i]));
242             }
243 40 100         for (i = 0; i < 9; i++)
244 36           pdfmake_array_push(arena, &mx, pdfmake_real(cs->matrix[i]));
245 4           k = pdfmake_arena_intern_name(arena, "WhitePoint", 10);
246 4           pdfmake_dict_set(arena, &dict, k, wp);
247 4           k = pdfmake_arena_intern_name(arena, "Gamma", 5);
248 4           pdfmake_dict_set(arena, &dict, k, gm);
249 4           k = pdfmake_arena_intern_name(arena, "Matrix", 6);
250 4           pdfmake_dict_set(arena, &dict, k, mx);
251 4           pdfmake_array_push(arena, &arr, dict);
252 4           cs->obj_num = pdfmake_doc_add(doc, arr);
253 4           return cs->obj_num;
254             }
255              
256 0           case PDFMAKE_CS_LAB: {
257 0           pdfmake_obj_t arr = pdfmake_array_new(arena);
258             pdfmake_obj_t dict;
259             pdfmake_obj_t wp;
260             pdfmake_obj_t rng;
261             int i;
262 0           pdfmake_array_push(arena, &arr, pdfmake_name_cstr(arena, "Lab"));
263 0           dict = pdfmake_dict_new(arena);
264 0           wp = pdfmake_array_new(arena);
265 0           rng = pdfmake_array_new(arena);
266 0 0         for (i = 0; i < 3; i++)
267 0           pdfmake_array_push(arena, &wp, pdfmake_real(cs->white_point[i]));
268 0 0         for (i = 0; i < 4; i++)
269 0           pdfmake_array_push(arena, &rng, pdfmake_real(cs->range[i]));
270 0           k = pdfmake_arena_intern_name(arena, "WhitePoint", 10);
271 0           pdfmake_dict_set(arena, &dict, k, wp);
272 0           k = pdfmake_arena_intern_name(arena, "Range", 5);
273 0           pdfmake_dict_set(arena, &dict, k, rng);
274 0           pdfmake_array_push(arena, &arr, dict);
275 0           cs->obj_num = pdfmake_doc_add(doc, arr);
276 0           return cs->obj_num;
277             }
278              
279 0           case PDFMAKE_CS_ICC_BASED: {
280             /* ICC profile as stream */
281 0           pdfmake_obj_t stream = pdfmake_stream_new(arena);
282             pdfmake_obj_t dict_obj;
283             uint32_t stream_num;
284             pdfmake_obj_t arr;
285 0           pdfmake_stream_set_data(arena, &stream, cs->icc_data, cs->icc_data_len);
286 0           dict_obj.kind = PDFMAKE_DICT;
287 0           dict_obj.as.dict = pdfmake_stream_dict(&stream);
288 0           k = pdfmake_arena_intern_name(arena, "N", 1);
289 0           pdfmake_dict_set(arena, &dict_obj, k, pdfmake_int(cs->components));
290 0 0         if (cs->icc_alt[0]) {
291 0           k = pdfmake_arena_intern_name(arena, "Alternate", 9);
292 0           pdfmake_dict_set(arena, &dict_obj, k, pdfmake_name_cstr(arena, cs->icc_alt));
293             }
294 0           k = pdfmake_arena_intern_name(arena, "Length", 6);
295 0           pdfmake_dict_set(arena, &dict_obj, k, pdfmake_int((int64_t)cs->icc_data_len));
296 0           stream_num = pdfmake_doc_add(doc, stream);
297              
298 0           arr = pdfmake_array_new(arena);
299 0           pdfmake_array_push(arena, &arr, pdfmake_name_cstr(arena, "ICCBased"));
300 0           pdfmake_array_push(arena, &arr, pdfmake_ref(stream_num, 0));
301 0           cs->obj_num = pdfmake_doc_add(doc, arr);
302 0           return cs->obj_num;
303             }
304              
305 3           case PDFMAKE_CS_SEPARATION: {
306             /* [/Separation /Name /DeviceCMYK tint_fn] */
307 3           pdfmake_obj_t arr = pdfmake_array_new(arena);
308             char fn_body[128];
309             pdfmake_obj_t fn_stream;
310             pdfmake_obj_t fn_dict_obj;
311             pdfmake_obj_t domain;
312             pdfmake_obj_t range;
313             int i;
314             uint32_t fn_num;
315 3           pdfmake_array_push(arena, &arr, pdfmake_name_cstr(arena, "Separation"));
316 3           pdfmake_array_push(arena, &arr, pdfmake_name_cstr(arena, cs->name));
317 3           pdfmake_array_push(arena, &arr, pdfmake_name_cstr(arena, "DeviceCMYK"));
318              
319             /* Type 4 PostScript calculator function: { c m y k } scaled by tint */
320 3           snprintf(fn_body, sizeof(fn_body),
321             "{ dup %g mul exch dup %g mul exch dup %g mul exch %g mul }",
322             cs->tint_cmyk[0], cs->tint_cmyk[1], cs->tint_cmyk[2], cs->tint_cmyk[3]);
323              
324 3           fn_stream = pdfmake_stream_new(arena);
325 3           pdfmake_stream_set_data(arena, &fn_stream,
326             (const uint8_t *)fn_body, strlen(fn_body));
327 3           fn_dict_obj.kind = PDFMAKE_DICT;
328 3           fn_dict_obj.as.dict = pdfmake_stream_dict(&fn_stream);
329 3           k = pdfmake_arena_intern_name(arena, "FunctionType", 12);
330 3           pdfmake_dict_set(arena, &fn_dict_obj, k, pdfmake_int(4));
331 3           domain = pdfmake_array_new(arena);
332 3           pdfmake_array_push(arena, &domain, pdfmake_real(0));
333 3           pdfmake_array_push(arena, &domain, pdfmake_real(1));
334 3           k = pdfmake_arena_intern_name(arena, "Domain", 6);
335 3           pdfmake_dict_set(arena, &fn_dict_obj, k, domain);
336 3           range = pdfmake_array_new(arena);
337 15 100         for (i = 0; i < 4; i++) {
338 12           pdfmake_array_push(arena, &range, pdfmake_real(0));
339 12           pdfmake_array_push(arena, &range, pdfmake_real(1));
340             }
341 3           k = pdfmake_arena_intern_name(arena, "Range", 5);
342 3           pdfmake_dict_set(arena, &fn_dict_obj, k, range);
343 3           k = pdfmake_arena_intern_name(arena, "Length", 6);
344 3           pdfmake_dict_set(arena, &fn_dict_obj, k, pdfmake_int((int64_t)strlen(fn_body)));
345              
346 3           fn_num = pdfmake_doc_add(doc, fn_stream);
347 3           pdfmake_array_push(arena, &arr, pdfmake_ref(fn_num, 0));
348              
349 3           cs->obj_num = pdfmake_doc_add(doc, arr);
350 3           return cs->obj_num;
351             }
352              
353 0           case PDFMAKE_CS_INDEXED: {
354 0           pdfmake_obj_t arr = pdfmake_array_new(arena);
355 0           pdfmake_array_push(arena, &arr, pdfmake_name_cstr(arena, "Indexed"));
356             /* Base color space name */
357 0 0         if (cs->base) {
358 0           const char *base_name = "DeviceRGB";
359 0           switch (cs->base->family) {
360 0           case PDFMAKE_CS_DEVICE_GRAY: base_name = "DeviceGray"; break;
361 0           case PDFMAKE_CS_DEVICE_RGB: base_name = "DeviceRGB"; break;
362 0           case PDFMAKE_CS_DEVICE_CMYK: base_name = "DeviceCMYK"; break;
363 0           default: break;
364             }
365 0           pdfmake_array_push(arena, &arr, pdfmake_name_cstr(arena, base_name));
366             } else {
367 0           pdfmake_array_push(arena, &arr, pdfmake_name_cstr(arena, "DeviceRGB"));
368             }
369 0           pdfmake_array_push(arena, &arr, pdfmake_int(cs->max_index));
370 0           pdfmake_array_push(arena, &arr,
371 0           pdfmake_hexstr(arena, cs->palette, cs->palette_len));
372 0           cs->obj_num = pdfmake_doc_add(doc, arr);
373 0           return cs->obj_num;
374             }
375              
376 0           default:
377 0           return 0;
378             }
379             }
380              
381             /* ── Output intent ─────────────────────────────────────── */
382              
383 0           pdfmake_err_t pdfmake_doc_set_output_intent(
384             pdfmake_doc_t *doc,
385             pdfmake_output_intent_type_t type,
386             pdfmake_colorspace_t *dest_profile,
387             const char *condition,
388             const char *info)
389             {
390             pdfmake_arena_t *arena;
391             uint32_t k;
392             const char *subtype;
393             pdfmake_obj_t intent;
394             pdfmake_obj_t *catalog;
395             pdfmake_obj_t intents_arr;
396              
397 0 0         if (!doc || !condition) return PDFMAKE_EINVAL;
    0          
398             /* Must be called after finalize (catalog exists) */
399 0 0         if (!doc->finalized || doc->root_num == 0) return PDFMAKE_EINVAL;
    0          
400              
401 0           arena = pdfmake_doc_arena(doc);
402              
403 0           switch (type) {
404 0           case PDFMAKE_INTENT_GTS_PDFX: subtype = "GTS_PDFX"; break;
405 0           case PDFMAKE_INTENT_GTS_PDFA: subtype = "GTS_PDFA1"; break;
406 0           case PDFMAKE_INTENT_ISO_PDFE: subtype = "ISO_PDFE1"; break;
407 0           default: subtype = "GTS_PDFX"; break;
408             }
409              
410 0           intent = pdfmake_dict_new(arena);
411 0           k = pdfmake_arena_intern_name(arena, "Type", 4);
412 0           pdfmake_dict_set(arena, &intent, k, pdfmake_name_cstr(arena, "OutputIntent"));
413 0           k = pdfmake_arena_intern_name(arena, "S", 1);
414 0           pdfmake_dict_set(arena, &intent, k, pdfmake_name_cstr(arena, subtype));
415 0           k = pdfmake_arena_intern_name(arena, "OutputConditionIdentifier", 25);
416 0           pdfmake_dict_set(arena, &intent, k, pdfmake_str_cstr(arena, condition));
417 0 0         if (info) {
418 0           k = pdfmake_arena_intern_name(arena, "Info", 4);
419 0           pdfmake_dict_set(arena, &intent, k, pdfmake_str_cstr(arena, info));
420             }
421 0           k = pdfmake_arena_intern_name(arena, "RegistryName", 12);
422 0           pdfmake_dict_set(arena, &intent, k, pdfmake_str_cstr(arena, "http://www.color.org"));
423              
424             /* Write ICC profile if provided */
425 0 0         if (dest_profile && dest_profile->family == PDFMAKE_CS_ICC_BASED) {
    0          
426 0           uint32_t profile_num = pdfmake_cs_write(dest_profile, doc);
427 0 0         if (profile_num > 0) {
428 0           k = pdfmake_arena_intern_name(arena, "DestOutputProfile", 17);
429 0           pdfmake_dict_set(arena, &intent, k, pdfmake_ref(profile_num, 0));
430             }
431             }
432              
433             /* /OutputIntents [intent] on catalog */
434 0           catalog = pdfmake_doc_get(doc, doc->root_num);
435 0 0         if (!catalog || catalog->kind != PDFMAKE_DICT) return PDFMAKE_EINVAL;
    0          
436              
437 0           intents_arr = pdfmake_array_new(arena);
438 0           pdfmake_array_push(arena, &intents_arr, intent);
439 0           k = pdfmake_arena_intern_name(arena, "OutputIntents", 13);
440 0           pdfmake_dict_set(arena, catalog, k, intents_arr);
441              
442 0           return PDFMAKE_OK;
443             }
444              
445             /* ── Basic conversion ──────────────────────────────────── */
446              
447 3           void pdfmake_rgb_to_cmyk(double r, double g, double b,
448             double *c, double *m, double *y, double *k_out) {
449 3           double k_val = 1.0 - fmax(fmax(r, g), b);
450 3 50         if (k_val >= 1.0) {
451 0           *c = 0; *m = 0; *y = 0; *k_out = 1.0;
452             } else {
453 3           *c = (1.0 - r - k_val) / (1.0 - k_val);
454 3           *m = (1.0 - g - k_val) / (1.0 - k_val);
455 3           *y = (1.0 - b - k_val) / (1.0 - k_val);
456 3           *k_out = k_val;
457             }
458 3           }
459              
460 4           void pdfmake_cmyk_to_rgb(double c, double m, double y, double k,
461             double *r, double *g, double *b) {
462 4           *r = (1.0 - c) * (1.0 - k);
463 4           *g = (1.0 - m) * (1.0 - k);
464 4           *b = (1.0 - y) * (1.0 - k);
465 4           }
466              
467 0           void pdfmake_gray_to_rgb(double gray, double *r, double *g, double *b) {
468 0           *r = *g = *b = gray;
469 0           }
470              
471 5           int pdfmake_hex_to_rgb(const char *hex, double *r, double *g, double *b) {
472             unsigned int rv, gv, bv;
473             size_t len;
474 5 50         if (!hex) return -1;
475 5 100         if (*hex == '#') hex++;
476 5           len = strlen(hex);
477 5 50         if (len == 3) {
478 0 0         if (sscanf(hex, "%1x%1x%1x", &rv, &gv, &bv) != 3) return -1;
479 0           *r = (rv * 17) / 255.0;
480 0           *g = (gv * 17) / 255.0;
481 0           *b = (bv * 17) / 255.0;
482 5 100         } else if (len == 6) {
483 4 50         if (sscanf(hex, "%2x%2x%2x", &rv, &gv, &bv) != 3) return -1;
484 4           *r = rv / 255.0;
485 4           *g = gv / 255.0;
486 4           *b = bv / 255.0;
487             } else {
488 1           return -1;
489             }
490 4           return 0;
491             }
492              
493             /* ── Cleanup ───────────────────────────────────────────── */
494              
495 13           void pdfmake_cs_free(pdfmake_colorspace_t *cs) {
496 13 50         if (!cs) return;
497             /* Don't free singletons */
498 13 50         if (cs == &_cs_gray || cs == &_cs_rgb || cs == &_cs_cmyk) return;
    50          
    50          
499 13           free(cs->icc_data);
500 13           free(cs->palette);
501 13           free(cs);
502             }