1 /*
2 svgread.c
3
4 reads svg gradient files - a list of svg_t structs is
5 returned (since a single svg file may contain several
6 svg gradients)
7
8 J.J. Green 2005, 2015
9 */
10
11 #include <stdlib.h>
12 #include <stdio.h>
13 #include <string.h>
14 #include <assert.h>
15 #include <errno.h>
16 #include <ctype.h>
17
18 #include <libxml/tree.h>
19 #include <libxml/parser.h>
20 #include <libxml/xpath.h>
21 #include <libxml/xpathInternals.h>
22
23 #include "colour.h"
24 #include "stdcol.h"
25 #include "btrace.h"
26
27 #include "svgread.h"
28
29 /* pass libxml error messages to btrace */
30
error_handler(void * user_data,xmlErrorPtr error)31 static void error_handler(void *user_data, xmlErrorPtr error)
32 {
33 size_t n = strlen(error->message);
34 if (n>0)
35 {
36 char copy[n+1], *end;
37 end = memcpy(copy, error->message, n+1) + n - 1;
38 while (end > copy && isspace(*end)) end--;
39 *(end+1) = '\0';
40 btrace("libxml2: %s", copy);
41 }
42 else
43 {
44 btrace("libxml2: empty error message");
45 }
46 }
47
48 static int svg_read_lingrads(xmlNodeSetPtr, svg_list_t*);
49
svg_read(const char * file,svg_list_t * list)50 extern int svg_read(const char* file, svg_list_t* list)
51 {
52 int err = 0;
53 xmlDocPtr doc;
54
55 xmlInitParser();
56 xmlSetStructuredErrorFunc(NULL, error_handler);
57
58 /* Load XML document */
59
60 if ((doc = xmlParseFile(file)) == NULL)
61 {
62 btrace("error: unable to parse file %s", file);
63 err = 1;
64 }
65 else
66 {
67 xmlXPathContextPtr xpc;
68
69 /* Create xpath evaluation context */
70
71 if ((xpc = xmlXPathNewContext(doc)) == NULL)
72 {
73 btrace("error: unable to create new XPath context");
74 err = 1;
75 }
76 else
77 {
78 /*
79 register the svg namespace -- often svg images use the default
80 svg namespace
81
82 <svg xmlns="http://www.w3.org/2000/svg" ...>
83
84 so that each unprefixed child element has an implicit svg: prefix,
85 and this needs to be accounted for in the xpath specification.
86
87 This means we need to register the namespace with the xpath
88 context.
89 */
90
91 const unsigned char
92 prefix[] = "svg",
93 href[] = "http://www.w3.org/2000/svg";
94
95 if (xmlXPathRegisterNs(xpc, prefix, href) != 0)
96 {
97 btrace("namespace error for %s:%s", prefix, href);
98 err = 1;
99 }
100 else
101 {
102 /*
103 handle both linearGradient nodes in the svg namespace (common
104 with images containing gradients) and in no namespace (usual
105 in stand-alone gradients).
106
107 in fact we do radialGradient at the same time since all we
108 are looking for are the stops
109 */
110
111 const xmlChar xpe[] =
112 "//linearGradient | "
113 "//svg:linearGradient | "
114 "//radialGradient | "
115 "//svg:radialGradient";
116
117 xmlXPathObjectPtr xpo;
118
119 /* evaluate xpath expression */
120
121 if ((xpo = xmlXPathEvalExpression(xpe, xpc)) == NULL)
122 {
123 btrace("unable to evaluate xpath expression %s", xpe);
124 err = 1;
125 }
126 else
127 {
128 /* process results */
129
130 err = svg_read_lingrads(xpo->nodesetval, list);
131
132 xmlXPathFreeObject(xpo);
133 }
134 }
135 xmlXPathFreeContext(xpc);
136 }
137 xmlFreeDoc(doc);
138 }
139
140 xmlCleanupParser();
141
142 return err;
143 }
144
145 static int svg_read_lingrad(xmlNodePtr, svg_t*);
146
147 /*
148 the first argument is a list of pointers to svg linearGradient
149 nodes, the second our svg_list_t struct.
150
151 we traverse the list, check that the linearGradient has an id
152 attribute (it must) and if so fetch an svg_t from the svg_list_t
153 and call svg_read_lingrad()
154 */
155
svg_read_lingrads(xmlNodeSetPtr nodes,svg_list_t * list)156 static int svg_read_lingrads(xmlNodeSetPtr nodes, svg_list_t* list)
157 {
158 int size, i;
159
160 size = (nodes ? nodes->nodeNr : 0);
161
162 for (i = 0 ; i<size ; ++i)
163 {
164 int err;
165 xmlChar *id, *href;
166 xmlNodePtr cur;
167 svg_t *svg;
168
169 cur = nodes->nodeTab[i];
170
171 assert(cur);
172
173 if (cur->type != XML_ELEMENT_NODE)
174 {
175 btrace("bad svg: gradient is not a node!");
176 return 1;
177 }
178
179 /*
180 references are used in gradients to share a set of
181 stops, and since we are only interested in the stops
182 we skip anything with a reference
183 */
184
185 if ((href = xmlGetProp(cur, (unsigned char*)"href")) != NULL)
186 {
187 xmlFree(href);
188 continue;
189 }
190
191 /*
192 just about the only thing we require in the gradient
193 is the name, so we check for it before fetching the
194 svg from the svg_list
195 */
196
197 if ((id = xmlGetProp(cur, (xmlChar *)"id")) == NULL)
198 {
199 btrace("gradient has no id attribute, skipping");
200 continue;
201 }
202
203 if ((svg = svg_list_svg(list)) == NULL)
204 {
205 btrace("failed to get svg object from list");
206 return 1;
207 }
208
209 svg->nodes = NULL;
210
211 /* so we might as well write it to the svg_t */
212
213 if (xmlStrPrintf(svg->name,
214 SVG_NAME_LEN-1,
215 "%s", id) >= SVG_NAME_LEN)
216 {
217 btrace("long gradient name truncated!");
218 }
219
220 xmlFree(id);
221
222 /* now process the gradient */
223
224 err = svg_read_lingrad(cur, svg);
225
226 if (err) return err;
227 }
228
229 return 0;
230 }
231
232 static int parse_style(const char*, rgb_t*, double*);
233 static int parse_offset(const char*, double*);
234 static int parse_colour(char*, rgb_t*, double*);
235 static int parse_opacity(char*, double*);
236
svg_read_lingrad(xmlNodePtr lgrad,svg_t * svg)237 static int svg_read_lingrad(xmlNodePtr lgrad, svg_t* svg)
238 {
239 xmlNode *nodes, *node;
240
241 /*
242 here is where you would read the relevant attributes
243 from the gradient, but we are not interested in them,
244 we just want the nodes.
245 */
246
247 nodes = lgrad->children;
248
249 for (node = nodes ; node ; node = node->next)
250 {
251 xmlNode *stop;
252 xmlChar *offset;
253
254 if (node->type != XML_ELEMENT_NODE) continue;
255
256 if (strcmp((const char*)node->name, "stop") != 0)
257 {
258 btrace("unexpected %s node", node->name);
259 continue;
260 }
261
262 /* we have a stop node */
263
264 stop = node;
265
266 int err = 0;
267
268 /* offset is required */
269
270 if ((offset = xmlGetProp(stop, (const unsigned char*)"offset")) == NULL)
271 {
272 btrace("stop has no offset attribute, skipping");
273 err++;
274 }
275 else
276 {
277 double z, op = 1.0;
278 rgb_t rgb = {0};
279
280 if (parse_offset((char*)offset, &z) != 0)
281 {
282 btrace("failed to parse offset \"%s\"", offset);
283 err++;
284 }
285 else
286 {
287 xmlChar *colour, *opacity, *style;
288
289 if ((style = xmlGetProp(stop, (const unsigned char*)"style")) != NULL)
290 {
291 if (parse_style((char*)style, &rgb, &op) != 0)
292 {
293 btrace("error parsing stop style \"%s\"", style);
294 err++;
295 }
296
297 xmlFree(style);
298 }
299
300 if ((colour = xmlGetProp(stop, (const unsigned char*)"stop-color")) != NULL)
301 {
302 if (parse_colour((char*)colour, &rgb, &op) != 0)
303 {
304 btrace("failed on bad colour : %s", colour);
305 err++;
306 }
307
308 xmlFree(colour);
309 }
310
311 if ((opacity = xmlGetProp(stop, (const unsigned char*)"stop-opacity")) != NULL)
312 {
313 if (parse_opacity((char*)opacity, &op) != 0)
314 {
315 btrace("problem parsing opacity \"%s\"", opacity);
316 err++;
317 }
318
319 xmlFree(opacity);
320 }
321
322 if ( ! err )
323 {
324 svg_stop_t svgstop = { .value = z,
325 .opacity = op,
326 .colour = rgb };
327 if (svg_append(svgstop, svg) != 0)
328 {
329 btrace("failed to insert stop");
330 err++;
331 }
332 }
333 }
334
335 xmlFree(offset);
336
337 if (err)
338 return 1;
339 }
340 }
341
342 return 0;
343 }
344
parse_offset(const char * po,double * z)345 static int parse_offset(const char *po, double *z)
346 {
347 double x;
348
349 x = atof(po);
350
351 *z = ((po[strlen(po)-1] == '%') ? x : x*100);
352
353 return 0;
354 }
355
356 /*
357 for some crackerjack reason loads of applications write
358
359 <stop offset="0%" style="stop-color:white;stop-opacity:1">
360
361 when
362
363 <stop offset="0%" stop-color="white" stop-opacity="1">
364
365 seems the obvious thing to do. ho hum.
366
367 This function writes the values in a style string into its
368 rgb and opacity argument if they are there, but does not touch
369 them otherwise. Neither case is an error.
370
371 We do a simple tokenisation with strtok_r, rather than
372 introduce a dependency on a css parsing library
373 */
374
375 static int parse_style_statement(const char*, rgb_t*, double*);
376
parse_style(const char * style,rgb_t * rgb,double * opacity)377 static int parse_style(const char *style, rgb_t* rgb, double* opacity)
378 {
379 size_t sz = strlen(style);
380 char buf[sz+1], *st, *state;
381
382 strcpy(buf, style);
383
384 if ((st = strtok_r(buf, ";", &state)) != NULL)
385 {
386 if (parse_style_statement(st, rgb, opacity) != 0)
387 {
388 btrace("failed on clause %s", st);
389 return 1;
390 }
391
392 while ((st = strtok_r(NULL, ";", &state)) != NULL)
393 {
394 if (parse_style_statement(st, rgb, opacity) != 0)
395 {
396 btrace("failed on clause %s", st);
397 return 1;
398 }
399 }
400 }
401
402 return 0;
403 }
404
parse_style_statement(const char * stmnt,rgb_t * rgb,double * opacity)405 static int parse_style_statement(const char *stmnt, rgb_t* rgb, double* opacity)
406 {
407 size_t sz = strlen(stmnt);
408 char buf[sz+1], *state, *key;
409
410 strcpy(buf, stmnt);
411
412 if ((key = strtok_r(buf, ":", &state)) != NULL)
413 {
414 char *val;
415
416 if ((val = strtok_r(NULL, ":", &state)) != NULL)
417 {
418 if (strcmp(key, "stop-color") == 0)
419 {
420 if (parse_colour(val, rgb, opacity) != 0)
421 {
422 btrace("stop-colour parse failed on %s [%s]", val, stmnt);
423 return 1;
424 }
425 }
426 else if (strcmp(key, "stop-opacity") == 0)
427 {
428 if (parse_opacity(val, opacity) != 0)
429 {
430 btrace("opacity parse failed on %s", val);
431 return 1;
432 }
433 }
434 }
435 }
436
437 return 0;
438 }
439
440 /* this one will take some work */
441
442 static int parse_colour_numeric1(const char*);
443 static int parse_colour_numeric2(const char*);
444
445 static char* whitespace_trim(char*, int*);
446
parse_colour(char * st,rgb_t * rgb,double * opacity)447 static int parse_colour(char *st, rgb_t *rgb, double *opacity)
448 {
449 const struct stdcol_t *p;
450 double op;
451 int r, g, b;
452 int n = 0;
453
454 if (st == NULL) return 1;
455
456 st = whitespace_trim(st, &n);
457
458 if (n == 0) return 1;
459
460 if (st[0] == '#')
461 {
462 switch (n)
463 {
464 case 4:
465
466 r = parse_colour_numeric1(st+1);
467 g = parse_colour_numeric1(st+2);
468 b = parse_colour_numeric1(st+3);
469
470 break;
471
472 case 7:
473
474 r = parse_colour_numeric2(st+1);
475 g = parse_colour_numeric2(st+3);
476 b = parse_colour_numeric2(st+5);
477
478 break;
479
480 default:
481
482 btrace("bad hex colour %s", st);
483
484 return 1;
485 }
486
487 if (r<0 || b<0 || g<0)
488 {
489 btrace("bad hex colour %s", st);
490
491 return 1;
492 }
493
494 rgb->red = r;
495 rgb->green = g;
496 rgb->blue = b;
497
498 return 0;
499 }
500
501 double c[3];
502
503 if (sscanf(st, "rgb(%lf%%,%lf%%,%lf%%)", c, c+1, c+2) == 3)
504 {
505 for (int i = 0 ; i < 3 ; i++)
506 {
507 if (c[i] < 0.0 || c[i] > 100.0)
508 {
509 btrace("bad percentage (%f%%) in rgb %s", c[i], st);
510 return 1;
511 }
512
513 c[i] /= 100.0;
514 }
515
516 return rgbD_to_rgb(c, rgb);
517 }
518
519 if (sscanf(st, "rgb(%i,%i,%i)", &r, &g, &b) == 3)
520 {
521 rgb->red = r;
522 rgb->green = g;
523 rgb->blue = b;
524
525 return 0;
526 }
527
528 /*
529 Apparently not yet fully standardized (though in alignment with the color
530 syntax in CSS3) which works in Firefox, Opera, Chrome and is occasionally
531 seen in the wild
532 */
533
534 if (sscanf(st, "rgba(%i,%i,%i,%lf)", &r, &g, &b, &op) == 4)
535 {
536 if ((op < 0) || (op > 1))
537 {
538 btrace("bad opacity value %.4lf", op);
539 return 1;
540 }
541
542 rgb->red = r;
543 rgb->green = g;
544 rgb->blue = b;
545
546 *opacity = op;
547
548 return 0;
549 }
550
551 if ((p = stdcol(st)) != NULL)
552 {
553 rgb->red = p->r;
554 rgb->green = p->g;
555 rgb->blue = p->b;
556
557 *opacity = (1.0 - p->t);
558
559 return 0;
560 }
561
562 return 1;
563 }
564
whitespace_trim(char * st,int * pn)565 static char* whitespace_trim(char* st, int* pn)
566 {
567 int i, n = strlen(st);
568
569 for (i=0 ; i<n ; i++)
570 {
571 switch (st[i])
572 {
573 case ' ':
574 case '\t':
575 case '\n':
576 break;
577 default:
578 *pn = n;
579
580 return st + i;
581 }
582 }
583
584 /* all is whitespace */
585
586 *pn = 0;
587
588 return st+n;
589 }
590
591
592 static int parse_hex(char);
593
parse_colour_numeric1(const char * st)594 static int parse_colour_numeric1(const char* st)
595 {
596 int p;
597
598 if ((p = parse_hex(st[0])) < 0) return -1;
599
600 return p*17;
601 }
602
parse_colour_numeric2(const char * st)603 static int parse_colour_numeric2(const char* st)
604 {
605 int p, q, v;
606
607 p = parse_hex(st[0]);
608 q = parse_hex(st[1]);
609
610 if (p<0 || q<0) return -1;
611
612 v = p*16+q;
613
614 return v;
615 }
616
parse_hex(char c)617 static int parse_hex(char c)
618 {
619 if ('0' <= c && c <= '9') return c - '0';
620 if ('a' <= c && c <= 'f') return c - 'a' + 10;
621 if ('A' <= c && c <= 'F') return c - 'A' + 10;
622
623 return -1;
624 }
625
parse_opacity(char * st,double * opacity)626 static int parse_opacity(char *st, double *opacity)
627 {
628 if (st == NULL) return 1;
629
630 errno = 0;
631
632 *opacity = strtod(st, NULL);
633
634 if (errno) return 1;
635
636 return 0;
637 }
638