PLplot: svg.c Source File

PLplot 5.10.0

- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

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;
65 int canvasXSize;
66 int canvasYSize;
67 PLFLT scale;
68 int svgIndent;
69 FILE *svgFile;
70 int gradient_index;
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
112 void plD_dispatch_init_svg( PLDispatchTable *pdt );
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
128 void plD_dispatch_init_svg( PLDispatchTable *pdt )
129 {
130 #ifndef ENABLE_DYNDRIVERS
131 pdt->pl_MenuStr = "Scalable Vector Graphics (SVG 1.1)";
132 pdt->pl_DevName = "svg";
133 #endif
134 pdt->pl_type = plDevType_FileOriented;
135 pdt->pl_seq = 57;
136 pdt->pl_init = (plD_init_fp) plD_init_svg;
137 pdt->pl_line = (plD_line_fp) plD_line_svg;
138 pdt->pl_polyline = (plD_polyline_fp) plD_polyline_svg;
139 pdt->pl_eop = (plD_eop_fp) plD_eop_svg;
140 pdt->pl_bop = (plD_bop_fp) plD_bop_svg;
141 pdt->pl_tidy = (plD_tidy_fp) plD_tidy_svg;
142 pdt->pl_state = (plD_state_fp) plD_state_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" );
277 svg_fill_background_color( pls );
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
373 void plD_state_svg( PLStream *PL_UNUSED( pls ), PLINT PL_UNUSED( op ) )
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 +
764 FONT_SHIFT_OFFSET );
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
1058 void svg_stroke_width( PLStream *pls )
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
1073 void svg_stroke_color( PLStream *pls )
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
1094 void svg_fill_color( PLStream *pls )
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
1115 void svg_fill_background_color( PLStream *pls )
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
1137 int svg_family_check( PLStream *pls )
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 }