]> gitweb.michael.orlitzky.com - libsvgtiny.git/blob - src/svgtiny_gradient.c
98a2964702cbccee1fbb76c2176709023e0cb1c2
[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", (void *) 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 = 0, y0 = 0, x0_trans, y0_trans, r0; /* segment start point */
284 float x1, y1, x1_trans, y1_trans, r1; /* segment end point */
285 /* segment control points (beziers only) */
286 float c0x = 0, c0y = 0, c1x = 0, c1y = 0;
287 float gradient_norm_squared = gradient_dx * gradient_dx +
288 gradient_dy * gradient_dy;
289 struct grad_point {
290 float x, y, r;
291 };
292 struct svgtiny_list *pts = svgtiny_list_create(
293 sizeof (struct grad_point));
294 if (!pts)
295 return svgtiny_OUT_OF_MEMORY;
296 float min_r = 1000;
297 unsigned int min_pt = 0;
298 for (unsigned int j = 0; j != n; ) {
299 int segment_type = (int) p[j];
300
301 if (segment_type == svgtiny_PATH_MOVE) {
302 x0 = p[j + 1];
303 y0 = p[j + 2];
304 j += 3;
305 continue;
306 }
307
308 assert(segment_type == svgtiny_PATH_CLOSE ||
309 segment_type == svgtiny_PATH_LINE ||
310 segment_type == svgtiny_PATH_BEZIER);
311
312 /* start point (x0, y0) */
313 x0_trans = trans[0]*x0 + trans[2]*y0 + trans[4];
314 y0_trans = trans[1]*x0 + trans[3]*y0 + trans[5];
315 r0 = ((x0_trans - gradient_x0) * gradient_dx +
316 (y0_trans - gradient_y0) * gradient_dy) /
317 gradient_norm_squared;
318 struct grad_point *point = svgtiny_list_push(pts);
319 if (!point) {
320 svgtiny_list_free(pts);
321 return svgtiny_OUT_OF_MEMORY;
322 }
323 point->x = x0;
324 point->y = y0;
325 point->r = r0;
326 if (r0 < min_r) {
327 min_r = r0;
328 min_pt = svgtiny_list_size(pts) - 1;
329 }
330
331 /* end point (x1, y1) */
332 if (segment_type == svgtiny_PATH_LINE) {
333 x1 = p[j + 1];
334 y1 = p[j + 2];
335 j += 3;
336 } else if (segment_type == svgtiny_PATH_CLOSE) {
337 x1 = p[1];
338 y1 = p[2];
339 j++;
340 } else /* svgtiny_PATH_BEZIER */ {
341 c0x = p[j + 1];
342 c0y = p[j + 2];
343 c1x = p[j + 3];
344 c1y = p[j + 4];
345 x1 = p[j + 5];
346 y1 = p[j + 6];
347 j += 7;
348 }
349 x1_trans = trans[0]*x1 + trans[2]*y1 + trans[4];
350 y1_trans = trans[1]*x1 + trans[3]*y1 + trans[5];
351 r1 = ((x1_trans - gradient_x0) * gradient_dx +
352 (y1_trans - gradient_y0) * gradient_dy) /
353 gradient_norm_squared;
354
355 /* determine steps from change in r */
356 steps = ceilf(fabsf(r1 - r0) / 0.05);
357 if (steps == 0)
358 steps = 1;
359 fprintf(stderr, "r0 %g, r1 %g, steps %i\n",
360 r0, r1, steps);
361
362 /* loop through intermediate points */
363 for (unsigned int z = 1; z != steps; z++) {
364 float t, x, y, x_trans, y_trans, r;
365 t = (float) z / (float) steps;
366 if (segment_type == svgtiny_PATH_BEZIER) {
367 x = (1-t) * (1-t) * (1-t) * x0 +
368 3 * t * (1-t) * (1-t) * c0x +
369 3 * t * t * (1-t) * c1x +
370 t * t * t * x1;
371 y = (1-t) * (1-t) * (1-t) * y0 +
372 3 * t * (1-t) * (1-t) * c0y +
373 3 * t * t * (1-t) * c1y +
374 t * t * t * y1;
375 } else {
376 x = (1-t) * x0 + t * x1;
377 y = (1-t) * y0 + t * y1;
378 }
379 x_trans = trans[0]*x + trans[2]*y + trans[4];
380 y_trans = trans[1]*x + trans[3]*y + trans[5];
381 r = ((x_trans - gradient_x0) * gradient_dx +
382 (y_trans - gradient_y0) * gradient_dy) /
383 gradient_norm_squared;
384 fprintf(stderr, "(%g %g [%g]) ", x, y, r);
385 struct grad_point *point = svgtiny_list_push(pts);
386 if (!point) {
387 svgtiny_list_free(pts);
388 return svgtiny_OUT_OF_MEMORY;
389 }
390 point->x = x;
391 point->y = y;
392 point->r = r;
393 if (r < min_r) {
394 min_r = r;
395 min_pt = svgtiny_list_size(pts) - 1;
396 }
397 }
398 fprintf(stderr, "\n");
399
400 /* next segment start point is this segment end point */
401 x0 = x1;
402 y0 = y1;
403 }
404 fprintf(stderr, "pts size %i, min_pt %i, min_r %.3f\n",
405 svgtiny_list_size(pts), min_pt, min_r);
406
407 /* render triangles */
408 unsigned int stop_count = state->linear_gradient_stop_count;
409 assert(2 <= stop_count);
410 unsigned int current_stop = 0;
411 float last_stop_r = 0;
412 float current_stop_r = state->gradient_stop[0].offset;
413 int red0, green0, blue0, red1, green1, blue1;
414 red0 = red1 = svgtiny_RED(state->gradient_stop[0].color);
415 green0 = green1 = svgtiny_GREEN(state->gradient_stop[0].color);
416 blue0 = blue1 = svgtiny_BLUE(state->gradient_stop[0].color);
417 unsigned int t, a, b;
418 t = min_pt;
419 a = (min_pt + 1) % svgtiny_list_size(pts);
420 b = min_pt == 0 ? svgtiny_list_size(pts) - 1 : min_pt - 1;
421 while (a != b) {
422 struct grad_point *point_t = svgtiny_list_get(pts, t);
423 struct grad_point *point_a = svgtiny_list_get(pts, a);
424 struct grad_point *point_b = svgtiny_list_get(pts, b);
425 float mean_r = (point_t->r + point_a->r + point_b->r) / 3;
426 /*fprintf(stderr, "triangle: t %i %.3f a %i %.3f b %i %.3f "
427 "mean_r %.3f\n",
428 t, pts[t].r, a, pts[a].r, b, pts[b].r,
429 mean_r);*/
430 while (current_stop != stop_count && current_stop_r < mean_r) {
431 current_stop++;
432 if (current_stop == stop_count)
433 break;
434 red0 = red1;
435 green0 = green1;
436 blue0 = blue1;
437 red1 = svgtiny_RED(state->
438 gradient_stop[current_stop].color);
439 green1 = svgtiny_GREEN(state->
440 gradient_stop[current_stop].color);
441 blue1 = svgtiny_BLUE(state->
442 gradient_stop[current_stop].color);
443 last_stop_r = current_stop_r;
444 current_stop_r = state->
445 gradient_stop[current_stop].offset;
446 }
447 float *p = malloc(10 * sizeof p[0]);
448 if (!p)
449 return svgtiny_OUT_OF_MEMORY;
450 p[0] = svgtiny_PATH_MOVE;
451 p[1] = point_t->x;
452 p[2] = point_t->y;
453 p[3] = svgtiny_PATH_LINE;
454 p[4] = point_a->x;
455 p[5] = point_a->y;
456 p[6] = svgtiny_PATH_LINE;
457 p[7] = point_b->x;
458 p[8] = point_b->y;
459 p[9] = svgtiny_PATH_CLOSE;
460 svgtiny_transform_path(p, 10, state);
461 struct svgtiny_shape *shape = svgtiny_add_shape(state);
462 if (!shape) {
463 free(p);
464 return svgtiny_OUT_OF_MEMORY;
465 }
466 shape->path = p;
467 shape->path_length = 10;
468 /*shape->fill = svgtiny_TRANSPARENT;*/
469 if (current_stop == 0)
470 shape->fill = state->gradient_stop[0].color;
471 else if (current_stop == stop_count)
472 shape->fill = state->
473 gradient_stop[stop_count - 1].color;
474 else {
475 float stop_r = (mean_r - last_stop_r) /
476 (current_stop_r - last_stop_r);
477 shape->fill = svgtiny_RGB(
478 (int) ((1 - stop_r) * red0 + stop_r * red1),
479 (int) ((1 - stop_r) * green0 + stop_r * green1),
480 (int) ((1 - stop_r) * blue0 + stop_r * blue1));
481 }
482 shape->stroke = svgtiny_TRANSPARENT;
483 #ifdef GRADIENT_DEBUG
484 shape->stroke = svgtiny_RGB(0, 0, 0xff);
485 #endif
486 state->diagram->shape_count++;
487 if (point_a->r < point_b->r) {
488 t = a;
489 a = (a + 1) % svgtiny_list_size(pts);
490 } else {
491 t = b;
492 b = b == 0 ? svgtiny_list_size(pts) - 1 : b - 1;
493 }
494 }
495
496 /* render gradient vector for debugging */
497 #ifdef GRADIENT_DEBUG
498 {
499 float *p = malloc(7 * sizeof p[0]);
500 if (!p)
501 return svgtiny_OUT_OF_MEMORY;
502 p[0] = svgtiny_PATH_MOVE;
503 p[1] = gradient_x0;
504 p[2] = gradient_y0;
505 p[3] = svgtiny_PATH_LINE;
506 p[4] = gradient_x1;
507 p[5] = gradient_y1;
508 p[6] = svgtiny_PATH_CLOSE;
509 svgtiny_transform_path(p, 7, state);
510 struct svgtiny_shape *shape = svgtiny_add_shape(state);
511 if (!shape) {
512 free(p);
513 return svgtiny_OUT_OF_MEMORY;
514 }
515 shape->path = p;
516 shape->path_length = 7;
517 shape->fill = svgtiny_TRANSPARENT;
518 shape->stroke = svgtiny_RGB(0xff, 0, 0);
519 state->diagram->shape_count++;
520 }
521 #endif
522
523 /* render triangle vertices with r values for debugging */
524 #ifdef GRADIENT_DEBUG
525 for (unsigned int i = 0; i != pts->size; i++) {
526 struct grad_point *point = svgtiny_list_get(pts, i);
527 struct svgtiny_shape *shape = svgtiny_add_shape(state);
528 if (!shape)
529 return svgtiny_OUT_OF_MEMORY;
530 char *text = malloc(20);
531 if (!text)
532 return svgtiny_OUT_OF_MEMORY;
533 sprintf(text, "%i=%.3f", i, point->r);
534 shape->text = text;
535 shape->text_x = state->ctm.a * point->x +
536 state->ctm.c * point->y + state->ctm.e;
537 shape->text_y = state->ctm.b * point->x +
538 state->ctm.d * point->y + state->ctm.f;
539 shape->fill = svgtiny_RGB(0, 0, 0);
540 shape->stroke = svgtiny_TRANSPARENT;
541 state->diagram->shape_count++;
542 }
543 #endif
544
545 /* plot actual path outline */
546 if (state->stroke != svgtiny_TRANSPARENT) {
547 svgtiny_transform_path(p, n, state);
548
549 struct svgtiny_shape *shape = svgtiny_add_shape(state);
550 if (!shape) {
551 free(p);
552 return svgtiny_OUT_OF_MEMORY;
553 }
554 shape->path = p;
555 shape->path_length = n;
556 shape->fill = svgtiny_TRANSPARENT;
557 state->diagram->shape_count++;
558 } else {
559 free(p);
560 }
561
562 svgtiny_list_free(pts);
563
564 return svgtiny_OK;
565 }
566
567
568 /**
569 * Get the bounding box of path.
570 */
571
572 void svgtiny_path_bbox(float *p, unsigned int n,
573 float *x0, float *y0, float *x1, float *y1)
574 {
575 *x0 = *x1 = p[1];
576 *y0 = *y1 = p[2];
577
578 for (unsigned int j = 0; j != n; ) {
579 unsigned int points = 0;
580 switch ((int) p[j]) {
581 case svgtiny_PATH_MOVE:
582 case svgtiny_PATH_LINE:
583 points = 1;
584 break;
585 case svgtiny_PATH_CLOSE:
586 points = 0;
587 break;
588 case svgtiny_PATH_BEZIER:
589 points = 3;
590 break;
591 default:
592 assert(0);
593 }
594 j++;
595 for (unsigned int k = 0; k != points; k++) {
596 float x = p[j], y = p[j + 1];
597 if (x < *x0)
598 *x0 = x;
599 else if (*x1 < x)
600 *x1 = x;
601 if (y < *y0)
602 *y0 = y;
603 else if (*y1 < y)
604 *y1 = y;
605 j += 2;
606 }
607 }
608 }
609
610
611 /**
612 * Invert a transformation matrix.
613 */
614 void svgtiny_invert_matrix(float *m, float *inv)
615 {
616 float determinant = m[0]*m[3] - m[1]*m[2];
617 inv[0] = m[3] / determinant;
618 inv[1] = -m[1] / determinant;
619 inv[2] = -m[2] / determinant;
620 inv[3] = m[0] / determinant;
621 inv[4] = (m[2]*m[5] - m[3]*m[4]) / determinant;
622 inv[5] = (m[1]*m[4] - m[0]*m[5]) / determinant;
623 }
624
625
626 /**
627 * Find an element in the document by id.
628 */
629
630 xmlNode *svgtiny_find_element_by_id(xmlNode *node, const char *id)
631 {
632 xmlNode *child;
633 xmlNode *found;
634
635 for (child = node->children; child; child = child->next) {
636 if (child->type != XML_ELEMENT_NODE)
637 continue;
638 xmlAttr *attr = xmlHasProp(child, (const xmlChar *) "id");
639 if (attr && strcmp(id, (const char *) attr->children->content)
640 == 0)
641 return child;
642 found = svgtiny_find_element_by_id(child, id);
643 if (found)
644 return found;
645 }
646
647 return 0;
648 }
649