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