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