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 */
12 #include "svgtiny_internal.h"
16 static svgtiny_code
svgtiny_parse_linear_gradient(xmlNode
*linear
,
17 struct svgtiny_parse_state
*state
);
18 static float svgtiny_parse_gradient_offset(const char *s
);
19 static void svgtiny_path_bbox(float *p
, unsigned int n
,
20 float *x0
, float *y0
, float *x1
, float *y1
);
24 * Find a gradient by id and parse it.
27 void svgtiny_find_gradient(const char *id
, struct svgtiny_parse_state
*state
)
29 fprintf(stderr
, "svgtiny_find_gradient: id \"%s\"\n", id
);
31 state
->linear_gradient_stop_count
= 0;
32 state
->gradient_x1
= "0%";
33 state
->gradient_y1
= "0%";
34 state
->gradient_x2
= "100%";
35 state
->gradient_y2
= "0%";
37 xmlNode
*gradient
= svgtiny_find_element_by_id(
38 (xmlNode
*) state
->document
, id
);
39 fprintf(stderr
, "gradient %p\n", gradient
);
41 fprintf(stderr
, "gradient \"%s\" not found\n", id
);
45 fprintf(stderr
, "gradient name \"%s\"\n", gradient
->name
);
46 if (strcmp((const char *) gradient
->name
, "linearGradient") == 0) {
47 svgtiny_parse_linear_gradient(gradient
, state
);
53 * Parse a <linearGradient> element node.
55 * http://www.w3.org/TR/SVG11/pservers#LinearGradients
58 svgtiny_code
svgtiny_parse_linear_gradient(xmlNode
*linear
,
59 struct svgtiny_parse_state
*state
)
61 xmlAttr
*href
= xmlHasProp(linear
, (const xmlChar
*) "href");
62 if (href
&& href
->children
->content
[0] == '#')
63 svgtiny_find_gradient((const char *) href
->children
->content
66 for (xmlAttr
*attr
= linear
->properties
; attr
; attr
= attr
->next
) {
67 const char *name
= (const char *) attr
->name
;
68 const char *content
= (const char *) attr
->children
->content
;
69 if (strcmp(name
, "x1") == 0)
70 state
->gradient_x1
= content
;
71 else if (strcmp(name
, "y1") == 0)
72 state
->gradient_y1
= content
;
73 else if (strcmp(name
, "x2") == 0)
74 state
->gradient_x2
= content
;
75 else if (strcmp(name
, "y2") == 0)
76 state
->gradient_y2
= content
;
80 for (xmlNode
*stop
= linear
->children
; stop
; stop
= stop
->next
) {
82 svgtiny_colour color
= svgtiny_TRANSPARENT
;
84 if (stop
->type
!= XML_ELEMENT_NODE
)
86 if (strcmp((const char *) stop
->name
, "stop") != 0)
89 for (xmlAttr
*attr
= stop
->properties
; attr
;
91 const char *name
= (const char *) attr
->name
;
93 (const char *) attr
->children
->content
;
94 if (strcmp(name
, "offset") == 0)
95 offset
= svgtiny_parse_gradient_offset(content
);
96 else if (strcmp(name
, "stop-color") == 0)
97 svgtiny_parse_color(content
, &color
, state
);
98 else if (strcmp(name
, "style") == 0) {
101 if ((s
= strstr(content
, "stop-color:"))) {
105 value
= strndup(s
, strcspn(s
, "; "));
106 svgtiny_parse_color(value
, &color
,
113 if (offset
!= -1 && color
!= svgtiny_TRANSPARENT
) {
114 fprintf(stderr
, "stop %g %x\n", offset
, color
);
115 state
->gradient_stop
[i
].offset
= offset
;
116 state
->gradient_stop
[i
].color
= color
;
120 if (i
== svgtiny_MAX_STOPS
)
125 state
->linear_gradient_stop_count
= i
;
131 float svgtiny_parse_gradient_offset(const char *s
)
133 int num_length
= strspn(s
, "0123456789+-.");
134 const char *unit
= s
+ num_length
;
135 float n
= atof((const char *) s
);
139 else if (unit
[0] == '%')
153 * Add a path with a linear gradient fill to the svgtiny_diagram.
156 svgtiny_code
svgtiny_add_path_linear_gradient(float *p
, unsigned int n
,
157 struct svgtiny_parse_state
*state
)
159 /* determine object bounding box */
160 float object_x0
, object_y0
, object_x1
, object_y1
;
161 svgtiny_path_bbox(p
, n
, &object_x0
, &object_y0
, &object_x1
, &object_y1
);
162 #ifdef GRADIENT_DEBUG
163 fprintf(stderr
, "object bbox: (%g %g) (%g %g)\n",
164 object_x0
, object_y0
, object_x1
, object_y1
);
167 /* compute gradient vector */
168 fprintf(stderr
, "x1 %s, y1 %s, x2 %s, y2 %s\n",
169 state
->gradient_x1
, state
->gradient_y1
,
170 state
->gradient_x2
, state
->gradient_y2
);
171 float gradient_x0
, gradient_y0
, gradient_x1
, gradient_y1
,
172 gradient_dx
, gradient_dy
;
173 gradient_x0
= object_x0
+ svgtiny_parse_length(state
->gradient_x1
,
174 object_x1
- object_x0
, *state
);
175 gradient_y0
= object_y0
+ svgtiny_parse_length(state
->gradient_y1
,
176 object_y1
- object_y0
, *state
);
177 gradient_x1
= object_x0
+ svgtiny_parse_length(state
->gradient_x2
,
178 object_x1
- object_x0
, *state
);
179 gradient_y1
= object_y0
+ svgtiny_parse_length(state
->gradient_y2
,
180 object_y1
- object_y0
, *state
);
181 gradient_dx
= gradient_x1
- gradient_x0
;
182 gradient_dy
= gradient_y1
- gradient_y0
;
183 #ifdef GRADIENT_DEBUG
184 fprintf(stderr
, "gradient vector: (%g %g) => (%g %g)\n",
185 gradient_x0
, gradient_y0
, gradient_x1
, gradient_y1
);
188 /* show theoretical gradient strips for debugging */
189 /*unsigned int strips = 10;
190 for (unsigned int z = 0; z != strips; z++) {
191 float f0, fd, strip_x0, strip_y0, strip_dx, strip_dy;
192 f0 = (float) z / (float) strips;
193 fd = (float) 1 / (float) strips;
194 strip_x0 = gradient_x0 + f0 * gradient_dx;
195 strip_y0 = gradient_y0 + f0 * gradient_dy;
196 strip_dx = fd * gradient_dx;
197 strip_dy = fd * gradient_dy;
198 fprintf(stderr, "strip %i vector: (%g %g) + (%g %g)\n",
199 z, strip_x0, strip_y0, strip_dx, strip_dy);
201 float *p = malloc(13 * sizeof p[0]);
203 return svgtiny_OUT_OF_MEMORY;
204 p[0] = svgtiny_PATH_MOVE;
205 p[1] = strip_x0 + (strip_dy * 3);
206 p[2] = strip_y0 - (strip_dx * 3);
207 p[3] = svgtiny_PATH_LINE;
208 p[4] = p[1] + strip_dx;
209 p[5] = p[2] + strip_dy;
210 p[6] = svgtiny_PATH_LINE;
211 p[7] = p[4] - (strip_dy * 6);
212 p[8] = p[5] + (strip_dx * 6);
213 p[9] = svgtiny_PATH_LINE;
214 p[10] = p[7] - strip_dx;
215 p[11] = p[8] - strip_dy;
216 p[12] = svgtiny_PATH_CLOSE;
217 svgtiny_transform_path(p, 13, state);
218 struct svgtiny_shape *shape = svgtiny_add_shape(state);
221 return svgtiny_OUT_OF_MEMORY;
224 shape->path_length = 13;
225 shape->fill = svgtiny_TRANSPARENT;
226 shape->stroke = svgtiny_RGB(0, 0xff, 0);
227 state->diagram->shape_count++;
230 /* compute points on the path for triangle vertices */
231 unsigned int steps
= 10;
232 float x0
, y0
, x1
, y1
;
233 float gradient_norm_squared
= gradient_dx
* gradient_dx
+
234 gradient_dy
* gradient_dy
;
238 struct grad_point
*pts
= malloc(n
* steps
* sizeof pts
[0]);
240 return svgtiny_OUT_OF_MEMORY
;
241 unsigned int pts_count
= 0;
243 unsigned int min_pt
= 0;
244 for (unsigned int j
= 0; j
!= n
; ) {
245 switch ((int) p
[j
]) {
246 case svgtiny_PATH_MOVE
:
251 case svgtiny_PATH_LINE
:
252 case svgtiny_PATH_CLOSE
:
253 if (((int) p
[j
]) == svgtiny_PATH_LINE
) {
262 fprintf(stderr
, "line: ");
263 for (unsigned int z
= 0; z
!= steps
; z
++) {
265 f
= (float) z
/ (float) steps
;
266 x
= x0
+ f
* (x1
- x0
);
267 y
= y0
+ f
* (y1
- y0
);
268 r
= ((x
- gradient_x0
) * gradient_dx
+
269 (y
- gradient_y0
) * gradient_dy
) /
270 gradient_norm_squared
;
271 fprintf(stderr
, "(%g %g [%g]) ", x
, y
, r
);
272 pts
[pts_count
].x
= x
;
273 pts
[pts_count
].y
= y
;
274 pts
[pts_count
].r
= r
;
281 fprintf(stderr
, "\n");
285 case svgtiny_PATH_BEZIER
:
286 fprintf(stderr
, "bezier: ");
287 for (unsigned int z
= 0; z
!= steps
; z
++) {
289 t
= (float) z
/ (float) steps
;
290 x
= (1-t
) * (1-t
) * (1-t
) * x0
+
291 3 * t
* (1-t
) * (1-t
) * p
[j
+ 1] +
292 3 * t
* t
* (1-t
) * p
[j
+ 3] +
293 t
* t
* t
* p
[j
+ 5];
294 y
= (1-t
) * (1-t
) * (1-t
) * y0
+
295 3 * t
* (1-t
) * (1-t
) * p
[j
+ 2] +
296 3 * t
* t
* (1-t
) * p
[j
+ 4] +
297 t
* t
* t
* p
[j
+ 6];
298 r
= ((x
- gradient_x0
) * gradient_dx
+
299 (y
- gradient_y0
) * gradient_dy
) /
300 gradient_norm_squared
;
301 fprintf(stderr
, "(%g %g [%g]) ", x
, y
, r
);
302 pts
[pts_count
].x
= x
;
303 pts
[pts_count
].y
= y
;
304 pts
[pts_count
].r
= r
;
311 fprintf(stderr
, "\n");
320 fprintf(stderr
, "pts_count %i, min_pt %i, min_r %.3f\n",
321 pts_count
, min_pt
, min_r
);
323 unsigned int stop_count
= state
->linear_gradient_stop_count
;
324 assert(2 <= stop_count
);
325 unsigned int current_stop
= 0;
326 float last_stop_r
= 0;
327 float current_stop_r
= state
->gradient_stop
[0].offset
;
328 int red0
, green0
, blue0
, red1
, green1
, blue1
;
329 red0
= red1
= svgtiny_RED(state
->gradient_stop
[0].color
);
330 green0
= green1
= svgtiny_GREEN(state
->gradient_stop
[0].color
);
331 blue0
= blue1
= svgtiny_BLUE(state
->gradient_stop
[0].color
);
332 unsigned int t
, a
, b
;
334 a
= (min_pt
+ 1) % pts_count
;
335 b
= min_pt
== 0 ? pts_count
- 1 : min_pt
- 1;
337 float mean_r
= (pts
[t
].r
+ pts
[a
].r
+ pts
[b
].r
) / 3;
338 /*fprintf(stderr, "triangle: t %i %.3f a %i %.3f b %i %.3f "
340 t, pts[t].r, a, pts[a].r, b, pts[b].r,
342 while (current_stop
!= stop_count
&& current_stop_r
< mean_r
) {
344 if (current_stop
== stop_count
)
349 red1
= svgtiny_RED(state
->
350 gradient_stop
[current_stop
].color
);
351 green1
= svgtiny_GREEN(state
->
352 gradient_stop
[current_stop
].color
);
353 blue1
= svgtiny_BLUE(state
->
354 gradient_stop
[current_stop
].color
);
355 last_stop_r
= current_stop_r
;
356 current_stop_r
= state
->
357 gradient_stop
[current_stop
].offset
;
359 float *p
= malloc(10 * sizeof p
[0]);
361 return svgtiny_OUT_OF_MEMORY
;
362 p
[0] = svgtiny_PATH_MOVE
;
365 p
[3] = svgtiny_PATH_LINE
;
368 p
[6] = svgtiny_PATH_LINE
;
371 p
[9] = svgtiny_PATH_CLOSE
;
372 svgtiny_transform_path(p
, 10, state
);
373 struct svgtiny_shape
*shape
= svgtiny_add_shape(state
);
376 return svgtiny_OUT_OF_MEMORY
;
379 shape
->path_length
= 10;
380 /*shape->fill = svgtiny_TRANSPARENT;*/
381 if (current_stop
== 0)
382 shape
->fill
= state
->gradient_stop
[0].color
;
383 else if (current_stop
== stop_count
)
384 shape
->fill
= state
->
385 gradient_stop
[stop_count
- 1].color
;
387 float stop_r
= (mean_r
- last_stop_r
) /
388 (current_stop_r
- last_stop_r
);
389 shape
->fill
= svgtiny_RGB(
390 (int) ((1 - stop_r
) * red0
+ stop_r
* red1
),
391 (int) ((1 - stop_r
) * green0
+ stop_r
* green1
),
392 (int) ((1 - stop_r
) * blue0
+ stop_r
* blue1
));
394 shape
->stroke
= svgtiny_TRANSPARENT
;
395 #ifdef GRADIENT_DEBUG
396 shape
->stroke
= svgtiny_RGB(0, 0, 0xff);
398 state
->diagram
->shape_count
++;
399 if (pts
[a
].r
< pts
[b
].r
) {
401 a
= (a
+ 1) % pts_count
;
404 b
= b
== 0 ? pts_count
- 1 : b
- 1;
408 /* render gradient vector for debugging */
409 #ifdef GRADIENT_DEBUG
411 float *p
= malloc(7 * sizeof p
[0]);
413 return svgtiny_OUT_OF_MEMORY
;
414 p
[0] = svgtiny_PATH_MOVE
;
417 p
[3] = svgtiny_PATH_LINE
;
420 p
[6] = svgtiny_PATH_CLOSE
;
421 svgtiny_transform_path(p
, 7, state
);
422 struct svgtiny_shape
*shape
= svgtiny_add_shape(state
);
425 return svgtiny_OUT_OF_MEMORY
;
428 shape
->path_length
= 7;
429 shape
->fill
= svgtiny_TRANSPARENT
;
430 shape
->stroke
= svgtiny_RGB(0xff, 0, 0);
431 state
->diagram
->shape_count
++;
435 /* render triangle vertices with r values for debugging */
436 #ifdef GRADIENT_DEBUG
437 for (unsigned int i
= 0; i
!= pts_count
; i
++) {
438 struct svgtiny_shape
*shape
= svgtiny_add_shape(state
);
440 return svgtiny_OUT_OF_MEMORY
;
441 char *text
= malloc(20);
443 return svgtiny_OUT_OF_MEMORY
;
444 sprintf(text
, "%i=%.3f", i
, pts
[i
].r
);
446 shape
->text_x
= state
->ctm
.a
* pts
[i
].x
+
447 state
->ctm
.c
* pts
[i
].y
+ state
->ctm
.e
;
448 shape
->text_y
= state
->ctm
.b
* pts
[i
].x
+
449 state
->ctm
.d
* pts
[i
].y
+ state
->ctm
.f
;
450 shape
->fill
= svgtiny_RGB(0, 0, 0);
451 state
->diagram
->shape_count
++;
455 /* plot actual path outline */
456 if (state
->stroke
!= svgtiny_TRANSPARENT
) {
457 svgtiny_transform_path(p
, n
, state
);
459 struct svgtiny_shape
*shape
= svgtiny_add_shape(state
);
462 return svgtiny_OUT_OF_MEMORY
;
465 shape
->path_length
= n
;
466 shape
->fill
= svgtiny_TRANSPARENT
;
467 state
->diagram
->shape_count
++;
475 * Get the bounding box of path.
478 void svgtiny_path_bbox(float *p
, unsigned int n
,
479 float *x0
, float *y0
, float *x1
, float *y1
)
484 for (unsigned int j
= 0; j
!= n
; ) {
485 unsigned int points
= 0;
486 switch ((int) p
[j
]) {
487 case svgtiny_PATH_MOVE
:
488 case svgtiny_PATH_LINE
:
491 case svgtiny_PATH_CLOSE
:
494 case svgtiny_PATH_BEZIER
:
501 for (unsigned int k
= 0; k
!= points
; k
++) {
502 float x
= p
[j
], y
= p
[j
+ 1];
518 * Find an element in the document by id.
521 xmlNode
*svgtiny_find_element_by_id(xmlNode
*node
, const char *id
)
526 for (child
= node
->children
; child
; child
= child
->next
) {
527 if (child
->type
!= XML_ELEMENT_NODE
)
529 xmlAttr
*attr
= xmlHasProp(child
, (const xmlChar
*) "id");
530 if (attr
&& strcmp(id
, (const char *) attr
->children
->content
)
533 found
= svgtiny_find_element_by_id(child
, id
);