1 /* This file is part of the GNU plotutils package.  Copyright (C) 1995,
2    1996, 1997, 1998, 1999, 2000, 2005, 2008, Free Software Foundation, Inc.
3 
4    The GNU plotutils package is free software.  You may redistribute it
5    and/or modify it under the terms of the GNU General Public License as
6    published by the Free Software foundation; either version 2, or (at your
7    option) any later version.
8 
9    The GNU plotutils package is distributed in the hope that it will be
10    useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License along
15    with the GNU plotutils package; see the file COPYING.  If not, write to
16    the Free Software Foundation, Inc., 51 Franklin St., Fifth Floor,
17    Boston, MA 02110-1301, USA. */
18 
19 #include "sys-defines.h"
20 #include "extern.h"
21 
22 /* forward references */
23 static void write_svg_transform (plOutbuf *outbuf, const double m[6]);
24 
25 bool
_pl_s_end_page(S___ (Plotter * _plotter))26 _pl_s_end_page (S___(Plotter *_plotter))
27 {
28   plOutbuf *svg_header, *svg_trailer;
29 
30   /* SVG files contain only one page of graphics so this is a sanity check */
31   if (_plotter->data->page_number != 1)
32     return true;
33 
34   /* prepare SVG header (i.e. page header), write it to a plOutbuf */
35   svg_header = _new_outbuf ();
36 
37   /* start with DTD */
38   sprintf (svg_header->point, "\
39 <?xml version=\"1.0\" encoding=\"ISO-8859-1\" standalone=\"no\"?>\n\
40 <!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
41   _update_buffer (svg_header);
42 
43   /* Emit nominal physical size of the device-frame viewport (and specify
44      that in the device-frame coordinates we use, it's a unit square).
45      viewport_{x,y}size are set from the PAGESIZE Plotter parameter, via
46      the xsize and ysize options, and either or both may be negative.  If
47      they are, we flipped the NDC_frame->device_frame map to compensate
48      (see s_defplot.c).  Which is why we can take absolute values here. */
49 
50   if (_plotter->data->page_data->metric)
51     sprintf (svg_header->point,
52 	     "<svg version=\"1.1\" baseProfile=\"full\" id=\"body\" width=\"%.5gcm\" height=\"%.5gcm\" ",
53 	     2.54 * FABS(_plotter->data->viewport_xsize),
54 	     2.54 * FABS(_plotter->data->viewport_ysize));
55   else
56     sprintf (svg_header->point,
57 	     "<svg version=\"1.1\" baseProfile=\"full\" id=\"body\" width=\"%.5gin\" height=\"%.5gin\" ",
58 	     FABS(_plotter->data->viewport_xsize),
59 	     FABS(_plotter->data->viewport_ysize));
60   _update_buffer (svg_header);
61   sprintf (svg_header->point,
62 	   "%s %s %s %s %s>\n",
63 	   "viewBox=\"0 0 1 1\"",
64 	   "preserveAspectRatio=\"none\"",
65 	   /* bind SVG namespace */
66 	   "xmlns=\"http://www.w3.org/2000/svg\"",
67 	   /* bind XLink and XML Events namespaces for good measure */
68 	   "xmlns:xlink=\"http://www.w3.org/1999/xlink\"",
69 	   "xmlns:ev=\"http://www.w3.org/2001/xml-events\"");
70   _update_buffer (svg_header);
71 
72   sprintf (svg_header->point, "<title>SVG drawing</title>\n");
73   _update_buffer (svg_header);
74 
75   sprintf (svg_header->point, "<desc>This was produced by version %s of GNU libplot, a free library for exporting 2-D vector graphics.</desc>\n",
76 	   PL_LIBPLOT_VER_STRING);
77   _update_buffer (svg_header);
78 
79   if (_plotter->s_bgcolor_suppressed == false)
80   /* place a background rectangle behind, covering entire viewport */
81     {
82       char color_buf[8];	/* enough room for "#ffffff", incl. NUL */
83 
84       sprintf (svg_header->point,
85 	       "<rect id=\"background\" x=\"0\" y=\"0\" width=\"1\" height=\"1\" stroke=\"none\" fill=\"%s\"/>\n",
86 	       _libplot_color_to_svg_color (_plotter->s_bgcolor, color_buf));
87       _update_buffer (svg_header);
88     }
89 
90   /* enclose everything else in a container */
91   sprintf (svg_header->point, "<g id=\"content\" ");
92   _update_buffer (svg_header);
93 
94   if (_plotter->s_matrix_is_unknown == false
95       && _plotter->s_matrix_is_bogus == false)
96     /* Place a transform in the container: this page's default
97        transformation matrix, which is simply the transformation matrix
98        attribute of the very first graphical object plotted on the page.
99 
100        In libplot, `transformation matrix attribute' refers to the affine
101        map from user space to NDC space.  So we're careful to multiply by
102        `m_ndc_to_device', which transforms NDC space to device space.
103        Because SVG uses a flipped-y convention, `m_ndc_to_device' flips the
104        y coordinate.  (There will be additional flipping if the
105        user-specified xsize, ysize are negative; see s_defplot.c.  Also, if
106        the ROTATION Plotter parameter is specified by the user, it may
107        rotate.) */
108     {
109       double product[6];
110 
111       _matrix_product (_plotter->s_matrix, _plotter->data->m_ndc_to_device,
112 		       product);
113       write_svg_transform (svg_header, product);
114     }
115 
116   /* turn off SVG's default [unfortunate] XML-inherited treatment of spaces */
117   sprintf (svg_header->point, "xml:space=\"preserve\" ");
118   _update_buffer (svg_header);
119 
120   /* specify style properties (all libplot defaults) */
121 
122   sprintf (svg_header->point, "stroke=\"%s\" ",
123 	   "black");
124   _update_buffer (svg_header);
125 
126   sprintf (svg_header->point, "stroke-linecap=\"%s\" ",
127 	   "butt");
128   _update_buffer (svg_header);
129 
130   sprintf (svg_header->point, "stroke-linejoin=\"%s\" ",
131 	   "miter");
132   _update_buffer (svg_header);
133 
134   sprintf (svg_header->point, "stroke-miterlimit=\"%.5g\" ",
135 	   PL_DEFAULT_MITER_LIMIT);
136   _update_buffer (svg_header);
137 
138   sprintf (svg_header->point, "stroke-dasharray=\"%s\" ",
139 	   "none");
140   _update_buffer (svg_header);
141 
142   /* should use `px' here to specify user units, per the SVG Authoring
143      Guide, but ImageMagick objects to that */
144   sprintf (svg_header->point, "stroke-dashoffset=\"%.5g\" ",
145 	   0.0);
146   _update_buffer (svg_header);
147 
148   sprintf (svg_header->point, "stroke-opacity=\"%.5g\" ",
149 	   1.0);
150   _update_buffer (svg_header);
151 
152   sprintf (svg_header->point, "fill=\"%s\" ",
153 	   "none");
154   _update_buffer (svg_header);
155 
156   sprintf (svg_header->point, "fill-rule=\"%s\" ",
157 	   "evenodd");
158   _update_buffer (svg_header);
159 
160   sprintf (svg_header->point, "fill-opacity=\"%.5g\" ",
161 	   1.0);
162   _update_buffer (svg_header);
163 
164   sprintf (svg_header->point, "font-style=\"%s\" ",
165 	   "normal");
166   _update_buffer (svg_header);
167 
168   sprintf (svg_header->point, "font-variant=\"%s\" ",
169 	   "normal");
170   _update_buffer (svg_header);
171 
172   sprintf (svg_header->point, "font-weight=\"%s\" ",
173 	   "normal");
174   _update_buffer (svg_header);
175 
176   sprintf (svg_header->point, "font-stretch=\"%s\" ",
177 	   "normal");
178   _update_buffer (svg_header);
179 
180   sprintf (svg_header->point, "font-size-adjust=\"%s\" ",
181 	   "none");
182   _update_buffer (svg_header);
183 
184   sprintf (svg_header->point, "letter-spacing=\"%s\" ",
185 	   "normal");
186   _update_buffer (svg_header);
187 
188   sprintf (svg_header->point, "word-spacing=\"%s\" ",
189 	   "normal");
190   _update_buffer (svg_header);
191 
192   sprintf (svg_header->point, "text-anchor=\"%s\"",
193 	   "start");
194   _update_buffer (svg_header);
195 
196   sprintf (svg_header->point, ">\n");
197   _update_buffer (svg_header);
198 
199   /* place SVG header in this page's plOutbuf */
200   _plotter->data->page->header = svg_header;
201 
202   /* prepare SVG trailer too, write it to a plOutbuf */
203   svg_trailer = _new_outbuf ();
204 
205   sprintf (svg_trailer->point, "</g>\n");
206   _update_buffer (svg_trailer);
207 
208   sprintf (svg_trailer->point, "</svg>\n");
209   _update_buffer (svg_trailer);
210 
211   /* place SVG trailer in this page's plOutbuf */
212   _plotter->data->page->trailer = svg_trailer;
213 
214   return true;
215 }
216 
217 /* This function is invoked while writing any graphical object on a page to
218    the page's output buffer.  It emits the string "transform=\"...\" ",
219    where the "\"...\"" is computed from a transformation matrix attribute
220    of the object, which is passed.  I.e., it transforms a per-object
221    transformation matrix to an SVG-style transformation matrix, and emits
222    the latter as an SVG element attribute.  The per-object transformation
223    matrix is always the identity, except for rotated text strings and
224    ellipses.
225 
226    This code evaluates the SVG transformation matrix as the composition of
227    two transformations: the local transformation, which acts first (in user
228    space), which is passed as an argument; and a 2nd transformation, which
229    is the current transformation from user to NDC coordinates.  Typically,
230    it's the 1st which this code emits as the value of the `transform'
231    attribute.  That's because when this is called for the first time on a
232    page (or newly erased page), the 2nd is stored in `s_matrix', the global
233    transformation matrix for the page, which will later be written at the
234    head of the SVG code for the page when closepl() is invoked (see above).
235 
236    This separation of the two distinct transformations will of course work
237    only if the 2nd doesn't change from object to object on the page.  For
238    this reason, what's actually emitted as the value of the SVG transform
239    attribute is a composite transformation, made up in succession of
240 
241    (1) the passed per-object transformation
242    (2) the current value of the transformation from user to NDC coordinates
243    (3) the inverse of s_matrix.
244 
245    If the user space -> NDC space map is the same for all objects on the
246    page, then (2) and (3) will cancel each other out for all objects on
247    the page.
248 
249    Note that in this code we flag `s_matrix' as bogus if it's singular.  If
250    it's bogus, it won't be written out when closepl() is invoked, and the
251    global transformation matrix of the page will effectively be the
252    identity (i.e., we'll punt). */
253 
254 void
_pl_s_set_matrix(R___ (Plotter * _plotter)const double m_local[6])255 _pl_s_set_matrix (R___(Plotter *_plotter) const double m_local[6])
256 {
257   double m_base[6], m[6];
258   const double *m_emitted = (const double *)NULL; /* keep compiler happy */
259   bool need_transform_attribute = false;
260   int i;
261 
262   for (i = 0; i < 6; i++)
263     m_base[i] = _plotter->drawstate->transform.m_user_to_ndc[i];
264 
265   /* if this is the first time this function is invoked on a page (or newly
266      erased page), store the current user-to-NDC matrix for later use as
267      the global transformation matrix for the page */
268   if (_plotter->s_matrix_is_unknown)
269     {
270       for (i = 0; i < 6; i++)
271 	_plotter->s_matrix[i] = m_base[i];
272 
273       _plotter->s_matrix_is_unknown = false;
274 
275       if (m_base[0] * m_base[3] - m_base[1] * m_base[2] == 0.0)
276 	/* singular, won't be used even though stored */
277 	_plotter->s_matrix_is_bogus = true;
278     }
279 
280   /* compute product: current transformation matrix (in the transformation
281      from user to NDC coors, local acts first, then base)  */
282   _matrix_product (m_local, m_base, m);
283 
284   /* determine whether current matrix is different from the global one that
285      will be wrapped around the entire page (if there is one) */
286 
287   if (_plotter->s_matrix_is_bogus == false)
288     /* have a global page-specific transformation matrix that will be
289        applied, so object's transform attribute may need to compensate */
290     {
291       for (i = 0; i < 6; i++)
292 	{
293 	  if (m[i] != _plotter->s_matrix[i])
294 	    /* different, so need to compensate */
295 	    {
296 	      need_transform_attribute = true;
297 	      break;
298 	    }
299 	}
300 
301       if (need_transform_attribute)
302 	{
303 	  double inverse_of_global[6], product[6];
304 
305 	  _matrix_inverse (_plotter->s_matrix, inverse_of_global);
306 
307 	  /* emitted transform attribute of object will be a product of
308 	     three matrices: (1) the passed matrix, (2) the current
309 	     user-to-NDC transformation matrix, and (3) the inverse of the
310 	     global transformation matrix */
311 	  _matrix_product (m, inverse_of_global, product);
312 	  m_emitted = product;
313 	}
314     }
315   else
316     /* no global transformation matrix for this page (no doubt because of
317        the abovementioned non-invertibility problem), so object's transform
318        attribute will simply be the current matrix */
319     {
320       need_transform_attribute = true;
321       m_emitted = m;
322     }
323 
324   /* emit object's transform attribute if it's not the identity */
325   if (need_transform_attribute)
326     write_svg_transform (_plotter->data->page, m_emitted);
327 }
328 
329 /* Internal function for writing out a PS-style affine transformation as a
330    SVG-style affine transformation.  If matrix is the identity, nothing is
331    written.
332 
333    In SVG format, the value of the `transform' attribute is a sequence of
334    transformations such as `rotate', `scale', and `translate', where the
335    sequence (as a composite transformation from user space to device [NDC]
336    space) is read from right to left.  This is the opposite of the PS
337    convention.  SVG documentation uses column vectors, while PS
338    documentation uses row vectors.
339 
340    Presumably the SVG convention arose from a desire to make the
341    `nestedness' of the transform attribute, implemented as the computation
342    of a composite transformation, more intuitive. */
343 
344 static void
write_svg_transform(plOutbuf * outbuf,const double m[6])345 write_svg_transform (plOutbuf *outbuf, const double m[6])
346 {
347   double mm[6];
348   double max_value = 0.0;
349   int i;
350   int type = 0;			/* default */
351 
352   /* compensate for possible roundoff error: treat very small elements of
353      linear transformation (if any) as zero */
354 #define VERY_SMALL_FACTOR 1e-10
355 
356   for (i = 0; i < 4; i++)
357     max_value = DMAX(max_value, FABS(m[i]));
358   for (i = 0; i < 6; i++)
359     if (i < 4 && FABS(m[i]) < VERY_SMALL_FACTOR * max_value)
360       mm[i] = 0;
361     else
362       mm[i] = m[i];
363 
364   if (mm[0] == 1.0 && mm[1] == 0.0 && mm[2] == 0.0 && mm[3] == 1.0
365       && mm[4] == 0.0 && mm[5] == 0.0)
366     /* identity matrix, unnecessary to write it */
367     return;
368 
369   /* treat several types of affine transformation specially */
370 
371   if (mm[1] == 0.0 && mm[2] == 0.0)
372     type = 1;			/* scale + translation */
373 
374   else if (mm[0] == 0.0 && mm[1] == 1.0 && mm[2] == -1.0 && mm[3] == 0.0)
375     type = 2;			/* rotation by 90 + translation */
376   else if (mm[0] == 0.0 && mm[1] == -1.0 && mm[2] == 1.0 && mm[3] == 0.0)
377     type = 3;			/* rotation by 270 + translation */
378   else if (mm[0] == 0.0 && mm[1] == 1.0 && mm[2] == 1.0 && mm[3] == 0.0)
379     type = 4;			/* y-flip + rotation by 90 + translation */
380   else if (mm[0] == 0.0 && mm[1] == -1.0 && mm[2] == -1.0 && mm[3] == 0.0)
381     type = 5;			/* y-flip + rotation by 270 + translation */
382 
383   sprintf (outbuf->point, "transform=\"");
384   _update_buffer (outbuf);
385 
386   if (type != 0)
387     {
388       /* emit translation if any (SVG will perform it last, since SVG uses
389 	 opposite order from PS for multiplying matrices) */
390       if (mm[4] != 0.0 || mm[5] != 0.0)
391 	{
392 	  if (mm[5] == 0.0)
393 	    sprintf (outbuf->point, "translate(%.5g) ",
394 		     mm[4]);
395 	  else
396 	    sprintf (outbuf->point, "translate(%.5g,%.5g) ",
397 		     mm[4], mm[5]);
398 	  _update_buffer (outbuf);
399 	}
400 
401       switch (type)
402 	{
403 	case 1:
404 	  if (mm[0] != 1.0 || mm[3] != 1.0)
405 	    {
406 	      if (mm[3] == mm[0])
407 		sprintf (outbuf->point, "scale(%.5g) ",
408 			 mm[0]);
409 	      else if (mm[3] == -mm[0])
410 		{
411 		  if (mm[0] != 1.0)
412 		    sprintf (outbuf->point, "scale(1,-1) scale(%.5g) ",
413 			     mm[0]);
414 		  else
415 		    sprintf (outbuf->point, "scale(1,-1) ");
416 		}
417 	      else
418 		sprintf (outbuf->point, "scale(%.5g,%.5g) ",
419 			 mm[0], mm[3]);
420 	      _update_buffer (outbuf);
421 	    }
422 	  break;
423 
424 	case 2:
425 	  sprintf (outbuf->point, "rotate(90) ");
426 	  _update_buffer (outbuf);
427 	  break;
428 
429 	case 3:
430 	  sprintf (outbuf->point, "rotate(270) ");
431 	  _update_buffer (outbuf);
432 	  break;
433 
434 	case 4:
435 	  sprintf (outbuf->point, "rotate(90) scale(1,-1) ");
436 	  _update_buffer (outbuf);
437 	  break;
438 
439 	case 5:
440 	  sprintf (outbuf->point, "rotate(270) scale(1,-1) ");
441 	  _update_buffer (outbuf);
442 	  break;
443 
444 	default:		/* shouldn't happen */
445 	  break;
446 	}
447     }
448   else
449     /* general affine transformation */
450     {
451       sprintf (outbuf->point, "matrix(%.5g %.5g %.5g %.5g %.5g %.5g) ",
452 	       mm[0], mm[1], mm[2], mm[3], mm[4], mm[5]);
453       _update_buffer (outbuf);
454     }
455 
456   sprintf (outbuf->point, "\" ");
457   _update_buffer (outbuf);
458 }
459