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