1 /* This file is part of the GNU plotutils package. Copyright (C) 1995,
2 1996, 1997, 1998, 1999, 2000, 2005, 2008, 2009, Free Software
3 Foundation, Inc.
4
5 The GNU plotutils package is free software. You may redistribute it
6 and/or modify it under the terms of the GNU General Public License as
7 published by the Free Software foundation; either version 2, or (at your
8 option) any later version.
9
10 The GNU plotutils package is distributed in the hope that it will be
11 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
14
15 You should have received a copy of the GNU General Public License along
16 with the GNU plotutils package; see the file COPYING. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin St., Fifth Floor,
18 Boston, MA 02110-1301, USA. */
19
20 /* This file contains the internal paint_path() and paint_paths() methods,
21 which the public method endpath() is a wrapper around. */
22
23 /* This version of paint_path() is for SVGPlotters. It renders a libplot
24 path in terms of SVG shapes:
25 path/rect/circle/ellipse/line/polyline/polygon. */
26
27 #include "sys-defines.h"
28 #include "extern.h"
29
30 /* SVG join styles, i.e., stroke-linejoin attribute, indexed by internal
31 number (miter/rd./bevel/triangular) */
32 static const char * const svg_join_style[PL_NUM_JOIN_TYPES] =
33 { "miter", "round", "bevel", "round" };
34
35 /* SVG cap styles, i.e., stroke-linecap attribute, indexed by internal
36 number (butt/rd./project/triangular) */
37 static const char * const svg_cap_style[PL_NUM_CAP_TYPES] =
38 { "butt", "round", "square", "round" };
39
40 /* SVG fill rule styles, i.e., fill-rule attribute, indexed by internal
41 number (evenodd/nonzero winding number) */
42 static const char * const svg_fill_style[PL_NUM_FILL_RULES] =
43 { "evenodd", "nonzero" };
44
45 static const double identity_matrix[6] = { 1.0, 0.0, 0.0, 1.0, 0.0, 0.0 };
46
47 /* forward references */
48 static void write_svg_path_data (plOutbuf *page, const plPath *path);
49 static void write_svg_path_style (plOutbuf *page, const plDrawState *drawstate, bool need_cap, bool need_join);
50
51 void
_pl_s_paint_path(S___ (Plotter * _plotter))52 _pl_s_paint_path (S___(Plotter *_plotter))
53 {
54 switch ((int)_plotter->drawstate->path->type)
55 {
56 case (int)PATH_SEGMENT_LIST:
57 {
58 bool closed, lines_only;
59 int i;
60
61 /* sanity checks */
62 if (_plotter->drawstate->path->num_segments == 0)/* nothing to do */
63 break;
64 if (_plotter->drawstate->path->num_segments == 1) /*shouldn't happen */
65 break;
66
67 if ((_plotter->drawstate->path->num_segments >= 3)/*check for closure*/
68 && (_plotter->drawstate->path->segments[_plotter->drawstate->path->num_segments - 1].p.x == _plotter->drawstate->path->segments[0].p.x)
69 && (_plotter->drawstate->path->segments[_plotter->drawstate->path->num_segments - 1].p.y == _plotter->drawstate->path->segments[0].p.y))
70 closed = true;
71 else
72 closed = false; /* 2-point ones should be open */
73
74 /* determine which sort of SVG primitive shape this should be:
75 line/polyline/polygon, or general path */
76
77 lines_only = true;
78 for (i = 1; i < _plotter->drawstate->path->num_segments; i++)
79 {
80 plPathSegmentType element_type;
81
82 element_type = _plotter->drawstate->path->segments[i].type;
83 if (element_type != S_LINE)
84 {
85 lines_only = false;
86 break;
87 }
88 }
89
90 if (lines_only && _plotter->drawstate->path->num_segments == 2)
91 /* SVG line */
92 {
93 sprintf (_plotter->data->page->point, "<line ");
94 _update_buffer (_plotter->data->page);
95
96 _pl_s_set_matrix (R___(_plotter) identity_matrix);
97
98 sprintf (_plotter->data->page->point,
99 "x1=\"%.5g\" y1=\"%.5g\" x2=\"%.5g\" y2=\"%.5g\" ",
100 _plotter->drawstate->path->segments[0].p.x,
101 _plotter->drawstate->path->segments[0].p.y,
102 _plotter->drawstate->path->segments[1].p.x,
103 _plotter->drawstate->path->segments[1].p.y);
104 _update_buffer (_plotter->data->page);
105
106 write_svg_path_style (_plotter->data->page, _plotter->drawstate,
107 true, false);
108
109 sprintf (_plotter->data->page->point, "/>\n");
110 _update_buffer (_plotter->data->page);
111 }
112
113 else if (lines_only && !closed)
114 /* SVG polyline */
115 {
116 sprintf (_plotter->data->page->point, "<polyline ");
117 _update_buffer (_plotter->data->page);
118
119 _pl_s_set_matrix (R___(_plotter) identity_matrix);
120
121 sprintf (_plotter->data->page->point,
122 "points=\"");
123 _update_buffer (_plotter->data->page);
124 for (i = 0; i < _plotter->drawstate->path->num_segments; i++)
125 {
126 plPoint p;
127
128 p = _plotter->drawstate->path->segments[i].p;
129 sprintf (_plotter->data->page->point,
130 "%.5g,%.5g ",
131 p.x, p.y);
132 _update_buffer (_plotter->data->page);
133 }
134 sprintf (_plotter->data->page->point,
135 "\" ");
136 _update_buffer (_plotter->data->page);
137
138 write_svg_path_style (_plotter->data->page, _plotter->drawstate,
139 true, true);
140
141 sprintf (_plotter->data->page->point,
142 "/>\n");
143 _update_buffer (_plotter->data->page);
144 }
145
146 else if (lines_only && closed)
147 /* SVG polygon */
148 {
149 sprintf (_plotter->data->page->point, "<polygon ");
150 _update_buffer (_plotter->data->page);
151
152 _pl_s_set_matrix (R___(_plotter) identity_matrix);
153
154 sprintf (_plotter->data->page->point,
155 "points=\"");
156 _update_buffer (_plotter->data->page);
157 for (i = 0; i < _plotter->drawstate->path->num_segments - 1; i++)
158 {
159 plPoint p;
160
161 p = _plotter->drawstate->path->segments[i].p;
162 sprintf (_plotter->data->page->point,
163 "%.5g,%.5g ",
164 p.x, p.y);
165 _update_buffer (_plotter->data->page);
166 }
167 sprintf (_plotter->data->page->point,
168 "\" ");
169 _update_buffer (_plotter->data->page);
170
171 write_svg_path_style (_plotter->data->page, _plotter->drawstate,
172 false, true);
173
174 sprintf (_plotter->data->page->point,
175 "/>\n");
176 _update_buffer (_plotter->data->page);
177 }
178
179 else
180 /* general SVG path */
181 {
182 sprintf (_plotter->data->page->point, "<path ");
183 _update_buffer (_plotter->data->page);
184
185 _pl_s_set_matrix (R___(_plotter) identity_matrix);
186
187 sprintf (_plotter->data->page->point,
188 "d=\"");
189 _update_buffer (_plotter->data->page);
190
191 /* write SVG path data string */
192 write_svg_path_data (_plotter->data->page,
193 _plotter->drawstate->path);
194
195 sprintf (_plotter->data->page->point,
196 "\" ");
197 _update_buffer (_plotter->data->page);
198
199 write_svg_path_style (_plotter->data->page, _plotter->drawstate,
200 true, true);
201
202 sprintf (_plotter->data->page->point,
203 "/>\n");
204 _update_buffer (_plotter->data->page);
205 }
206 }
207 break;
208
209 case (int)PATH_BOX:
210 {
211 plPoint p0, p1;
212 double xmin, ymin, xmax, ymax;
213
214 p0 = _plotter->drawstate->path->p0;
215 p1 = _plotter->drawstate->path->p1;
216 xmin = DMIN(p0.x, p1.x);
217 ymin = DMIN(p0.y, p1.y);
218 xmax = DMAX(p0.x, p1.x);
219 ymax = DMAX(p0.y, p1.y);
220
221 sprintf (_plotter->data->page->point, "<rect ");
222 _update_buffer (_plotter->data->page);
223
224 _pl_s_set_matrix (R___(_plotter) identity_matrix);
225
226 sprintf (_plotter->data->page->point,
227 "x=\"%.5g\" y=\"%.5g\" width=\"%.5g\" height=\"%.5g\" ",
228 xmin, ymin, xmax - xmin, ymax - ymin);
229 _update_buffer (_plotter->data->page);
230
231 write_svg_path_style (_plotter->data->page, _plotter->drawstate,
232 false, true);
233 sprintf (_plotter->data->page->point,
234 "/>\n");
235 _update_buffer (_plotter->data->page);
236 }
237 break;
238
239 case (int)PATH_CIRCLE:
240 {
241 plPoint pc;
242 double radius = _plotter->drawstate->path->radius;
243
244 sprintf (_plotter->data->page->point, "<circle ");
245 _update_buffer (_plotter->data->page);
246
247 _pl_s_set_matrix (R___(_plotter) identity_matrix);
248
249 pc = _plotter->drawstate->path->pc;
250 sprintf (_plotter->data->page->point,
251 "cx=\"%.5g\" cy=\"%.5g\" r=\"%.5g\" ",
252 pc.x, pc.y, radius);
253 _update_buffer (_plotter->data->page);
254
255 write_svg_path_style (_plotter->data->page, _plotter->drawstate,
256 false, false);
257
258 sprintf (_plotter->data->page->point,
259 "/>\n");
260 _update_buffer (_plotter->data->page);
261 }
262 break;
263
264 case (int)PATH_ELLIPSE:
265 {
266 plPoint pc;
267 double rx = _plotter->drawstate->path->rx;
268 double ry = _plotter->drawstate->path->ry;
269 double angle = _plotter->drawstate->path->angle;
270 double local_matrix[6];
271
272 sprintf (_plotter->data->page->point, "<ellipse ");
273 _update_buffer (_plotter->data->page);
274
275 pc = _plotter->drawstate->path->pc;
276 local_matrix[0] = cos (M_PI * angle / 180.0);
277 local_matrix[1] = sin (M_PI * angle / 180.0);
278 local_matrix[2] = -sin (M_PI * angle / 180.0);
279 local_matrix[3] = cos (M_PI * angle / 180.0);
280 local_matrix[4] = pc.x;
281 local_matrix[5] = pc.y;
282 _pl_s_set_matrix (R___(_plotter) local_matrix);
283
284 sprintf (_plotter->data->page->point, "rx=\"%.5g\" ry=\"%.5g\" ",
285 rx, ry);
286 _update_buffer (_plotter->data->page);
287
288 write_svg_path_style (_plotter->data->page, _plotter->drawstate,
289 false, false);
290
291 sprintf (_plotter->data->page->point, "/>\n");
292 _update_buffer (_plotter->data->page);
293 }
294 break;
295
296 default: /* shouldn't happen */
297 break;
298 }
299 }
300
301 bool
_pl_s_paint_paths(S___ (Plotter * _plotter))302 _pl_s_paint_paths (S___(Plotter *_plotter))
303 {
304 int i;
305
306 sprintf (_plotter->data->page->point,
307 "<path ");
308 _update_buffer (_plotter->data->page);
309
310 _pl_s_set_matrix (R___(_plotter) identity_matrix);
311
312 sprintf (_plotter->data->page->point,
313 "d=\"");
314 _update_buffer (_plotter->data->page);
315
316 for (i = 0; i < _plotter->drawstate->num_paths; i++)
317 {
318 plPath *path = _plotter->drawstate->paths[i];
319
320 switch ((int)path->type)
321 {
322 case (int)PATH_SEGMENT_LIST:
323 /* write SVG path data string */
324 write_svg_path_data (_plotter->data->page, path);
325 break;
326
327 case (int)PATH_CIRCLE:
328 /* draw as four quarter-circles */
329 {
330 plPoint pc;
331 double radius;
332
333 pc = path->pc;
334 radius = path->radius;
335 if (path->clockwise == false)
336 /* counter-clockwise */
337 sprintf (_plotter->data->page->point, "\
338 M%.5g,%.5g \
339 A%.5g,%.5g,%.5g,%d,%d,%.5g,%.5g \
340 A%.5g,%.5g,%.5g,%d,%d,%.5g,%.5g \
341 A%.5g,%.5g,%.5g,%d,%d,%.5g,%.5g \
342 A%.5g,%.5g,%.5g,%d,%d,%.5g,%.5g Z ",
343 pc.x + radius, pc.y,
344 radius, radius, 0.0, 0, 1, pc.x, pc.y + radius,
345 radius, radius, 0.0, 0, 1, pc.x - radius, pc.y,
346 radius, radius, 0.0, 0, 1, pc.x, pc.y - radius,
347 radius, radius, 0.0, 0, 1, pc.x + radius, pc.y);
348 else
349 /* clockwise */
350 sprintf (_plotter->data->page->point, "\
351 M%.5g,%.5g \
352 A%.5g,%.5g,%.5g,%d,%d,%.5g,%.5g \
353 A%.5g,%.5g,%.5g,%d,%d,%.5g,%.5g \
354 A%.5g,%.5g,%.5g,%d,%d,%.5g,%.5g \
355 A%.5g,%.5g,%.5g,%d,%d,%.5g,%.5g Z ",
356 pc.x + radius, pc.y,
357 radius, radius, 0.0, 0, 0, pc.x, pc.y - radius,
358 radius, radius, 0.0, 0, 0, pc.x - radius, pc.y,
359 radius, radius, 0.0, 0, 0, pc.x, pc.y + radius,
360 radius, radius, 0.0, 0, 0, pc.x + radius, pc.y);
361 _update_buffer (_plotter->data->page);
362 }
363 break;
364
365 case (int)PATH_ELLIPSE:
366 /* draw as four quarter-ellipses */
367 {
368 plPoint pc;
369 double rx, ry, angle;
370 plVector v1, v2;
371
372 pc = path->pc;
373 rx = path->rx;
374 ry = path->ry;
375 angle = path->angle;
376 v1.x = rx * cos (M_PI * angle / 180.0);
377 v1.y = rx * sin (M_PI * angle / 180.0);
378 v2.x = -ry * sin (M_PI * angle / 180.0);
379 v2.y = ry * cos (M_PI * angle / 180.0);
380
381 if (path->clockwise == false)
382 /* counter-clockwise */
383 sprintf (_plotter->data->page->point, "\
384 M%.5g,%.5g \
385 A%.5g,%.5g,%.5g,%d,%d,%.5g,%.5g \
386 A%.5g,%.5g,%.5g,%d,%d,%.5g,%.5g \
387 A%.5g,%.5g,%.5g,%d,%d,%.5g,%.5g \
388 A%.5g,%.5g,%.5g,%d,%d,%.5g,%.5g Z ",
389 pc.x + v1.x, pc.y + v1.y,
390 rx, ry, 0.0, 0, 1, pc.x + v2.x, pc.y + v2.y,
391 rx, ry, 0.0, 0, 1, pc.x - v1.x, pc.y - v1.y,
392 rx, ry, 0.0, 0, 1, pc.x - v2.x, pc.y - v2.y,
393 rx, ry, 0.0, 0, 1, pc.x + v1.x, pc.y + v1.y);
394 else
395 /* clockwise */
396 sprintf (_plotter->data->page->point, "\
397 M%.5g,%.5g \
398 A%.5g,%.5g,%.5g,%d,%d,%.5g,%.5g \
399 A%.5g,%.5g,%.5g,%d,%d,%.5g,%.5g \
400 A%.5g,%.5g,%.5g,%d,%d,%.5g,%.5g \
401 A%.5g,%.5g,%.5g,%d,%d,%.5g,%.5g Z ",
402 pc.x + v1.x, pc.y + v1.y,
403 rx, ry, 0.0, 0, 0, pc.x - v2.x, pc.y - v2.y,
404 rx, ry, 0.0, 0, 0, pc.x - v1.x, pc.y - v1.y,
405 rx, ry, 0.0, 0, 0, pc.x + v2.x, pc.y + v2.y,
406 rx, ry, 0.0, 0, 0, pc.x + v1.x, pc.y + v1.y);
407 _update_buffer (_plotter->data->page);
408 }
409 break;
410
411 case (int)PATH_BOX:
412 {
413 plPoint p0, p1;
414 bool x_move_is_first;
415
416 p0 = path->p0;
417 p1 = path->p1;
418
419 /* if counterclockwise, would first pen motion be in x
420 direction? */
421 x_move_is_first = ((p1.x >= p0.x && p1.y >= p0.y)
422 || (p1.x < p0.x && p1.y < p0.y) ? true : false);
423
424 if (path->clockwise)
425 /* take complement */
426 x_move_is_first = (x_move_is_first == true ? false : true);
427
428 if (x_move_is_first)
429 sprintf (_plotter->data->page->point,
430 "M%.5g,%.5g H%.5g V%.5g H%.5g Z ",
431 p0.x, p0.y, p1.x, p1.y, p0.x);
432 else
433 sprintf (_plotter->data->page->point,
434 "M%.5g,%.5g V%.5g H%.5g V%.5g Z ",
435 p0.x, p0.y, p1.y, p1.x, p0.y);
436 _update_buffer (_plotter->data->page);
437 }
438 break;
439
440 default: /* shouldn't happen */
441 break;
442 }
443 }
444 sprintf (_plotter->data->page->point,
445 "\" ");
446 _update_buffer (_plotter->data->page);
447
448 write_svg_path_style (_plotter->data->page, _plotter->drawstate,
449 true, true);
450
451 sprintf (_plotter->data->page->point,
452 "/>\n");
453 _update_buffer (_plotter->data->page);
454
455 return true;
456 }
457
458 /* Write an SVG path data string that specifies a single simple path. This
459 may be called only on a libplot segment-list path, not on a libplot path
460 that consists of a single closed path primitive (box/circle/ellipse). */
461
462 static void
write_svg_path_data(plOutbuf * page,const plPath * path)463 write_svg_path_data (plOutbuf *page, const plPath *path)
464 {
465 bool closed;
466 plPoint p, oldpoint;
467 int i;
468
469 /* sanity check */
470 if (path->type != PATH_SEGMENT_LIST)
471 return;
472
473 if ((path->num_segments >= 3) /* check for closure */
474 && (path->segments[path->num_segments - 1].p.x == path->segments[0].p.x)
475 && (path->segments[path->num_segments - 1].p.y == path->segments[0].p.y))
476 closed = true;
477 else
478 closed = false; /* 2-point ones should be open */
479
480 p = path->segments[0].p; /* initial seg should be a moveto */
481 sprintf (page->point, "M%.5g,%.5g ",
482 p.x, p.y);
483 _update_buffer (page);
484
485 oldpoint = p;
486 for (i = 1; i < path->num_segments; i++)
487 {
488 plPathSegmentType type;
489 plPoint pc, pd;
490
491 type = path->segments[i].type;
492 p = path->segments[i].p;
493 pc = path->segments[i].pc;
494 pd = path->segments[i].pd;
495
496 if (closed
497 && i == path->num_segments - 1
498 && type == S_LINE)
499 continue; /* i.e. don't end with line-as-closepath */
500
501 switch ((int)type)
502 {
503 case (int)S_LINE:
504 if (p.y == oldpoint.y)
505 sprintf (page->point, "H%.5g ",
506 p.x);
507 else if (p.x == oldpoint.x)
508 sprintf (page->point, "V%.5g ",
509 p.y);
510 else
511 sprintf (page->point, "L%.5g,%.5g ",
512 p.x, p.y);
513 break;
514
515 case (int)S_ARC:
516 {
517 double radius;
518 double angle;
519
520 /* compute angle in radians, range -pi..pi */
521 angle = _angle_of_arc (oldpoint, p, pc);
522
523 radius = sqrt ((p.x - pc.x)*(p.x - pc.x)
524 + (p.y - pc.y)*(p.y - pc.y));
525 sprintf (page->point, "A%.5g,%.5g,%.5g,%d,%d,%.5g,%.5g ",
526 radius, radius,
527 0.0, /* rotation of x-axis of ellipse */
528 0, /* large-arc-flag, 0/1 = small/large */
529 angle >= 0.0 ? 1 : 0,/* sweep-flag, 0/1 = clock/c'clock */
530 p.x, p.y);
531 }
532 break;
533
534 case (int)S_ELLARC:
535 {
536 double cross, mixing_angle, rx, ry, theta;
537 plVector u, v, semi_axis_1, semi_axis_2;
538 bool clockwise;
539
540 /* conjugate radial vectors for the quarter-ellipse */
541 u.x = oldpoint.x - pc.x;
542 u.y = oldpoint.y - pc.y;
543 v.x = p.x - pc.x;
544 v.y = p.y - pc.y;
545 cross = u.x * v.y - v.x * u.y;
546 clockwise = cross < 0.0 ? true : false;
547
548 /* angle by which they should be mixed, to yield vectors along
549 the major and minor axes */
550 mixing_angle = 0.5 * _xatan2 (2.0 * (u.x * v.x + u.y * v.y),
551 u.x * u.x + u.y * u.y
552 - v.x * v.x + v.y * v.y);
553
554 /* semi-axis vectors */
555 semi_axis_1.x = u.x * cos(mixing_angle) + v.x * sin(mixing_angle);
556 semi_axis_1.y = u.y * cos(mixing_angle) + v.y * sin(mixing_angle);
557 semi_axis_2.x = (u.x * cos(mixing_angle + M_PI_2)
558 + v.x * sin(mixing_angle + M_PI_2));
559 semi_axis_2.y = (u.y * cos(mixing_angle + M_PI_2)
560 + v.y * sin(mixing_angle + M_PI_2));
561
562 /* semi-axis lengths */
563 rx = sqrt (semi_axis_1.x * semi_axis_1.x
564 + semi_axis_1.y * semi_axis_1.y);
565 ry = sqrt (semi_axis_2.x * semi_axis_2.x
566 + semi_axis_2.y * semi_axis_2.y);
567
568 /* angle of inclination of first semi-axis */
569 theta = _xatan2 (semi_axis_1.y, semi_axis_1.x);
570
571 /* compensate for possible roundoff error: treat a very small inclination
572 angle of the 1st semi-axis, relative to the x-axis, as zero */
573 #define VERY_SMALL_ANGLE 1e-10
574
575 if (theta < VERY_SMALL_ANGLE && theta > -(VERY_SMALL_ANGLE))
576 theta = 0.0;
577
578 sprintf (page->point, "A%.5g,%.5g,%.5g,%d,%d,%.5g,%.5g ",
579 rx, ry,
580 theta * 180.0 / M_PI, /* rotation of x-axis of ellipse */
581 0, /* large-arc-flag, 0/1 = small/large */
582 clockwise ? 0 : 1, /* sweep-flag, 0/1 = clock/c'clock */
583 p.x, p.y);
584 }
585 break;
586
587 case (int)S_QUAD:
588 sprintf (page->point, "Q%.5g,%.5g,%.5g,%.5g ",
589 pc.x, pc.y, p.x, p.y);
590 break;
591
592 case (int)S_CUBIC:
593 sprintf (page->point, "C%.5g,%.5g,%.5g,%.5g,%.5g,%.5g ",
594 pc.x, pc.y, pd.x, pd.y, p.x, p.y);
595 break;
596
597 default: /* shouldn't happen */
598 break;
599 }
600 _update_buffer (page);
601
602 oldpoint = p;
603 }
604
605 if (closed)
606 {
607 sprintf (page->point, "Z ");
608 _update_buffer (page);
609 }
610 }
611
612 static void
write_svg_path_style(plOutbuf * page,const plDrawState * drawstate,bool need_cap,bool need_join)613 write_svg_path_style (plOutbuf *page, const plDrawState *drawstate, bool need_cap, bool need_join)
614 {
615 char color_buf[8]; /* enough room for "#ffffff", incl. NUL */
616
617 if (drawstate->pen_type)
618 {
619 if (drawstate->fgcolor.red != 0
620 || drawstate->fgcolor.green != 0
621 || drawstate->fgcolor.blue != 0)
622 /* non-black, i.e. non-default */
623 {
624 sprintf (page->point, "stroke=\"%s\" ",
625 _libplot_color_to_svg_color (drawstate->fgcolor,
626 color_buf));
627 _update_buffer (page);
628 }
629
630 /* should use `px' here to specify user units, per the SVG Authoring
631 Guide, but ImageMagick objects to that */
632 sprintf (page->point, "stroke-width=\"%.5g\" ",
633 drawstate->line_width);
634 _update_buffer (page);
635
636 if (need_cap)
637 {
638 if (drawstate->cap_type != PL_CAP_BUTT) /* i.e. not default */
639 {
640 sprintf (page->point, "stroke-linecap=\"%s\" ",
641 svg_cap_style[drawstate->cap_type]);
642 _update_buffer (page);
643 }
644 }
645
646 if (need_join)
647 {
648 if (drawstate->join_type != PL_JOIN_MITER) /* i.e. not default */
649 {
650 sprintf (page->point, "stroke-linejoin=\"%s\" ",
651 svg_join_style[drawstate->join_type]);
652 _update_buffer (page);
653 }
654
655 if (drawstate->join_type == PL_JOIN_MITER
656 && drawstate->miter_limit != PL_DEFAULT_MITER_LIMIT)
657 {
658 sprintf (page->point, "stroke-miterlimit=\"%.5g\" ",
659 drawstate->miter_limit);
660 _update_buffer (page);
661 }
662 }
663
664 if ((drawstate->dash_array_in_effect /* user-specified dash array */
665 && drawstate->dash_array_len > 0)
666 ||
667 (drawstate->dash_array_in_effect == false
668 && drawstate->line_type != PL_L_SOLID)) /* non-solid builtin linetype*/
669 /* need to specify stroke-array, maybe stroke-offset too */
670 {
671 int i;
672 double *dashbuf, offset;
673 int num_dashes;
674
675 if (drawstate->dash_array_in_effect)
676 {
677 dashbuf = (double *)(drawstate->dash_array);
678 num_dashes = drawstate->dash_array_len;
679 offset = drawstate->dash_offset;
680 }
681 else
682 /* builtin line type, handcraft a SVG-style dash array for it */
683 {
684 const int *dash_array;
685 double min_sing_val, max_sing_val, min_width, scale;
686
687 /* compute maximum singular value of user->device coordinate
688 map, which we use as a divisive factor to convert size in
689 NCD frame back to size in the user frame */
690 _matrix_sing_vals (drawstate->transform.m_user_to_ndc,
691 &min_sing_val, &max_sing_val);
692 if (max_sing_val != 0.0)
693 min_width =
694 PL_DEFAULT_LINE_WIDTH_AS_FRACTION_OF_DISPLAY_SIZE / max_sing_val;
695 else
696 min_width = 0.0;
697 scale = DMAX(drawstate->line_width, min_width);
698
699 /* take normalized dash array (linemode-specific) from
700 internal table */
701 dash_array =
702 _pl_g_line_styles[drawstate->line_type].dash_array;
703 num_dashes =
704 _pl_g_line_styles[drawstate->line_type].dash_array_len;
705 dashbuf = (double *)_pl_xmalloc (num_dashes * sizeof(double));
706
707 /* scale length of each dash by current line width, unless
708 it's too small (see above computation) */
709 for (i = 0; i < num_dashes; i++)
710 dashbuf[i] = scale * dash_array[i];
711 offset = 0.0; /* true for all builtin line types */
712 }
713
714 sprintf (page->point, "stroke-dasharray=\"");
715 _update_buffer (page);
716 for (i = 0; i < num_dashes; i++)
717 {
718 sprintf (page->point, "%.5g%s",
719 dashbuf[i],
720 i < num_dashes - 1 ? ", " : "\"");
721 _update_buffer (page);
722 }
723
724 if (offset != 0.0) /* not default */
725 {
726 /* should use `px' here to specify user units, per the SVG
727 Authoring Guide, but ImageMagick objects to that */
728 sprintf (page->point, "stroke-dashoffset=\"%.5g\" ",
729 offset);
730 _update_buffer (page);
731 }
732
733 if (drawstate->dash_array_in_effect == false)
734 /* have a handcrafted dash array to free */
735 free (dashbuf);
736 }
737 else
738 /* solid, so don't specify stroke-dasharray or stroke-offset */
739 {
740 }
741 }
742 else
743 {
744 sprintf (page->point, "stroke=\"none\" ");
745 _update_buffer (page);
746 }
747
748 if (drawstate->fill_type)
749 {
750 sprintf (page->point, "fill=\"%s\" ",
751 _libplot_color_to_svg_color (drawstate->fillcolor, color_buf));
752 _update_buffer (page);
753
754 if (drawstate->fill_rule_type != PL_FILL_ODD_WINDING) /* not default */
755 {
756 sprintf (page->point, "fill-rule=\"%s\" ",
757 svg_fill_style[drawstate->fill_rule_type]);
758 _update_buffer (page);
759 }
760 }
761 }
762