]> gitweb.michael.orlitzky.com - libsvgtiny.git/blob - src/svgtiny_gradient.c
9c6e82ee1e591d7029923fc6e6f3d89277aa2d5a
[libsvgtiny.git] / src / svgtiny_gradient.c
1 /*
2 * This file is part of Libsvgtiny
3 * Licensed under the MIT License,
4 * http://opensource.org/licenses/mit-license.php
5 * Copyright 2008 James Bursa <james@semichrome.net>
6 */
7
8 #define _GNU_SOURCE /* for strndup */
9 #include <assert.h>
10 #include <math.h>
11 #include <string.h>
12 #include "svgtiny.h"
13 #include "svgtiny_internal.h"
14
15 #undef GRADIENT_DEBUG
16
17 static svgtiny_code svgtiny_parse_linear_gradient(xmlNode *linear,
18 struct svgtiny_parse_state *state);
19 static float svgtiny_parse_gradient_offset(const char *s);
20 static void svgtiny_path_bbox(float *p, unsigned int n,
21 float *x0, float *y0, float *x1, float *y1);
22 static void svgtiny_invert_matrix(float *m, float *inv);
23
24
25 /**
26 * Find a gradient by id and parse it.
27 */
28
29 void svgtiny_find_gradient(const char *id, struct svgtiny_parse_state *state)
30 {
31 xmlNode *gradient;
32
33 fprintf(stderr, "svgtiny_find_gradient: id \"%s\"\n", id);
34
35 state->linear_gradient_stop_count = 0;
36 state->gradient_x1 = "0%";
37 state->gradient_y1 = "0%";
38 state->gradient_x2 = "100%";
39 state->gradient_y2 = "0%";
40 state->gradient_user_space_on_use = false;
41 state->gradient_transform.a = 1;
42 state->gradient_transform.b = 0;
43 state->gradient_transform.c = 0;
44 state->gradient_transform.d = 1;
45 state->gradient_transform.e = 0;
46 state->gradient_transform.f = 0;
47
48 gradient = svgtiny_find_element_by_id(
49 (xmlNode *) state->document, id);
50 fprintf(stderr, "gradient %p\n", (void *) gradient);
51 if (!gradient) {
52 fprintf(stderr, "gradient \"%s\" not found\n", id);
53 return;
54 }
55
56 fprintf(stderr, "gradient name \"%s\"\n", gradient->name);
57 if (strcmp((const char *) gradient->name, "linearGradient") == 0) {
58 svgtiny_parse_linear_gradient(gradient, state);
59 }
60 }
61
62
63 /**
64 * Parse a <linearGradient> element node.
65 *
66 * http://www.w3.org/TR/SVG11/pservers#LinearGradients
67 */
68
69 svgtiny_code svgtiny_parse_linear_gradient(xmlNode *linear,
70 struct svgtiny_parse_state *state)
71 {
72 unsigned int i = 0;
73 xmlNode *stop;
74 xmlAttr *attr;
75 xmlAttr *href = xmlHasProp(linear, (const xmlChar *) "href");
76 if (href && href->children->content[0] == '#')
77 svgtiny_find_gradient((const char *) href->children->content
78 + 1, state);
79
80 for (attr = linear->properties; attr; attr = attr->next) {
81 const char *name = (const char *) attr->name;
82 const char *content = (const char *) attr->children->content;
83 if (strcmp(name, "x1") == 0)
84 state->gradient_x1 = content;
85 else if (strcmp(name, "y1") == 0)
86 state->gradient_y1 = content;
87 else if (strcmp(name, "x2") == 0)
88 state->gradient_x2 = content;
89 else if (strcmp(name, "y2") == 0)
90 state->gradient_y2 = content;
91 else if (strcmp(name, "gradientUnits") == 0)
92 state->gradient_user_space_on_use =
93 strcmp(content, "userSpaceOnUse") == 0;
94 else if (strcmp(name, "gradientTransform") == 0) {
95 float a = 1, b = 0, c = 0, d = 1, e = 0, f = 0;
96 char *s = strdup(content);
97 if (!s)
98 return svgtiny_OUT_OF_MEMORY;
99 svgtiny_parse_transform(s, &a, &b, &c, &d, &e, &f);
100 free(s);
101 fprintf(stderr, "transform %g %g %g %g %g %g\n",
102 a, b, c, d, e, f);
103 state->gradient_transform.a = a;
104 state->gradient_transform.b = b;
105 state->gradient_transform.c = c;
106 state->gradient_transform.d = d;
107 state->gradient_transform.e = e;
108 state->gradient_transform.f = f;
109 }
110 }
111
112 for (stop = linear->children; stop; stop = stop->next) {
113 float offset = -1;
114 svgtiny_colour color = svgtiny_TRANSPARENT;
115
116 if (stop->type != XML_ELEMENT_NODE)
117 continue;
118 if (strcmp((const char *) stop->name, "stop") != 0)
119 continue;
120
121 for (attr = stop->properties; attr;
122 attr = attr->next) {
123 const char *name = (const char *) attr->name;
124 const char *content =
125 (const char *) attr->children->content;
126 if (strcmp(name, "offset") == 0)
127 offset = svgtiny_parse_gradient_offset(content);
128 else if (strcmp(name, "stop-color") == 0)
129 svgtiny_parse_color(content, &color, state);
130 else if (strcmp(name, "style") == 0) {
131 const char *s;
132 char *value;
133 if ((s = strstr(content, "stop-color:"))) {
134 s += 11;
135 while (*s == ' ')
136 s++;
137 value = strndup(s, strcspn(s, "; "));
138 svgtiny_parse_color(value, &color,
139 state);
140 free(value);
141 }
142 }
143 }
144
145 if (offset != -1 && color != svgtiny_TRANSPARENT) {
146 fprintf(stderr, "stop %g %x\n", offset, color);
147 state->gradient_stop[i].offset = offset;
148 state->gradient_stop[i].color = color;
149 i++;
150 }
151
152 if (i == svgtiny_MAX_STOPS)
153 break;
154 }
155
156 if (i)
157 state->linear_gradient_stop_count = i;
158
159 return svgtiny_OK;
160 }
161
162
163 float svgtiny_parse_gradient_offset(const char *s)
164 {
165 int num_length = strspn(s, "0123456789+-.");
166 const char *unit = s + num_length;
167 float n = atof((const char *) s);
168
169 if (unit[0] == 0)
170 ;
171 else if (unit[0] == '%')
172 n /= 100.0;
173 else
174 return -1;
175
176 if (n < 0)
177 n = 0;
178 if (1 < n)
179 n = 1;
180 return n;
181 }
182
183
184 /**
185 * Add a path with a linear gradient fill to the svgtiny_diagram.
186 */
187
188 svgtiny_code svgtiny_add_path_linear_gradient(float *p, unsigned int n,
189 struct svgtiny_parse_state *state)
190 {
191 struct grad_point {
192 float x, y, r;
193 };
194 float object_x0, object_y0, object_x1, object_y1;
195 float gradient_x0, gradient_y0, gradient_x1, gradient_y1,
196 gradient_dx, gradient_dy;
197 float trans[6];
198 unsigned int steps = 10;
199 float x0 = 0, y0 = 0, x0_trans, y0_trans, r0; /* segment start point */
200 float x1, y1, x1_trans, y1_trans, r1; /* segment end point */
201 /* segment control points (beziers only) */
202 float c0x = 0, c0y = 0, c1x = 0, c1y = 0;
203 float gradient_norm_squared;
204 struct svgtiny_list *pts;
205 float min_r = 1000;
206 unsigned int min_pt = 0;
207 unsigned int j;
208 unsigned int stop_count;
209 unsigned int current_stop;
210 float last_stop_r;
211 float current_stop_r;
212 int red0, green0, blue0, red1, green1, blue1;
213 unsigned int t, a, b;
214
215 /* determine object bounding box */
216 svgtiny_path_bbox(p, n, &object_x0, &object_y0, &object_x1, &object_y1);
217 #ifdef GRADIENT_DEBUG
218 fprintf(stderr, "object bbox: (%g %g) (%g %g)\n",
219 object_x0, object_y0, object_x1, object_y1);
220 #endif
221
222 /* compute gradient vector */
223 fprintf(stderr, "x1 %s, y1 %s, x2 %s, y2 %s\n",
224 state->gradient_x1, state->gradient_y1,
225 state->gradient_x2, state->gradient_y2);
226 if (!state->gradient_user_space_on_use) {
227 gradient_x0 = object_x0 +
228 svgtiny_parse_length(state->gradient_x1,
229 object_x1 - object_x0, *state);
230 gradient_y0 = object_y0 +
231 svgtiny_parse_length(state->gradient_y1,
232 object_y1 - object_y0, *state);
233 gradient_x1 = object_x0 +
234 svgtiny_parse_length(state->gradient_x2,
235 object_x1 - object_x0, *state);
236 gradient_y1 = object_y0 +
237 svgtiny_parse_length(state->gradient_y2,
238 object_y1 - object_y0, *state);
239 } else {
240 gradient_x0 = svgtiny_parse_length(state->gradient_x1,
241 state->viewport_width, *state);
242 gradient_y0 = svgtiny_parse_length(state->gradient_y1,
243 state->viewport_height, *state);
244 gradient_x1 = svgtiny_parse_length(state->gradient_x2,
245 state->viewport_width, *state);
246 gradient_y1 = svgtiny_parse_length(state->gradient_y2,
247 state->viewport_height, *state);
248 }
249 gradient_dx = gradient_x1 - gradient_x0;
250 gradient_dy = gradient_y1 - gradient_y0;
251 #ifdef GRADIENT_DEBUG
252 fprintf(stderr, "gradient vector: (%g %g) => (%g %g)\n",
253 gradient_x0, gradient_y0, gradient_x1, gradient_y1);
254 #endif
255
256 /* show theoretical gradient strips for debugging */
257 /*unsigned int strips = 10;
258 for (unsigned int z = 0; z != strips; z++) {
259 float f0, fd, strip_x0, strip_y0, strip_dx, strip_dy;
260 f0 = (float) z / (float) strips;
261 fd = (float) 1 / (float) strips;
262 strip_x0 = gradient_x0 + f0 * gradient_dx;
263 strip_y0 = gradient_y0 + f0 * gradient_dy;
264 strip_dx = fd * gradient_dx;
265 strip_dy = fd * gradient_dy;
266 fprintf(stderr, "strip %i vector: (%g %g) + (%g %g)\n",
267 z, strip_x0, strip_y0, strip_dx, strip_dy);
268
269 float *p = malloc(13 * sizeof p[0]);
270 if (!p)
271 return svgtiny_OUT_OF_MEMORY;
272 p[0] = svgtiny_PATH_MOVE;
273 p[1] = strip_x0 + (strip_dy * 3);
274 p[2] = strip_y0 - (strip_dx * 3);
275 p[3] = svgtiny_PATH_LINE;
276 p[4] = p[1] + strip_dx;
277 p[5] = p[2] + strip_dy;
278 p[6] = svgtiny_PATH_LINE;
279 p[7] = p[4] - (strip_dy * 6);
280 p[8] = p[5] + (strip_dx * 6);
281 p[9] = svgtiny_PATH_LINE;
282 p[10] = p[7] - strip_dx;
283 p[11] = p[8] - strip_dy;
284 p[12] = svgtiny_PATH_CLOSE;
285 svgtiny_transform_path(p, 13, state);
286 struct svgtiny_shape *shape = svgtiny_add_shape(state);
287 if (!shape) {
288 free(p);
289 return svgtiny_OUT_OF_MEMORY;
290 }
291 shape->path = p;
292 shape->path_length = 13;
293 shape->fill = svgtiny_TRANSPARENT;
294 shape->stroke = svgtiny_RGB(0, 0xff, 0);
295 state->diagram->shape_count++;
296 }*/
297
298 /* invert gradient transform for applying to vertices */
299 svgtiny_invert_matrix(&state->gradient_transform.a, trans);
300 fprintf(stderr, "inverse transform %g %g %g %g %g %g\n",
301 trans[0], trans[1], trans[2], trans[3],
302 trans[4], trans[5]);
303
304 /* compute points on the path for triangle vertices */
305 /* r, r0, r1 are distance along gradient vector */
306 gradient_norm_squared = gradient_dx * gradient_dx +
307 gradient_dy * gradient_dy;
308 pts = svgtiny_list_create(
309 sizeof (struct grad_point));
310 if (!pts)
311 return svgtiny_OUT_OF_MEMORY;
312 for (j = 0; j != n; ) {
313 int segment_type = (int) p[j];
314 struct grad_point *point;
315 unsigned int z;
316
317 if (segment_type == svgtiny_PATH_MOVE) {
318 x0 = p[j + 1];
319 y0 = p[j + 2];
320 j += 3;
321 continue;
322 }
323
324 assert(segment_type == svgtiny_PATH_CLOSE ||
325 segment_type == svgtiny_PATH_LINE ||
326 segment_type == svgtiny_PATH_BEZIER);
327
328 /* start point (x0, y0) */
329 x0_trans = trans[0]*x0 + trans[2]*y0 + trans[4];
330 y0_trans = trans[1]*x0 + trans[3]*y0 + trans[5];
331 r0 = ((x0_trans - gradient_x0) * gradient_dx +
332 (y0_trans - gradient_y0) * gradient_dy) /
333 gradient_norm_squared;
334 point = svgtiny_list_push(pts);
335 if (!point) {
336 svgtiny_list_free(pts);
337 return svgtiny_OUT_OF_MEMORY;
338 }
339 point->x = x0;
340 point->y = y0;
341 point->r = r0;
342 if (r0 < min_r) {
343 min_r = r0;
344 min_pt = svgtiny_list_size(pts) - 1;
345 }
346
347 /* end point (x1, y1) */
348 if (segment_type == svgtiny_PATH_LINE) {
349 x1 = p[j + 1];
350 y1 = p[j + 2];
351 j += 3;
352 } else if (segment_type == svgtiny_PATH_CLOSE) {
353 x1 = p[1];
354 y1 = p[2];
355 j++;
356 } else /* svgtiny_PATH_BEZIER */ {
357 c0x = p[j + 1];
358 c0y = p[j + 2];
359 c1x = p[j + 3];
360 c1y = p[j + 4];
361 x1 = p[j + 5];
362 y1 = p[j + 6];
363 j += 7;
364 }
365 x1_trans = trans[0]*x1 + trans[2]*y1 + trans[4];
366 y1_trans = trans[1]*x1 + trans[3]*y1 + trans[5];
367 r1 = ((x1_trans - gradient_x0) * gradient_dx +
368 (y1_trans - gradient_y0) * gradient_dy) /
369 gradient_norm_squared;
370
371 /* determine steps from change in r */
372 steps = ceilf(fabsf(r1 - r0) / 0.05);
373 if (steps == 0)
374 steps = 1;
375 fprintf(stderr, "r0 %g, r1 %g, steps %i\n",
376 r0, r1, steps);
377
378 /* loop through intermediate points */
379 for (z = 1; z != steps; z++) {
380 float t, x, y, x_trans, y_trans, r;
381 struct grad_point *point;
382 t = (float) z / (float) steps;
383 if (segment_type == svgtiny_PATH_BEZIER) {
384 x = (1-t) * (1-t) * (1-t) * x0 +
385 3 * t * (1-t) * (1-t) * c0x +
386 3 * t * t * (1-t) * c1x +
387 t * t * t * x1;
388 y = (1-t) * (1-t) * (1-t) * y0 +
389 3 * t * (1-t) * (1-t) * c0y +
390 3 * t * t * (1-t) * c1y +
391 t * t * t * y1;
392 } else {
393 x = (1-t) * x0 + t * x1;
394 y = (1-t) * y0 + t * y1;
395 }
396 x_trans = trans[0]*x + trans[2]*y + trans[4];
397 y_trans = trans[1]*x + trans[3]*y + trans[5];
398 r = ((x_trans - gradient_x0) * gradient_dx +
399 (y_trans - gradient_y0) * gradient_dy) /
400 gradient_norm_squared;
401 fprintf(stderr, "(%g %g [%g]) ", x, y, r);
402 point = svgtiny_list_push(pts);
403 if (!point) {
404 svgtiny_list_free(pts);
405 return svgtiny_OUT_OF_MEMORY;
406 }
407 point->x = x;
408 point->y = y;
409 point->r = r;
410 if (r < min_r) {
411 min_r = r;
412 min_pt = svgtiny_list_size(pts) - 1;
413 }
414 }
415 fprintf(stderr, "\n");
416
417 /* next segment start point is this segment end point */
418 x0 = x1;
419 y0 = y1;
420 }
421 fprintf(stderr, "pts size %i, min_pt %i, min_r %.3f\n",
422 svgtiny_list_size(pts), min_pt, min_r);
423
424 /* render triangles */
425 stop_count = state->linear_gradient_stop_count;
426 assert(2 <= stop_count);
427 current_stop = 0;
428 last_stop_r = 0;
429 current_stop_r = state->gradient_stop[0].offset;
430 red0 = red1 = svgtiny_RED(state->gradient_stop[0].color);
431 green0 = green1 = svgtiny_GREEN(state->gradient_stop[0].color);
432 blue0 = blue1 = svgtiny_BLUE(state->gradient_stop[0].color);
433 t = min_pt;
434 a = (min_pt + 1) % svgtiny_list_size(pts);
435 b = min_pt == 0 ? svgtiny_list_size(pts) - 1 : min_pt - 1;
436 while (a != b) {
437 struct grad_point *point_t = svgtiny_list_get(pts, t);
438 struct grad_point *point_a = svgtiny_list_get(pts, a);
439 struct grad_point *point_b = svgtiny_list_get(pts, b);
440 float mean_r = (point_t->r + point_a->r + point_b->r) / 3;
441 float *p;
442 struct svgtiny_shape *shape;
443 /*fprintf(stderr, "triangle: t %i %.3f a %i %.3f b %i %.3f "
444 "mean_r %.3f\n",
445 t, pts[t].r, a, pts[a].r, b, pts[b].r,
446 mean_r);*/
447 while (current_stop != stop_count && current_stop_r < mean_r) {
448 current_stop++;
449 if (current_stop == stop_count)
450 break;
451 red0 = red1;
452 green0 = green1;
453 blue0 = blue1;
454 red1 = svgtiny_RED(state->
455 gradient_stop[current_stop].color);
456 green1 = svgtiny_GREEN(state->
457 gradient_stop[current_stop].color);
458 blue1 = svgtiny_BLUE(state->
459 gradient_stop[current_stop].color);
460 last_stop_r = current_stop_r;
461 current_stop_r = state->
462 gradient_stop[current_stop].offset;
463 }
464 p = malloc(10 * sizeof p[0]);
465 if (!p)
466 return svgtiny_OUT_OF_MEMORY;
467 p[0] = svgtiny_PATH_MOVE;
468 p[1] = point_t->x;
469 p[2] = point_t->y;
470 p[3] = svgtiny_PATH_LINE;
471 p[4] = point_a->x;
472 p[5] = point_a->y;
473 p[6] = svgtiny_PATH_LINE;
474 p[7] = point_b->x;
475 p[8] = point_b->y;
476 p[9] = svgtiny_PATH_CLOSE;
477 svgtiny_transform_path(p, 10, state);
478 shape = svgtiny_add_shape(state);
479 if (!shape) {
480 free(p);
481 return svgtiny_OUT_OF_MEMORY;
482 }
483 shape->path = p;
484 shape->path_length = 10;
485 /*shape->fill = svgtiny_TRANSPARENT;*/
486 if (current_stop == 0)
487 shape->fill = state->gradient_stop[0].color;
488 else if (current_stop == stop_count)
489 shape->fill = state->
490 gradient_stop[stop_count - 1].color;
491 else {
492 float stop_r = (mean_r - last_stop_r) /
493 (current_stop_r - last_stop_r);
494 shape->fill = svgtiny_RGB(
495 (int) ((1 - stop_r) * red0 + stop_r * red1),
496 (int) ((1 - stop_r) * green0 + stop_r * green1),
497 (int) ((1 - stop_r) * blue0 + stop_r * blue1));
498 }
499 shape->stroke = svgtiny_TRANSPARENT;
500 #ifdef GRADIENT_DEBUG
501 shape->stroke = svgtiny_RGB(0, 0, 0xff);
502 #endif
503 state->diagram->shape_count++;
504 if (point_a->r < point_b->r) {
505 t = a;
506 a = (a + 1) % svgtiny_list_size(pts);
507 } else {
508 t = b;
509 b = b == 0 ? svgtiny_list_size(pts) - 1 : b - 1;
510 }
511 }
512
513 /* render gradient vector for debugging */
514 #ifdef GRADIENT_DEBUG
515 {
516 float *p = malloc(7 * sizeof p[0]);
517 if (!p)
518 return svgtiny_OUT_OF_MEMORY;
519 p[0] = svgtiny_PATH_MOVE;
520 p[1] = gradient_x0;
521 p[2] = gradient_y0;
522 p[3] = svgtiny_PATH_LINE;
523 p[4] = gradient_x1;
524 p[5] = gradient_y1;
525 p[6] = svgtiny_PATH_CLOSE;
526 svgtiny_transform_path(p, 7, state);
527 struct svgtiny_shape *shape = svgtiny_add_shape(state);
528 if (!shape) {
529 free(p);
530 return svgtiny_OUT_OF_MEMORY;
531 }
532 shape->path = p;
533 shape->path_length = 7;
534 shape->fill = svgtiny_TRANSPARENT;
535 shape->stroke = svgtiny_RGB(0xff, 0, 0);
536 state->diagram->shape_count++;
537 }
538 #endif
539
540 /* render triangle vertices with r values for debugging */
541 #ifdef GRADIENT_DEBUG
542 for (unsigned int i = 0; i != pts->size; i++) {
543 struct grad_point *point = svgtiny_list_get(pts, i);
544 struct svgtiny_shape *shape = svgtiny_add_shape(state);
545 if (!shape)
546 return svgtiny_OUT_OF_MEMORY;
547 char *text = malloc(20);
548 if (!text)
549 return svgtiny_OUT_OF_MEMORY;
550 sprintf(text, "%i=%.3f", i, point->r);
551 shape->text = text;
552 shape->text_x = state->ctm.a * point->x +
553 state->ctm.c * point->y + state->ctm.e;
554 shape->text_y = state->ctm.b * point->x +
555 state->ctm.d * point->y + state->ctm.f;
556 shape->fill = svgtiny_RGB(0, 0, 0);
557 shape->stroke = svgtiny_TRANSPARENT;
558 state->diagram->shape_count++;
559 }
560 #endif
561
562 /* plot actual path outline */
563 if (state->stroke != svgtiny_TRANSPARENT) {
564 struct svgtiny_shape *shape;
565 svgtiny_transform_path(p, n, state);
566
567 shape = svgtiny_add_shape(state);
568 if (!shape) {
569 free(p);
570 return svgtiny_OUT_OF_MEMORY;
571 }
572 shape->path = p;
573 shape->path_length = n;
574 shape->fill = svgtiny_TRANSPARENT;
575 state->diagram->shape_count++;
576 } else {
577 free(p);
578 }
579
580 svgtiny_list_free(pts);
581
582 return svgtiny_OK;
583 }
584
585
586 /**
587 * Get the bounding box of path.
588 */
589
590 void svgtiny_path_bbox(float *p, unsigned int n,
591 float *x0, float *y0, float *x1, float *y1)
592 {
593 unsigned int j;
594
595 *x0 = *x1 = p[1];
596 *y0 = *y1 = p[2];
597
598 for (j = 0; j != n; ) {
599 unsigned int points = 0;
600 unsigned int k;
601 switch ((int) p[j]) {
602 case svgtiny_PATH_MOVE:
603 case svgtiny_PATH_LINE:
604 points = 1;
605 break;
606 case svgtiny_PATH_CLOSE:
607 points = 0;
608 break;
609 case svgtiny_PATH_BEZIER:
610 points = 3;
611 break;
612 default:
613 assert(0);
614 }
615 j++;
616 for (k = 0; k != points; k++) {
617 float x = p[j], y = p[j + 1];
618 if (x < *x0)
619 *x0 = x;
620 else if (*x1 < x)
621 *x1 = x;
622 if (y < *y0)
623 *y0 = y;
624 else if (*y1 < y)
625 *y1 = y;
626 j += 2;
627 }
628 }
629 }
630
631
632 /**
633 * Invert a transformation matrix.
634 */
635 void svgtiny_invert_matrix(float *m, float *inv)
636 {
637 float determinant = m[0]*m[3] - m[1]*m[2];
638 inv[0] = m[3] / determinant;
639 inv[1] = -m[1] / determinant;
640 inv[2] = -m[2] / determinant;
641 inv[3] = m[0] / determinant;
642 inv[4] = (m[2]*m[5] - m[3]*m[4]) / determinant;
643 inv[5] = (m[1]*m[4] - m[0]*m[5]) / determinant;
644 }
645
646
647 /**
648 * Find an element in the document by id.
649 */
650
651 xmlNode *svgtiny_find_element_by_id(xmlNode *node, const char *id)
652 {
653 xmlNode *child;
654 xmlNode *found;
655
656 for (child = node->children; child; child = child->next) {
657 xmlAttr *attr;
658 if (child->type != XML_ELEMENT_NODE)
659 continue;
660 attr = xmlHasProp(child, (const xmlChar *) "id");
661 if (attr && strcmp(id, (const char *) attr->children->content)
662 == 0)
663 return child;
664 found = svgtiny_find_element_by_id(child, id);
665 if (found)
666 return found;
667 }
668
669 return 0;
670 }
671