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>
8 #define _GNU_SOURCE /* for strndup */
13 #include "svgtiny_internal.h"
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
);
26 * Find a gradient by id and parse it.
29 void svgtiny_find_gradient(const char *id
, struct svgtiny_parse_state
*state
)
33 fprintf(stderr
, "svgtiny_find_gradient: id \"%s\"\n", id
);
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;
48 gradient
= svgtiny_find_element_by_id(
49 (xmlNode
*) state
->document
, id
);
50 fprintf(stderr
, "gradient %p\n", (void *) gradient
);
52 fprintf(stderr
, "gradient \"%s\" not found\n", id
);
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
);
64 * Parse a <linearGradient> element node.
66 * http://www.w3.org/TR/SVG11/pservers#LinearGradients
69 svgtiny_code
svgtiny_parse_linear_gradient(xmlNode
*linear
,
70 struct svgtiny_parse_state
*state
)
75 xmlAttr
*href
= xmlHasProp(linear
, (const xmlChar
*) "href");
76 if (href
&& href
->children
->content
[0] == '#')
77 svgtiny_find_gradient((const char *) href
->children
->content
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
);
98 return svgtiny_OUT_OF_MEMORY
;
99 svgtiny_parse_transform(s
, &a
, &b
, &c
, &d
, &e
, &f
);
101 fprintf(stderr
, "transform %g %g %g %g %g %g\n",
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
;
112 for (stop
= linear
->children
; stop
; stop
= stop
->next
) {
114 svgtiny_colour color
= svgtiny_TRANSPARENT
;
116 if (stop
->type
!= XML_ELEMENT_NODE
)
118 if (strcmp((const char *) stop
->name
, "stop") != 0)
121 for (attr
= stop
->properties
; attr
;
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) {
133 if ((s
= strstr(content
, "stop-color:"))) {
137 value
= strndup(s
, strcspn(s
, "; "));
138 svgtiny_parse_color(value
, &color
,
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
;
152 if (i
== svgtiny_MAX_STOPS
)
157 state
->linear_gradient_stop_count
= i
;
163 float svgtiny_parse_gradient_offset(const char *s
)
165 int num_length
= strspn(s
, "0123456789+-.");
166 const char *unit
= s
+ num_length
;
167 float n
= atof((const char *) s
);
171 else if (unit
[0] == '%')
185 * Add a path with a linear gradient fill to the svgtiny_diagram.
188 svgtiny_code
svgtiny_add_path_linear_gradient(float *p
, unsigned int n
,
189 struct svgtiny_parse_state
*state
)
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
;
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
;
206 unsigned int min_pt
= 0;
208 unsigned int stop_count
;
209 unsigned int current_stop
;
211 float current_stop_r
;
212 int red0
, green0
, blue0
, red1
, green1
, blue1
;
213 unsigned int t
, a
, b
;
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
);
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
);
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
);
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
);
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);
269 float *p = malloc(13 * sizeof p[0]);
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);
289 return svgtiny_OUT_OF_MEMORY;
292 shape->path_length = 13;
293 shape->fill = svgtiny_TRANSPARENT;
294 shape->stroke = svgtiny_RGB(0, 0xff, 0);
295 state->diagram->shape_count++;
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],
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
));
311 return svgtiny_OUT_OF_MEMORY
;
312 for (j
= 0; j
!= n
; ) {
313 int segment_type
= (int) p
[j
];
314 struct grad_point
*point
;
317 if (segment_type
== svgtiny_PATH_MOVE
) {
324 assert(segment_type
== svgtiny_PATH_CLOSE
||
325 segment_type
== svgtiny_PATH_LINE
||
326 segment_type
== svgtiny_PATH_BEZIER
);
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
);
336 svgtiny_list_free(pts
);
337 return svgtiny_OUT_OF_MEMORY
;
344 min_pt
= svgtiny_list_size(pts
) - 1;
347 /* end point (x1, y1) */
348 if (segment_type
== svgtiny_PATH_LINE
) {
352 } else if (segment_type
== svgtiny_PATH_CLOSE
) {
356 } else /* svgtiny_PATH_BEZIER */ {
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
;
371 /* determine steps from change in r */
372 steps
= ceilf(fabsf(r1
- r0
) / 0.05);
375 fprintf(stderr
, "r0 %g, r1 %g, steps %i\n",
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
+
388 y
= (1-t
) * (1-t
) * (1-t
) * y0
+
389 3 * t
* (1-t
) * (1-t
) * c0y
+
390 3 * t
* t
* (1-t
) * c1y
+
393 x
= (1-t
) * x0
+ t
* x1
;
394 y
= (1-t
) * y0
+ t
* y1
;
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
);
404 svgtiny_list_free(pts
);
405 return svgtiny_OUT_OF_MEMORY
;
412 min_pt
= svgtiny_list_size(pts
) - 1;
415 fprintf(stderr
, "\n");
417 /* next segment start point is this segment end point */
421 fprintf(stderr
, "pts size %i, min_pt %i, min_r %.3f\n",
422 svgtiny_list_size(pts
), min_pt
, min_r
);
424 /* render triangles */
425 stop_count
= state
->linear_gradient_stop_count
;
426 assert(2 <= stop_count
);
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
);
434 a
= (min_pt
+ 1) % svgtiny_list_size(pts
);
435 b
= min_pt
== 0 ? svgtiny_list_size(pts
) - 1 : min_pt
- 1;
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;
442 struct svgtiny_shape
*shape
;
443 /*fprintf(stderr, "triangle: t %i %.3f a %i %.3f b %i %.3f "
445 t, pts[t].r, a, pts[a].r, b, pts[b].r,
447 while (current_stop
!= stop_count
&& current_stop_r
< mean_r
) {
449 if (current_stop
== stop_count
)
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
;
464 p
= malloc(10 * sizeof p
[0]);
466 return svgtiny_OUT_OF_MEMORY
;
467 p
[0] = svgtiny_PATH_MOVE
;
470 p
[3] = svgtiny_PATH_LINE
;
473 p
[6] = svgtiny_PATH_LINE
;
476 p
[9] = svgtiny_PATH_CLOSE
;
477 svgtiny_transform_path(p
, 10, state
);
478 shape
= svgtiny_add_shape(state
);
481 return svgtiny_OUT_OF_MEMORY
;
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
;
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
));
499 shape
->stroke
= svgtiny_TRANSPARENT
;
500 #ifdef GRADIENT_DEBUG
501 shape
->stroke
= svgtiny_RGB(0, 0, 0xff);
503 state
->diagram
->shape_count
++;
504 if (point_a
->r
< point_b
->r
) {
506 a
= (a
+ 1) % svgtiny_list_size(pts
);
509 b
= b
== 0 ? svgtiny_list_size(pts
) - 1 : b
- 1;
513 /* render gradient vector for debugging */
514 #ifdef GRADIENT_DEBUG
516 float *p
= malloc(7 * sizeof p
[0]);
518 return svgtiny_OUT_OF_MEMORY
;
519 p
[0] = svgtiny_PATH_MOVE
;
522 p
[3] = svgtiny_PATH_LINE
;
525 p
[6] = svgtiny_PATH_CLOSE
;
526 svgtiny_transform_path(p
, 7, state
);
527 struct svgtiny_shape
*shape
= svgtiny_add_shape(state
);
530 return svgtiny_OUT_OF_MEMORY
;
533 shape
->path_length
= 7;
534 shape
->fill
= svgtiny_TRANSPARENT
;
535 shape
->stroke
= svgtiny_RGB(0xff, 0, 0);
536 state
->diagram
->shape_count
++;
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
);
546 return svgtiny_OUT_OF_MEMORY
;
547 char *text
= malloc(20);
549 return svgtiny_OUT_OF_MEMORY
;
550 sprintf(text
, "%i=%.3f", i
, point
->r
);
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
++;
562 /* plot actual path outline */
563 if (state
->stroke
!= svgtiny_TRANSPARENT
) {
564 struct svgtiny_shape
*shape
;
565 svgtiny_transform_path(p
, n
, state
);
567 shape
= svgtiny_add_shape(state
);
570 return svgtiny_OUT_OF_MEMORY
;
573 shape
->path_length
= n
;
574 shape
->fill
= svgtiny_TRANSPARENT
;
575 state
->diagram
->shape_count
++;
580 svgtiny_list_free(pts
);
587 * Get the bounding box of path.
590 void svgtiny_path_bbox(float *p
, unsigned int n
,
591 float *x0
, float *y0
, float *x1
, float *y1
)
598 for (j
= 0; j
!= n
; ) {
599 unsigned int points
= 0;
601 switch ((int) p
[j
]) {
602 case svgtiny_PATH_MOVE
:
603 case svgtiny_PATH_LINE
:
606 case svgtiny_PATH_CLOSE
:
609 case svgtiny_PATH_BEZIER
:
616 for (k
= 0; k
!= points
; k
++) {
617 float x
= p
[j
], y
= p
[j
+ 1];
633 * Invert a transformation matrix.
635 void svgtiny_invert_matrix(float *m
, float *inv
)
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
;
648 * Find an element in the document by id.
651 xmlNode
*svgtiny_find_element_by_id(xmlNode
*node
, const char *id
)
656 for (child
= node
->children
; child
; child
= child
->next
) {
658 if (child
->type
!= XML_ELEMENT_NODE
)
660 attr
= xmlHasProp(child
, (const xmlChar
*) "id");
661 if (attr
&& strcmp(id
, (const char *) attr
->children
->content
)
664 found
= svgtiny_find_element_by_id(child
, id
);