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