PLplot  5.10.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros
svg.c
Go to the documentation of this file.
1 // November 8, 2006
2 //
3 // PLplot driver for SVG 1.1 (http://www.w3.org/Graphics/SVG/)
4 //
5 // Copyright (C) 2006 Hazen Babcock
6 //
7 // This file is part of PLplot.
8 //
9 // PLplot is free software; you can redistribute it and/or modify
10 // it under the terms of the GNU Library General Public License as published
11 // by the Free Software Foundation; either version 2 of the License, or
12 // (at your option) any later version.
13 //
14 // PLplot is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 // GNU Library General Public License for more details.
18 //
19 // You should have received a copy of the GNU Library General Public License
20 // along with PLplot; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 //
23 //
24 
25 //---------------------------------------------
26 // Header files, defines and local variables
27 // ---------------------------------------------
28 
29 #include <stdarg.h>
30 #include <math.h>
31 
32 // PLplot header files
33 
34 #include "plplotP.h"
35 #include "drivers.h"
36 
37 // constants
38 
39 #define SVG_Default_X 720
40 #define SVG_Default_Y 540
41 
42 #define POINTS_PER_INCH 72
43 
44 #define MAX_STRING_LEN 1000
45 
46 // This has been generated empirically by looking carefully at results from
47 // examples 1 and 2.
48 
49 #define FONT_SIZE_RATIO 1.34
50 #define FONT_SHIFT_RATIO 0.705
51 #define FONT_SHIFT_OFFSET 0.5
52 
53 // local variables
54 
55 PLDLLIMPEXP_DRIVER const char* plD_DEVICE_INFO_svg = "svg:Scalable Vector Graphics (SVG 1.1):1:svg:57:svg\n";
56 
57 static int already_warned = 0;
58 
59 static int text_clipping = 1;
60 static DrvOpt svg_options[] = { { "text_clipping", DRV_INT, &text_clipping, "Use text clipping (text_clipping=0|1)" } };
61 
62 typedef struct
63 {
64  short textClipping;
68  int svgIndent;
69  FILE *svgFile;
71  // char curColor[7];
72 } SVG;
73 
74 // font stuff
75 
76 // Debugging extras
77 
78 //-----------------------------------------------
79 // function declarations
80 // -----------------------------------------------
81 
82 // Functions for writing XML SVG tags to a file
83 
84 static void svg_open( SVG *, const char * );
85 static void svg_open_end( SVG * );
86 static void svg_attr_value( SVG *, const char *, const char * );
87 static void svg_attr_values( SVG *, const char *, const char *, ... );
88 static void svg_close( SVG *, const char * );
89 static void svg_general( SVG *, const char * );
90 static void svg_indent( SVG * );
91 static void svg_stroke_width( PLStream * );
92 static void svg_stroke_color( PLStream * );
93 static void svg_fill_color( PLStream * );
94 static void svg_fill_background_color( PLStream * );
95 static int svg_family_check( PLStream * );
96 
97 
98 // General
99 
100 static void poly_line( PLStream *, short *, short *, PLINT, short );
101 static void gradient( PLStream *, short *, short *, PLINT );
102 static void write_hex( FILE *, unsigned char );
103 static void write_unicode( FILE *, PLUNICODE );
104 static void specify_font( FILE *, PLUNICODE );
105 
106 // String processing
107 
108 static void proc_str( PLStream *, EscText * );
109 
110 // PLplot interface functions
111 
113 void plD_init_svg( PLStream * );
114 void plD_line_svg( PLStream *, short, short, short, short );
115 void plD_polyline_svg( PLStream *, short *, short *, PLINT );
116 void plD_eop_svg( PLStream * );
117 void plD_bop_svg( PLStream * );
118 void plD_tidy_svg( PLStream * );
119 void plD_state_svg( PLStream *, PLINT );
120 void plD_esc_svg( PLStream *, PLINT, void * );
121 
122 //--------------------------------------------------------------------------
123 // dispatch_init_init()
124 //
125 // Initialize device dispatch table
126 //--------------------------------------------------------------------------
127 
129 {
130 #ifndef ENABLE_DYNDRIVERS
131  pdt->pl_MenuStr = "Scalable Vector Graphics (SVG 1.1)";
132  pdt->pl_DevName = "svg";
133 #endif
135  pdt->pl_seq = 57;
139  pdt->pl_eop = (plD_eop_fp) plD_eop_svg;
140  pdt->pl_bop = (plD_bop_fp) plD_bop_svg;
143  pdt->pl_esc = (plD_esc_fp) plD_esc_svg;
144 }
145 
146 //--------------------------------------------------------------------------
147 // svg_init()
148 //
149 // Initialize device
150 //--------------------------------------------------------------------------
151 
152 void plD_init_svg( PLStream *pls )
153 {
154  SVG *aStream;
155 
156  pls->termin = 0; // not an interactive device
157  pls->color = 1; // supports color
158  pls->width = 1;
159  pls->verbose = 1;
160  pls->bytecnt = 0;
161  //pls->debug = 1;
162  pls->dev_text = 1; // handles text
163  pls->dev_unicode = 1; // wants text as unicode
164  pls->page = 0;
165  pls->dev_fill0 = 1; // driver generates solid fills
166  pls->dev_fill1 = 0; // Use PLplot core fallback for pattern fills
167  pls->dev_gradient = 1; // driver renders gradient
168 
169  pls->graphx = GRAPHICS_MODE;
170 
171  if ( !pls->colorset )
172  pls->color = 1;
173 
174  // Initialize family file info
175  plFamInit( pls );
176 
177  // Prompt for a file name if not already set
178  plOpenFile( pls );
179 // Allocate and initialize device-specific data
180 
181  if ( pls->dev != NULL )
182  free( (void *) pls->dev );
183 
184  pls->dev = calloc( 1, (size_t) sizeof ( SVG ) );
185  if ( pls->dev == NULL )
186  plexit( "plD_init_svg: Out of memory." );
187 
188  aStream = (SVG *) pls->dev;
189 
190  // Set the bounds for plotting in points (unit of 1/72 of an inch). Default is SVG_Default_X points x SVG_Default_Y points unless otherwise specified by plspage or -geometry option.
191 
192  if ( pls->xlength <= 0 || pls->ylength <= 0 )
193  {
194  aStream->canvasXSize = SVG_Default_X;
195  aStream->canvasYSize = SVG_Default_Y;
196  }
197  else
198  {
199  aStream->canvasXSize = pls->xlength;
200  aStream->canvasYSize = pls->ylength;
201  }
202  // Calculate ratio of (larger) internal PLplot coordinates to external
203  // coordinates used for svg file.
204  if ( aStream->canvasXSize > aStream->canvasYSize )
205  aStream->scale = (PLFLT) ( PIXELS_X - 1 ) / (PLFLT) aStream->canvasXSize;
206  else
207  aStream->scale = (PLFLT) PIXELS_Y / (PLFLT) aStream->canvasYSize;
208  plP_setphy( (PLINT) 0, (PLINT) ( aStream->scale * aStream->canvasXSize ), (PLINT) 0, (PLINT) ( aStream->scale * aStream->canvasYSize ) ); // Scaled points.
209 
210  plP_setpxl( aStream->scale * POINTS_PER_INCH / 25.4, aStream->scale * POINTS_PER_INCH / 25.4 ); // Scaled points/mm.
211 
212  aStream->svgFile = pls->OutFile;
213 
214  // Handle the text clipping option
215  plParseDrvOpts( svg_options );
216 
217  // Turn on text clipping if the user desires this
218  if ( text_clipping )
219  {
220  aStream->textClipping = 1;
221  }
222  aStream->textClipping = (short) text_clipping;
223 
224  aStream->svgIndent = 0;
225  aStream->gradient_index = 0;
226  svg_general( aStream, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" );
227  svg_general( aStream, "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n" );
228  svg_general( aStream, " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n" );
229 }
230 
231 //--------------------------------------------------------------------------
232 // svg_bop()
233 //
234 // Set up for the next page.
235 //--------------------------------------------------------------------------
236 
237 void plD_bop_svg( PLStream *pls )
238 {
239  SVG *aStream;
240 
241  // Plot familying stuff. Not really understood, just copying gd.c
242  plGetFam( pls );
243 // n.b. pls->dev can change because of an indirect call to plD_init_svg
244 // from plGetFam if familying is enabled. Thus, wait to define aStream until
245 // now.
246  aStream = pls->dev;
247 
248  pls->famadv = 1;
249  pls->page++;
250  if ( svg_family_check( pls ) )
251  {
252  return;
253  }
254 
255  // write opening svg tag for the new page
256 
257  svg_open( aStream, "svg" );
258  svg_attr_value( aStream, "xmlns", "http://www.w3.org/2000/svg" );
259  svg_attr_value( aStream, "xmlns:xlink", "http://www.w3.org/1999/xlink" );
260  svg_attr_value( aStream, "version", "1.1" );
261  // svg_attr_values("width", "%dcm", (int)((double)canvasXSize/POINTS_PER_INCH * 2.54));
262  // svg_attr_values("height", "%dcm", (int)((double)canvasYSize/POINTS_PER_INCH * 2.54));
263  svg_attr_values( aStream, "width", "%dpt", aStream->canvasXSize );
264  svg_attr_values( aStream, "height", "%dpt", aStream->canvasYSize );
265  svg_attr_values( aStream, "viewBox", "%d %d %d %d", 0, 0, aStream->canvasXSize, aStream->canvasYSize );
266  svg_general( aStream, ">\n" );
267 
268  // set the background by drawing a rectangle that is the size of
269  // of the canvas and filling it with the background color.
270 
271  svg_open( aStream, "rect" );
272  svg_attr_values( aStream, "x", "%d", 0 );
273  svg_attr_values( aStream, "y", "%d", 0 );
274  svg_attr_values( aStream, "width", "%d", aStream->canvasXSize );
275  svg_attr_values( aStream, "height", "%d", aStream->canvasYSize );
276  svg_attr_value( aStream, "stroke", "none" );
278  svg_open_end( aStream );
279 
280  // invert the coordinate system so that PLplot graphs appear right side up
281 
282  svg_open( aStream, "g" );
283  svg_attr_values( aStream, "transform", "matrix(1 0 0 -1 0 %d)", aStream->canvasYSize );
284  svg_general( aStream, ">\n" );
285 }
286 
287 //--------------------------------------------------------------------------
288 // svg_line()
289 //
290 // Draw a line in the current color from (x1,y1) to (x2,y2).
291 //--------------------------------------------------------------------------
292 
293 void plD_line_svg( PLStream *pls, short x1a, short y1a, short x2a, short y2a )
294 {
295  SVG *aStream;
296 
297  aStream = pls->dev;
298 
299  if ( svg_family_check( pls ) )
300  {
301  return;
302  }
303  svg_open( aStream, "polyline" );
304  svg_stroke_width( pls );
305  svg_stroke_color( pls );
306  svg_attr_value( aStream, "fill", "none" );
307  // svg_attr_value(aStream, "shape-rendering", "crispEdges");
308  svg_attr_values( aStream, "points", "%r,%r %r,%r", (double) x1a / aStream->scale, (double) y1a / aStream->scale, (double) x2a / aStream->scale, (double) y2a / aStream->scale );
309  svg_open_end( aStream );
310 }
311 
312 //--------------------------------------------------------------------------
313 // svg_polyline()
314 //
315 // Draw a polyline in the current color.
316 //--------------------------------------------------------------------------
317 
318 void plD_polyline_svg( PLStream *pls, short *xa, short *ya, PLINT npts )
319 {
320  if ( svg_family_check( pls ) )
321  {
322  return;
323  }
324  poly_line( pls, xa, ya, npts, 0 );
325 }
326 
327 //--------------------------------------------------------------------------
328 // svg_eop()
329 //
330 // End of page
331 //--------------------------------------------------------------------------
332 
333 void plD_eop_svg( PLStream *pls )
334 {
335  SVG *aStream;
336 
337  aStream = pls->dev;
338 
339  if ( svg_family_check( pls ) )
340  {
341  return;
342  }
343  // write the closing svg tag
344 
345  svg_close( aStream, "g" );
346  svg_close( aStream, "svg" );
347 }
348 
349 //--------------------------------------------------------------------------
350 // svg_tidy()
351 //
352 // Close graphics file or otherwise clean up.
353 //--------------------------------------------------------------------------
354 
355 void plD_tidy_svg( PLStream *pls )
356 {
357  if ( svg_family_check( pls ) )
358  {
359  return;
360  }
361  plCloseFile( pls );
362 }
363 
364 //--------------------------------------------------------------------------
365 // plD_state_svg()
366 //
367 // Handle change in PLStream state (color, pen width, fill attribute, etc).
368 //
369 // Nothing is done here because these attributes are aquired from
370 // PLStream for each element that is drawn.
371 //--------------------------------------------------------------------------
372 
374 {
375 }
376 
377 //--------------------------------------------------------------------------
378 // svg_esc()
379 //
380 // Escape function.
381 //--------------------------------------------------------------------------
382 
383 void plD_esc_svg( PLStream *pls, PLINT op, void *ptr )
384 {
385  if ( svg_family_check( pls ) )
386  {
387  return;
388  }
389  switch ( op )
390  {
391  case PLESC_FILL: // fill polygon
392  poly_line( pls, pls->dev_x, pls->dev_y, pls->dev_npts, 1 );
393  break;
394  case PLESC_GRADIENT: // render gradient inside polygon
395  gradient( pls, pls->dev_x, pls->dev_y, pls->dev_npts );
396  break;
397  case PLESC_HAS_TEXT: // render text
398  proc_str( pls, (EscText *) ptr );
399  break;
400  }
401 }
402 
403 //--------------------------------------------------------------------------
404 // poly_line()
405 //
406 // Handles drawing filled and unfilled polygons
407 //--------------------------------------------------------------------------
408 
409 void poly_line( PLStream *pls, short *xa, short *ya, PLINT npts, short fill )
410 {
411  int i;
412  SVG *aStream;
413 
414  aStream = pls->dev;
415 
416  svg_open( aStream, "polyline" );
417  if ( fill )
418  {
419  // Two adjacent regions will put non-zero width boundary strokes on top
420  // of each other on their common boundary. Thus, a stroke on the boundary
421  // of a filled region is generally a bad idea when the fill is partially
422  // opaque because the partial opacity of the two boundary strokes which
423  // are on top of each other will mutually interfere and produce a
424  // bad-looking result. On the other hand, for completely opaque fills
425  // a boundary stroke is a good idea since if it is of sufficient width
426  // it will keep the background from leaking through at the anti-aliased
427  // edges of filled regions that have a common boundary with other
428  // filled regions.
429  if ( pls->curcolor.a < 0.99 )
430  {
431  svg_attr_value( aStream, "stroke", "none" );
432  }
433  else
434  {
435  svg_stroke_width( pls );
436  svg_stroke_color( pls );
437  }
438  svg_fill_color( pls );
439  if ( pls->dev_eofill )
440  svg_attr_value( aStream, "fill-rule", "evenodd" );
441  else
442  svg_attr_value( aStream, "fill-rule", "nonzero" );
443  }
444  else
445  {
446  svg_stroke_width( pls );
447  svg_stroke_color( pls );
448  svg_attr_value( aStream, "fill", "none" );
449  }
450  //svg_attr_value(aStream, "shape-rendering", "crispEdges");
451  svg_indent( aStream );
452  fprintf( aStream->svgFile, "points=\"" );
453  for ( i = 0; i < npts; i++ )
454  {
455  fprintf( aStream->svgFile, "%.2f,%.2f ", (double) xa[i] / aStream->scale, (double) ya[i] / aStream->scale );
456  if ( ( ( i + 1 ) % 10 ) == 0 )
457  {
458  fprintf( aStream->svgFile, "\n" );
459  svg_indent( aStream );
460  }
461  }
462  fprintf( aStream->svgFile, "\"/>\n" );
463  aStream->svgIndent -= 2;
464 }
465 
466 //--------------------------------------------------------------------------
467 // gradient()
468 //
469 // Draws gradient
470 //--------------------------------------------------------------------------
471 
472 void gradient( PLStream *pls, short *xa, short *ya, PLINT npts )
473 {
474  int i;
475  // 27 should be the maximum needed below, but be generous.
476  char buffer[50];
477  SVG *aStream;
478 
479  aStream = pls->dev;
480 
481  svg_open( aStream, "g>" );
482  svg_open( aStream, "defs>" );
483  svg_open( aStream, "linearGradient" );
484  // Allows ~2^31 unique gradient id's, gradient_index incremented below.
485  sprintf( buffer, "MyGradient%010d", aStream->gradient_index );
486  svg_attr_value( aStream, "id", buffer );
487  svg_attr_value( aStream, "gradientUnits", "userSpaceOnUse" );
488  sprintf( buffer, "%.2f", pls->xgradient[0] / aStream->scale );
489  svg_attr_value( aStream, "x1", buffer );
490  sprintf( buffer, "%.2f", pls->ygradient[0] / aStream->scale );
491  svg_attr_value( aStream, "y1", buffer );
492  sprintf( buffer, "%.2f", pls->xgradient[1] / aStream->scale );
493  svg_attr_value( aStream, "x2", buffer );
494  sprintf( buffer, "%.2f", pls->ygradient[1] / aStream->scale );
495  svg_attr_value( aStream, "y2", buffer );
496  svg_general( aStream, ">\n" );
497 
498  for ( i = 0; i < pls->ncol1; i++ )
499  {
500  svg_indent( aStream );
501  fprintf( aStream->svgFile, "<stop offset=\"%.3f\" ",
502  (double) i / (double) ( pls->ncol1 - 1 ) );
503  fprintf( aStream->svgFile, "stop-color=\"#" );
504  write_hex( aStream->svgFile, pls->cmap1[i].r );
505  write_hex( aStream->svgFile, pls->cmap1[i].g );
506  write_hex( aStream->svgFile, pls->cmap1[i].b );
507  fprintf( aStream->svgFile, "\" " );
508  fprintf( aStream->svgFile, "stop-opacity=\"%.3f\"/>\n", pls->cmap1[i].a );
509  }
510 
511  svg_close( aStream, "linearGradient" );
512  svg_close( aStream, "defs" );
513  svg_open( aStream, "polyline" );
514  sprintf( buffer, "url(#MyGradient%010d)", aStream->gradient_index++ );
515  svg_attr_value( aStream, "fill", buffer );
516  svg_indent( aStream );
517  fprintf( aStream->svgFile, "points=\"" );
518  for ( i = 0; i < npts; i++ )
519  {
520  fprintf( aStream->svgFile, "%.2f,%.2f ", (double) xa[i] / aStream->scale, (double) ya[i] / aStream->scale );
521  if ( ( ( i + 1 ) % 10 ) == 0 )
522  {
523  fprintf( aStream->svgFile, "\n" );
524  svg_indent( aStream );
525  }
526  }
527  fprintf( aStream->svgFile, "\"/>\n" );
528  aStream->svgIndent -= 2;
529  svg_close( aStream, "g" );
530 }
531 
532 //--------------------------------------------------------------------------
533 // proc_str()
534 //
535 // Processes strings for display.
536 //
537 // NOTE:
538 //
539 // (1) This was tested on Firefox and Camino where it seemed to display
540 // text properly. However, it isn't obvious to me that these browsers
541 // conform to the specification. Basically the issue is that some of
542 // the text properties (i.e. dy) that you specify inside a tspan element
543 // remain in force until the end of the text element. It would seem to
544 // me that they should only apply inside the tspan tag. To get around
545 // this, and because it was easier anyway, I used what is essentially
546 // a list of tspan tags rather than a tree of tspan tags. Perhaps
547 // better described as a tree with one branch?
548 //
549 // (2) To deal with the some whitespace annoyances, the entire text
550 // element must be written on a single line. If there are lots of
551 // format characters then this line might end up being too long
552 // for some SVG implementations.
553 //
554 // (3) Text placement is not ideal. Vertical offset seems to be
555 // particularly troublesome.
556 //
557 // (4) See additional notes in specify_font re. to sans / serif
558 //
559 //--------------------------------------------------------------------------
560 
561 void proc_str( PLStream *pls, EscText *args )
562 {
563  char plplot_esc;
564  static short which_clip = 0;
565  short i;
566  short totalTags = 1;
567  short ucs4Len = (short) args->unicode_array_len;
568  double ftHt, scaled_offset, scaled_ftHt;
569  PLUNICODE fci;
570  PLINT rcx[4], rcy[4];
571  static PLINT prev_rcx[4], prev_rcy[4];
572  PLFLT rotation, shear, stride, cos_rot, sin_rot, sin_shear, cos_shear;
573  PLFLT t[4];
574  int glyph_size, sum_glyph_size;
575  short if_write;
576  // PLFLT *t = args->xform;
577  PLUNICODE *ucs4 = args->unicode_array;
578  SVG *aStream;
579  PLFLT old_sscale, sscale, old_soffset, soffset, old_dup, ddup;
580  PLINT level;
581  PLINT same_clip;
582 
583  // check that we got unicode
584  if ( ucs4Len == 0 )
585  {
586  printf( "Non unicode string passed to SVG driver, ignoring\n" );
587  return;
588  }
589 
590  // get plplot escape character and the current font
591  plgesc( &plplot_esc );
592  plgfci( &fci );
593 
594  // determine the font height in points.
595  ftHt = FONT_SIZE_RATIO * pls->chrht * POINTS_PER_INCH / 25.4;
596 
597  // Setup & apply text clipping area if desired
598  aStream = (SVG *) pls->dev;
599  if ( aStream->textClipping )
600  {
601  // Use PLplot core routine difilt_clip to appropriately
602  // transform the coordinates of the clipping rectangle
603  difilt_clip( rcx, rcy );
604  same_clip = TRUE;
605  if ( which_clip == 0 )
606  {
607  same_clip = FALSE;
608  }
609  else
610  {
611  for ( i = 0; i < 4; i++ )
612  {
613  if ( rcx[i] != prev_rcx[i] ||
614  rcy[i] != prev_rcy[i] )
615  same_clip = FALSE;
616  }
617  }
618  if ( !same_clip )
619  {
620  svg_open( aStream, "clipPath" );
621  svg_attr_values( aStream, "id", "text-clipping%d", which_clip );
622  svg_general( aStream, ">\n" );
623 
624  // Output a polygon to represent the clipping region.
625  svg_open( aStream, "polygon" );
626  svg_attr_values( aStream,
627  "points",
628  "%f,%f %f,%f %f,%f %f,%f",
629  ( (PLFLT) rcx[0] ) / aStream->scale,
630  ( (PLFLT) rcy[0] ) / aStream->scale,
631  ( (PLFLT) rcx[1] ) / aStream->scale,
632  ( (PLFLT) rcy[1] ) / aStream->scale,
633  ( (PLFLT) rcx[2] ) / aStream->scale,
634  ( (PLFLT) rcy[2] ) / aStream->scale,
635  ( (PLFLT) rcx[3] ) / aStream->scale,
636  ( (PLFLT) rcy[3] ) / aStream->scale );
637  svg_open_end( aStream );
638 
639  svg_close( aStream, "clipPath" );
640  for ( i = 0; i < 4; i++ )
641  {
642  prev_rcx[i] = rcx[i];
643  prev_rcy[i] = rcy[i];
644  }
645  which_clip++;
646  }
647  svg_open( aStream, "g" );
648  svg_attr_values( aStream, "clip-path",
649  "url(#text-clipping%d)", which_clip - 1 );
650  svg_general( aStream, ">\n" );
651  }
652 
653  // This draws the clipping region on the screen which can
654  // be very helpful for debugging.
655 
656  //
657  // svg_open(aStream, "polygon");
658  // svg_attr_values(aStream,
659  // "points",
660  // "%f,%f %f,%f %f,%f %f,%f",
661  // ((PLFLT)rcx[0])/aStream->scale,
662  // ((PLFLT)rcy[0])/aStream->scale,
663  // ((PLFLT)rcx[1])/aStream->scale,
664  // ((PLFLT)rcy[1])/aStream->scale,
665  // ((PLFLT)rcx[2])/aStream->scale,
666  // ((PLFLT)rcy[2])/aStream->scale,
667  // ((PLFLT)rcx[3])/aStream->scale,
668  // ((PLFLT)rcy[3])/aStream->scale);
669  // svg_stroke_width(pls);
670  // svg_stroke_color(pls);
671  // svg_attr_value(aStream, "fill", "none");
672  // svg_open_end(aStream);
673  //
674 
675  // Calculate the tranformation matrix for SVG based on the
676  // transformation matrix provided by PLplot.
677  plRotationShear( args->xform, &rotation, &shear, &stride );
678  // N.B. Experimentally, I (AWI) have found the svg rotation angle is
679  // the negative of the libcairo rotation angle, and the svg shear angle
680  // is pi minus the libcairo shear angle.
681  rotation -= pls->diorot * PI / 2.0;
682  cos_rot = cos( rotation );
683  sin_rot = -sin( rotation );
684  sin_shear = sin( shear );
685  cos_shear = -cos( shear );
686  t[0] = cos_rot * stride;
687  t[1] = -sin_rot * stride;
688  t[2] = cos_rot * sin_shear + sin_rot * cos_shear;
689  t[3] = -sin_rot * sin_shear + cos_rot * cos_shear;
690 
691  //--------------
692  // open text tag
693  // --------------
694 
695  svg_open( aStream, "text" );
696 
697  svg_attr_value( aStream, "dominant-baseline", "no-change" );
698 
699  // set font color
700  svg_fill_color( pls );
701 
702  // white space preserving mode
703  svg_attr_value( aStream, "xml:space", "preserve" );
704 
705  // set the font size
706  svg_attr_values( aStream, "font-size", "%d", (int) ftHt );
707 
708  // Apply coordinate transform for text display.
709  // The transformation also defines the location of the text in x and y.
710  svg_attr_values( aStream, "transform", "matrix(%f %f %f %f %f %f)",
711  t[0], t[1], t[2], t[3],
712  (double) ( args->x / aStream->scale ),
713  (double) ( args->y / aStream->scale ) );
714 
715 
716  //----------------------------------------------------------
717  // Write the text with formatting
718  // We just keep stacking up tspan tags, then close them all
719  // after we have written out all of the text.
720  // ----------------------------------------------------------
721 
722  // For if_write = 0, we write nothing and instead accumulate the
723  // sum_glyph_size from the fontsize of the individual glyphs which
724  // is then used to figure out the initial x position from text-anchor and
725  // args->just that is used to write out the SVG xml for if_write = 1.
726 
727  glyph_size = (int) ftHt;
728  sum_glyph_size = 0;
729  if_write = 0;
730  while ( if_write < 2 )
731  {
732  if ( if_write == 1 )
733  {
734  //printf("number of characters = %f\n", sum_glyph_size/ftHt);
735  // The above coordinate transform defines the _raw_ x position of the
736  // text without justification so this attribute value depends on
737  // text-anchor and args->just*sum_glyph_size
738  // N.B. sum_glyph_size calculation only correct for monospaced fonts
739  // so generally sum_glyph_size will be overestimated by various amounts
740  // depending on what glyphs are to be rendered, the font, etc. However,
741  // this correction is differential respect to the end points or the
742  // middle so you should be okay so long as you don't deviate too far
743  // from those anchor points.
744  if ( args->just < 0.33 )
745  {
746  svg_attr_value( aStream, "text-anchor", "start" ); // left justification
747  svg_attr_values( aStream, "x", "%f", (double) ( -args->just * sum_glyph_size ) );
748  }
749  else if ( args->just > 0.66 )
750  {
751  svg_attr_value( aStream, "text-anchor", "end" ); // right justification
752  svg_attr_values( aStream, "x", "%f", (double) ( ( 1. - args->just ) * sum_glyph_size ) );
753  }
754  else
755  {
756  svg_attr_value( aStream, "text-anchor", "middle" ); // center
757  svg_attr_values( aStream, "x", "%f", (double) ( ( 0.5 - args->just ) * sum_glyph_size ) );
758  }
759 
760  // The text goes at zero in y since the above
761  // coordinate transform defines the y position of the text
762  svg_attr_values( aStream, "y", "%f",
763  FONT_SHIFT_RATIO * 0.5 * ftHt +
765 
766  fprintf( aStream->svgFile, ">" );
767 
768  // specify the initial font
769  specify_font( aStream->svgFile, fci );
770  }
771  i = 0;
772  scaled_ftHt = ftHt;
773  level = 0;
774  ddup = 0.;
775  while ( i < ucs4Len )
776  {
777  if ( ucs4[i] < PL_FCI_MARK ) // not a font change
778  {
779  if ( ucs4[i] != (PLUNICODE) plplot_esc ) // a character to display
780  {
781  if ( if_write )
782  {
783  write_unicode( aStream->svgFile, ucs4[i] );
784  }
785  else
786  {
787  sum_glyph_size += glyph_size;
788  }
789  i++;
790  continue;
791  }
792  i++;
793  if ( ucs4[i] == (PLUNICODE) plplot_esc ) // a escape character to display
794  {
795  if ( if_write )
796  {
797  write_unicode( aStream->svgFile, ucs4[i] );
798  }
799  else
800  {
801  sum_glyph_size += glyph_size;
802  }
803  i++;
804  continue;
805  }
806  else
807  {
808  // super/subscript logic follows that in plstr routine (plsym.c)
809  // for Hershey fonts. Factor of FONT_SHIFT_RATIO*0.80 is empirical
810  // adjustment.
811  if ( ucs4[i] == (PLUNICODE) 'u' ) // Superscript
812  {
813  plP_script_scale( TRUE, &level,
814  &old_sscale, &sscale, &old_soffset, &soffset );
815  // The correction for the difference in magnitude
816  // between the baseline and middle coordinate systems
817  // for superscripts should be
818  // 0.5*(base font size - superscript/subscript font size).
819  old_dup = ddup;
820  ddup = 0.5 * ( 1.0 - sscale );
821  if ( level <= 0 )
822  {
823  scaled_offset = FONT_SHIFT_RATIO * ftHt * ( 0.80 * ( soffset - old_soffset ) - ( ddup - old_dup ) );
824  }
825  else
826  {
827  scaled_offset = -FONT_SHIFT_RATIO * ftHt * ( 0.80 * ( soffset - old_soffset ) + ( ddup - old_dup ) );
828  }
829  scaled_ftHt = sscale * ftHt;
830  if ( if_write )
831  {
832  totalTags++;
833  fprintf( aStream->svgFile, "<tspan dy=\"%f\" font-size=\"%d\">", scaled_offset, (int) scaled_ftHt );
834  }
835  else
836  {
837  glyph_size = (int) scaled_ftHt;
838  }
839  }
840  if ( ucs4[i] == (PLUNICODE) 'd' ) // Subscript
841  {
842  plP_script_scale( FALSE, &level,
843  &old_sscale, &sscale, &old_soffset, &soffset );
844  // The correction for the difference in magnitude
845  // between the baseline and middle coordinate systems
846  // for superscripts should be
847  // 0.5*(base font size - superscript/subscript font size).
848  old_dup = ddup;
849  ddup = 0.5 * ( 1.0 - sscale );
850  if ( level < 0 )
851  {
852  scaled_offset = FONT_SHIFT_RATIO * ftHt * ( 0.80 * ( soffset - old_soffset ) - ( ddup - old_dup ) );
853  }
854  else
855  {
856  scaled_offset = -FONT_SHIFT_RATIO * ftHt * ( 0.80 * ( soffset - old_soffset ) + ( ddup - old_dup ) );
857  }
858  scaled_ftHt = sscale * ftHt;
859  if ( if_write )
860  {
861  totalTags++;
862  fprintf( aStream->svgFile, "<tspan dy=\"%f\" font-size=\"%d\">", scaled_offset, (int) scaled_ftHt );
863  }
864  else
865  {
866  glyph_size = (int) scaled_ftHt;
867  }
868  }
869  i++;
870  }
871  }
872  else // a font change
873  {
874  if ( if_write )
875  {
876  specify_font( aStream->svgFile, ucs4[i] );
877  totalTags++;
878  }
879  i++;
880  }
881  }
882  if_write++;
883  }
884 
885  //----------------------------------------------
886  // close out all the tspan tags and the text tag
887  // ----------------------------------------------
888 
889  for ( i = 0; i < totalTags; i++ )
890  {
891  fprintf( aStream->svgFile, "</tspan>" );
892  }
893  // The following commented out (by AWI) because it is a bad idea to
894  // put line ends in the middle of a text tag. This was the key to
895  // all the text rendering issues we had.
896  //fprintf(svgFile,"\n");
897  // For the same reason use fprintf and svgIndent -= 2;
898  // to close the text tag rather than svg_close("text"); since
899  // we don't want indentation spaces entering the text.
900  // svg_close("text");
901  fprintf( aStream->svgFile, "</text>\n" );
902  aStream->svgIndent -= 2;
903  if ( aStream->textClipping )
904  {
905  svg_close( aStream, "g" );
906  }
907 }
908 
909 //--------------------------------------------------------------------------
910 // svg_open ()
911 //
912 // Used to open a new XML expression, sets the indent level appropriately
913 //--------------------------------------------------------------------------
914 
915 void svg_open( SVG *aStream, const char *tag )
916 {
917  svg_indent( aStream );
918  fprintf( aStream->svgFile, "<%s\n", tag );
919  aStream->svgIndent += 2;
920 }
921 
922 //--------------------------------------------------------------------------
923 // svg_open_end ()
924 //
925 // Used to end the opening of a new XML expression i.e. add
926 // the final ">".
927 //--------------------------------------------------------------------------
928 
929 void svg_open_end( SVG *aStream )
930 {
931  svg_indent( aStream );
932  fprintf( aStream->svgFile, "/>\n" );
933  aStream->svgIndent -= 2;
934 }
935 
936 //--------------------------------------------------------------------------
937 // svg_attr_value ()
938 //
939 // Prints two strings to svgFile as a XML attribute value pair
940 // i.e. foo="bar"
941 //--------------------------------------------------------------------------
942 
943 void svg_attr_value( SVG *aStream, const char *attribute, const char *value )
944 {
945  svg_indent( aStream );
946  fprintf( aStream->svgFile, "%s=\"%s\"\n", attribute, value );
947 }
948 
949 //--------------------------------------------------------------------------
950 // svg_attr_values ()
951 //
952 // Prints a string and a bunch of numbers / strings as a XML attribute
953 // value pair i.e. foo="0 10 1.0 5.3 bar"
954 //
955 // This function is derived from an example in
956 // "The C Programming Language" by Kernighan and Ritchie.
957 //
958 //--------------------------------------------------------------------------
959 
960 void svg_attr_values( SVG *aStream, const char *attribute, const char *format, ... )
961 {
962  va_list ap;
963  const char *p, *sval;
964  int ival;
965  double dval;
966 
967  svg_indent( aStream );
968  fprintf( aStream->svgFile, "%s=\"", attribute );
969  va_start( ap, format );
970  for ( p = format; *p; p++ )
971  {
972  if ( *p != '%' )
973  {
974  fprintf( aStream->svgFile, "%c", *p );
975  continue;
976  }
977  switch ( *++p )
978  {
979  case 'd':
980  ival = va_arg( ap, int );
981  fprintf( aStream->svgFile, "%d", ival );
982  break;
983  case 'f':
984  dval = va_arg( ap, double );
985  fprintf( aStream->svgFile, "%f", dval );
986  break;
987  case 'r':
988  // r is non-standard, but use it here to format rounded value
989  dval = va_arg( ap, double );
990  fprintf( aStream->svgFile, "%.2f", dval );
991  break;
992  case 's':
993  sval = va_arg( ap, char * );
994  fprintf( aStream->svgFile, "%s", sval );
995  break;
996  default:
997  fprintf( aStream->svgFile, "%c", *p );
998  break;
999  }
1000  }
1001  fprintf( aStream->svgFile, "\"\n" );
1002  va_end( ap );
1003 }
1004 
1005 //--------------------------------------------------------------------------
1006 // svg_close ()
1007 //
1008 // Used to close a XML expression, sets the indent level appropriately
1009 //--------------------------------------------------------------------------
1010 
1011 void svg_close( SVG *aStream, const char *tag )
1012 {
1013  aStream->svgIndent -= 2;
1014  svg_indent( aStream );
1015  if ( strlen( tag ) > 0 )
1016  {
1017  fprintf( aStream->svgFile, "</%s>\n", tag );
1018  }
1019  else
1020  {
1021  fprintf( aStream->svgFile, "/>\n" );
1022  }
1023 }
1024 
1025 //--------------------------------------------------------------------------
1026 // svg_general ()
1027 //
1028 // Used to print any text into the svgFile
1029 //--------------------------------------------------------------------------
1030 
1031 void svg_general( SVG *aStream, const char *text )
1032 {
1033  svg_indent( aStream );
1034  fprintf( aStream->svgFile, "%s", text );
1035 }
1036 
1037 //--------------------------------------------------------------------------
1038 // svg_indent ()
1039 //
1040 // Indents properly based on the current indent level
1041 //--------------------------------------------------------------------------
1042 
1043 void svg_indent( SVG *aStream )
1044 {
1045  short i;
1046  for ( i = 0; i < aStream->svgIndent; i++ )
1047  {
1048  fprintf( aStream->svgFile, " " );
1049  }
1050 }
1051 
1052 //--------------------------------------------------------------------------
1053 // svg_stroke_width ()
1054 //
1055 // sets the stroke width based on the current width
1056 //--------------------------------------------------------------------------
1057 
1059 {
1060  SVG *aStream;
1061 
1062  aStream = pls->dev;
1063  svg_indent( aStream );
1064  fprintf( aStream->svgFile, "stroke-width=\"%e\"\n", pls->width );
1065 }
1066 
1067 //--------------------------------------------------------------------------
1068 // svg_stroke_color ()
1069 //
1070 // sets the stroke color based on the current color
1071 //--------------------------------------------------------------------------
1072 
1074 {
1075  SVG *aStream;
1076 
1077  aStream = pls->dev;
1078  svg_indent( aStream );
1079  fprintf( aStream->svgFile, "stroke=\"#" );
1080  write_hex( aStream->svgFile, pls->curcolor.r );
1081  write_hex( aStream->svgFile, pls->curcolor.g );
1082  write_hex( aStream->svgFile, pls->curcolor.b );
1083  fprintf( aStream->svgFile, "\"\n" );
1084  svg_indent( aStream );
1085  fprintf( aStream->svgFile, "stroke-opacity=\"%f\"\n", pls->curcolor.a );
1086 }
1087 
1088 //--------------------------------------------------------------------------
1089 // svg_fill_color ()
1090 //
1091 // sets the fill color based on the current color
1092 //--------------------------------------------------------------------------
1093 
1095 {
1096  SVG *aStream;
1097 
1098  aStream = pls->dev;
1099  svg_indent( aStream );
1100  fprintf( aStream->svgFile, "fill=\"#" );
1101  write_hex( aStream->svgFile, pls->curcolor.r );
1102  write_hex( aStream->svgFile, pls->curcolor.g );
1103  write_hex( aStream->svgFile, pls->curcolor.b );
1104  fprintf( aStream->svgFile, "\"\n" );
1105  svg_indent( aStream );
1106  fprintf( aStream->svgFile, "fill-opacity=\"%f\"\n", pls->curcolor.a );
1107 }
1108 
1109 //--------------------------------------------------------------------------
1110 // svg_fill_background_color ()
1111 //
1112 // sets the background fill color based on the current background color
1113 //--------------------------------------------------------------------------
1114 
1116 {
1117  SVG *aStream;
1118 
1119  aStream = pls->dev;
1120  svg_indent( aStream );
1121  fprintf( aStream->svgFile, "fill=\"#" );
1122  write_hex( aStream->svgFile, pls->cmap0[0].r );
1123  write_hex( aStream->svgFile, pls->cmap0[0].g );
1124  write_hex( aStream->svgFile, pls->cmap0[0].b );
1125  fprintf( aStream->svgFile, "\"\n" );
1126  svg_indent( aStream );
1127  fprintf( aStream->svgFile, "fill-opacity=\"%f\"\n", pls->cmap0[0].a );
1128 }
1129 
1130 //--------------------------------------------------------------------------
1131 // svg_family_check ()
1132 //
1133 // support function to help supress more than one page if family file
1134 // output not specified by the user (e.g., with the -fam command-line option).
1135 //--------------------------------------------------------------------------
1136 
1138 {
1139  if ( pls->family || pls->page == 1 )
1140  {
1141  return 0;
1142  }
1143  else
1144  {
1145  if ( !already_warned )
1146  {
1147  already_warned = 1;
1148  plwarn( "All pages after the first skipped because family file output not specified.\n" );
1149  }
1150  return 1;
1151  }
1152 }
1153 
1154 //--------------------------------------------------------------------------
1155 // write_hex ()
1156 //
1157 // writes a unsigned char as an appropriately formatted hex value
1158 //--------------------------------------------------------------------------
1159 
1160 void write_hex( FILE *svgFile, unsigned char val )
1161 {
1162  if ( val < 16 )
1163  {
1164  fprintf( svgFile, "0%X", val );
1165  }
1166  else
1167  {
1168  fprintf( svgFile, "%X", val );
1169  }
1170 }
1171 
1172 //--------------------------------------------------------------------------
1173 // write_unicode ()
1174 //
1175 // writes a unicode character, appropriately formatted (i.e. &#xNNN)
1176 // with invalid xml characters replaced by ' '.
1177 //--------------------------------------------------------------------------
1178 
1179 void write_unicode( FILE *svgFile, PLUNICODE ucs4_char )
1180 {
1181  if ( ucs4_char >= ' ' || ucs4_char == '\t' || ucs4_char == '\n' || ucs4_char == '\r' )
1182  fprintf( svgFile, "&#x%x;", ucs4_char );
1183  else
1184  fprintf( svgFile, "&#x%x;", ' ' );
1185 }
1186 
1187 //--------------------------------------------------------------------------
1188 // specify_font ()
1189 //
1190 // Note:
1191 // We don't actually specify a font, just the fonts properties.
1192 // The hope is that this will give the display program the freedom
1193 // to choose the font with the glyphs that it needs to display
1194 // the text.
1195 //
1196 // Known Issues:
1197 // (1) On OS-X 10.4 with Firefox and Camino the "serif" font-family
1198 // looks more like the "italic" font-style.
1199 //
1200 //--------------------------------------------------------------------------
1201 
1202 void specify_font( FILE *svgFile, PLUNICODE ucs4_char )
1203 {
1204  fprintf( svgFile, "<tspan " );
1205 
1206  // sans, serif, mono, script, symbol
1207 
1208  if ( ( ucs4_char & 0x00F ) == 0x000 )
1209  {
1210  fprintf( svgFile, "font-family=\"sans-serif\" " );
1211  }
1212  else if ( ( ucs4_char & 0x00F ) == 0x001 )
1213  {
1214  fprintf( svgFile, "font-family=\"serif\" " );
1215  }
1216  else if ( ( ucs4_char & 0x00F ) == 0x002 )
1217  {
1218  fprintf( svgFile, "font-family=\"mono-space\" " );
1219  }
1220  else if ( ( ucs4_char & 0x00F ) == 0x003 )
1221  {
1222  fprintf( svgFile, "font-family=\"cursive\" " );
1223  }
1224  else if ( ( ucs4_char & 0x00F ) == 0x004 )
1225  {
1226  // this should be symbol, but that doesn't seem to be available
1227  fprintf( svgFile, "font-family=\"sans-serif\" " );
1228  }
1229 
1230  // normal, italic, oblique
1231 
1232  if ( ( ucs4_char & 0x0F0 ) == 0x000 )
1233  {
1234  fprintf( svgFile, "font-style=\"normal\" " );
1235  }
1236  else if ( ( ucs4_char & 0x0F0 ) == 0x010 )
1237  {
1238  fprintf( svgFile, "font-style=\"italic\" " );
1239  }
1240  else if ( ( ucs4_char & 0x0F0 ) == 0x020 )
1241  {
1242  fprintf( svgFile, "font-style=\"oblique\" " );
1243  }
1244 
1245  // normal, bold
1246 
1247  if ( ( ucs4_char & 0xF00 ) == 0x000 )
1248  {
1249  fprintf( svgFile, "font-weight=\"normal\">" );
1250  }
1251  else if ( ( ucs4_char & 0xF00 ) == 0x100 )
1252  {
1253  fprintf( svgFile, "font-weight=\"bold\">" );
1254  }
1255 }