PLplot
5.10.0
|
00001 // November 8, 2006 00002 // 00003 // PLplot driver for SVG 1.1 (http://www.w3.org/Graphics/SVG/) 00004 // 00005 // Copyright (C) 2006 Hazen Babcock 00006 // 00007 // This file is part of PLplot. 00008 // 00009 // PLplot is free software; you can redistribute it and/or modify 00010 // it under the terms of the GNU Library General Public License as published 00011 // by the Free Software Foundation; either version 2 of the License, or 00012 // (at your option) any later version. 00013 // 00014 // PLplot is distributed in the hope that it will be useful, 00015 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00016 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00017 // GNU Library General Public License for more details. 00018 // 00019 // You should have received a copy of the GNU Library General Public License 00020 // along with PLplot; if not, write to the Free Software 00021 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00022 // 00023 // 00024 00025 //--------------------------------------------- 00026 // Header files, defines and local variables 00027 // --------------------------------------------- 00028 00029 #include <stdarg.h> 00030 #include <math.h> 00031 00032 // PLplot header files 00033 00034 #include "plplotP.h" 00035 #include "drivers.h" 00036 00037 // constants 00038 00039 #define SVG_Default_X 720 00040 #define SVG_Default_Y 540 00041 00042 #define POINTS_PER_INCH 72 00043 00044 #define MAX_STRING_LEN 1000 00045 00046 // This has been generated empirically by looking carefully at results from 00047 // examples 1 and 2. 00048 00049 #define FONT_SIZE_RATIO 1.34 00050 #define FONT_SHIFT_RATIO 0.705 00051 #define FONT_SHIFT_OFFSET 0.5 00052 00053 // local variables 00054 00055 PLDLLIMPEXP_DRIVER const char* plD_DEVICE_INFO_svg = "svg:Scalable Vector Graphics (SVG 1.1):1:svg:57:svg\n"; 00056 00057 static int already_warned = 0; 00058 00059 static int text_clipping = 1; 00060 static DrvOpt svg_options[] = { { "text_clipping", DRV_INT, &text_clipping, "Use text clipping (text_clipping=0|1)" } }; 00061 00062 typedef struct 00063 { 00064 short textClipping; 00065 int canvasXSize; 00066 int canvasYSize; 00067 PLFLT scale; 00068 int svgIndent; 00069 FILE *svgFile; 00070 int gradient_index; 00071 // char curColor[7]; 00072 } SVG; 00073 00074 // font stuff 00075 00076 // Debugging extras 00077 00078 //----------------------------------------------- 00079 // function declarations 00080 // ----------------------------------------------- 00081 00082 // Functions for writing XML SVG tags to a file 00083 00084 static void svg_open( SVG *, const char * ); 00085 static void svg_open_end( SVG * ); 00086 static void svg_attr_value( SVG *, const char *, const char * ); 00087 static void svg_attr_values( SVG *, const char *, const char *, ... ); 00088 static void svg_close( SVG *, const char * ); 00089 static void svg_general( SVG *, const char * ); 00090 static void svg_indent( SVG * ); 00091 static void svg_stroke_width( PLStream * ); 00092 static void svg_stroke_color( PLStream * ); 00093 static void svg_fill_color( PLStream * ); 00094 static void svg_fill_background_color( PLStream * ); 00095 static int svg_family_check( PLStream * ); 00096 00097 00098 // General 00099 00100 static void poly_line( PLStream *, short *, short *, PLINT, short ); 00101 static void gradient( PLStream *, short *, short *, PLINT ); 00102 static void write_hex( FILE *, unsigned char ); 00103 static void write_unicode( FILE *, PLUNICODE ); 00104 static void specify_font( FILE *, PLUNICODE ); 00105 00106 // String processing 00107 00108 static void proc_str( PLStream *, EscText * ); 00109 00110 // PLplot interface functions 00111 00112 void plD_dispatch_init_svg( PLDispatchTable *pdt ); 00113 void plD_init_svg( PLStream * ); 00114 void plD_line_svg( PLStream *, short, short, short, short ); 00115 void plD_polyline_svg( PLStream *, short *, short *, PLINT ); 00116 void plD_eop_svg( PLStream * ); 00117 void plD_bop_svg( PLStream * ); 00118 void plD_tidy_svg( PLStream * ); 00119 void plD_state_svg( PLStream *, PLINT ); 00120 void plD_esc_svg( PLStream *, PLINT, void * ); 00121 00122 //-------------------------------------------------------------------------- 00123 // dispatch_init_init() 00124 // 00125 // Initialize device dispatch table 00126 //-------------------------------------------------------------------------- 00127 00128 void plD_dispatch_init_svg( PLDispatchTable *pdt ) 00129 { 00130 #ifndef ENABLE_DYNDRIVERS 00131 pdt->pl_MenuStr = "Scalable Vector Graphics (SVG 1.1)"; 00132 pdt->pl_DevName = "svg"; 00133 #endif 00134 pdt->pl_type = plDevType_FileOriented; 00135 pdt->pl_seq = 57; 00136 pdt->pl_init = (plD_init_fp) plD_init_svg; 00137 pdt->pl_line = (plD_line_fp) plD_line_svg; 00138 pdt->pl_polyline = (plD_polyline_fp) plD_polyline_svg; 00139 pdt->pl_eop = (plD_eop_fp) plD_eop_svg; 00140 pdt->pl_bop = (plD_bop_fp) plD_bop_svg; 00141 pdt->pl_tidy = (plD_tidy_fp) plD_tidy_svg; 00142 pdt->pl_state = (plD_state_fp) plD_state_svg; 00143 pdt->pl_esc = (plD_esc_fp) plD_esc_svg; 00144 } 00145 00146 //-------------------------------------------------------------------------- 00147 // svg_init() 00148 // 00149 // Initialize device 00150 //-------------------------------------------------------------------------- 00151 00152 void plD_init_svg( PLStream *pls ) 00153 { 00154 SVG *aStream; 00155 00156 pls->termin = 0; // not an interactive device 00157 pls->color = 1; // supports color 00158 pls->width = 1; 00159 pls->verbose = 1; 00160 pls->bytecnt = 0; 00161 //pls->debug = 1; 00162 pls->dev_text = 1; // handles text 00163 pls->dev_unicode = 1; // wants text as unicode 00164 pls->page = 0; 00165 pls->dev_fill0 = 1; // driver generates solid fills 00166 pls->dev_fill1 = 0; // Use PLplot core fallback for pattern fills 00167 pls->dev_gradient = 1; // driver renders gradient 00168 00169 pls->graphx = GRAPHICS_MODE; 00170 00171 if ( !pls->colorset ) 00172 pls->color = 1; 00173 00174 // Initialize family file info 00175 plFamInit( pls ); 00176 00177 // Prompt for a file name if not already set 00178 plOpenFile( pls ); 00179 // Allocate and initialize device-specific data 00180 00181 if ( pls->dev != NULL ) 00182 free( (void *) pls->dev ); 00183 00184 pls->dev = calloc( 1, (size_t) sizeof ( SVG ) ); 00185 if ( pls->dev == NULL ) 00186 plexit( "plD_init_svg: Out of memory." ); 00187 00188 aStream = (SVG *) pls->dev; 00189 00190 // 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. 00191 00192 if ( pls->xlength <= 0 || pls->ylength <= 0 ) 00193 { 00194 aStream->canvasXSize = SVG_Default_X; 00195 aStream->canvasYSize = SVG_Default_Y; 00196 } 00197 else 00198 { 00199 aStream->canvasXSize = pls->xlength; 00200 aStream->canvasYSize = pls->ylength; 00201 } 00202 // Calculate ratio of (larger) internal PLplot coordinates to external 00203 // coordinates used for svg file. 00204 if ( aStream->canvasXSize > aStream->canvasYSize ) 00205 aStream->scale = (PLFLT) ( PIXELS_X - 1 ) / (PLFLT) aStream->canvasXSize; 00206 else 00207 aStream->scale = (PLFLT) PIXELS_Y / (PLFLT) aStream->canvasYSize; 00208 plP_setphy( (PLINT) 0, (PLINT) ( aStream->scale * aStream->canvasXSize ), (PLINT) 0, (PLINT) ( aStream->scale * aStream->canvasYSize ) ); // Scaled points. 00209 00210 plP_setpxl( aStream->scale * POINTS_PER_INCH / 25.4, aStream->scale * POINTS_PER_INCH / 25.4 ); // Scaled points/mm. 00211 00212 aStream->svgFile = pls->OutFile; 00213 00214 // Handle the text clipping option 00215 plParseDrvOpts( svg_options ); 00216 00217 // Turn on text clipping if the user desires this 00218 if ( text_clipping ) 00219 { 00220 aStream->textClipping = 1; 00221 } 00222 aStream->textClipping = (short) text_clipping; 00223 00224 aStream->svgIndent = 0; 00225 aStream->gradient_index = 0; 00226 svg_general( aStream, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" ); 00227 svg_general( aStream, "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n" ); 00228 svg_general( aStream, " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n" ); 00229 } 00230 00231 //-------------------------------------------------------------------------- 00232 // svg_bop() 00233 // 00234 // Set up for the next page. 00235 //-------------------------------------------------------------------------- 00236 00237 void plD_bop_svg( PLStream *pls ) 00238 { 00239 SVG *aStream; 00240 00241 // Plot familying stuff. Not really understood, just copying gd.c 00242 plGetFam( pls ); 00243 // n.b. pls->dev can change because of an indirect call to plD_init_svg 00244 // from plGetFam if familying is enabled. Thus, wait to define aStream until 00245 // now. 00246 aStream = pls->dev; 00247 00248 pls->famadv = 1; 00249 pls->page++; 00250 if ( svg_family_check( pls ) ) 00251 { 00252 return; 00253 } 00254 00255 // write opening svg tag for the new page 00256 00257 svg_open( aStream, "svg" ); 00258 svg_attr_value( aStream, "xmlns", "http://www.w3.org/2000/svg" ); 00259 svg_attr_value( aStream, "xmlns:xlink", "http://www.w3.org/1999/xlink" ); 00260 svg_attr_value( aStream, "version", "1.1" ); 00261 // svg_attr_values("width", "%dcm", (int)((double)canvasXSize/POINTS_PER_INCH * 2.54)); 00262 // svg_attr_values("height", "%dcm", (int)((double)canvasYSize/POINTS_PER_INCH * 2.54)); 00263 svg_attr_values( aStream, "width", "%dpt", aStream->canvasXSize ); 00264 svg_attr_values( aStream, "height", "%dpt", aStream->canvasYSize ); 00265 svg_attr_values( aStream, "viewBox", "%d %d %d %d", 0, 0, aStream->canvasXSize, aStream->canvasYSize ); 00266 svg_general( aStream, ">\n" ); 00267 00268 // set the background by drawing a rectangle that is the size of 00269 // of the canvas and filling it with the background color. 00270 00271 svg_open( aStream, "rect" ); 00272 svg_attr_values( aStream, "x", "%d", 0 ); 00273 svg_attr_values( aStream, "y", "%d", 0 ); 00274 svg_attr_values( aStream, "width", "%d", aStream->canvasXSize ); 00275 svg_attr_values( aStream, "height", "%d", aStream->canvasYSize ); 00276 svg_attr_value( aStream, "stroke", "none" ); 00277 svg_fill_background_color( pls ); 00278 svg_open_end( aStream ); 00279 00280 // invert the coordinate system so that PLplot graphs appear right side up 00281 00282 svg_open( aStream, "g" ); 00283 svg_attr_values( aStream, "transform", "matrix(1 0 0 -1 0 %d)", aStream->canvasYSize ); 00284 svg_general( aStream, ">\n" ); 00285 } 00286 00287 //-------------------------------------------------------------------------- 00288 // svg_line() 00289 // 00290 // Draw a line in the current color from (x1,y1) to (x2,y2). 00291 //-------------------------------------------------------------------------- 00292 00293 void plD_line_svg( PLStream *pls, short x1a, short y1a, short x2a, short y2a ) 00294 { 00295 SVG *aStream; 00296 00297 aStream = pls->dev; 00298 00299 if ( svg_family_check( pls ) ) 00300 { 00301 return; 00302 } 00303 svg_open( aStream, "polyline" ); 00304 svg_stroke_width( pls ); 00305 svg_stroke_color( pls ); 00306 svg_attr_value( aStream, "fill", "none" ); 00307 // svg_attr_value(aStream, "shape-rendering", "crispEdges"); 00308 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 ); 00309 svg_open_end( aStream ); 00310 } 00311 00312 //-------------------------------------------------------------------------- 00313 // svg_polyline() 00314 // 00315 // Draw a polyline in the current color. 00316 //-------------------------------------------------------------------------- 00317 00318 void plD_polyline_svg( PLStream *pls, short *xa, short *ya, PLINT npts ) 00319 { 00320 if ( svg_family_check( pls ) ) 00321 { 00322 return; 00323 } 00324 poly_line( pls, xa, ya, npts, 0 ); 00325 } 00326 00327 //-------------------------------------------------------------------------- 00328 // svg_eop() 00329 // 00330 // End of page 00331 //-------------------------------------------------------------------------- 00332 00333 void plD_eop_svg( PLStream *pls ) 00334 { 00335 SVG *aStream; 00336 00337 aStream = pls->dev; 00338 00339 if ( svg_family_check( pls ) ) 00340 { 00341 return; 00342 } 00343 // write the closing svg tag 00344 00345 svg_close( aStream, "g" ); 00346 svg_close( aStream, "svg" ); 00347 } 00348 00349 //-------------------------------------------------------------------------- 00350 // svg_tidy() 00351 // 00352 // Close graphics file or otherwise clean up. 00353 //-------------------------------------------------------------------------- 00354 00355 void plD_tidy_svg( PLStream *pls ) 00356 { 00357 if ( svg_family_check( pls ) ) 00358 { 00359 return; 00360 } 00361 plCloseFile( pls ); 00362 } 00363 00364 //-------------------------------------------------------------------------- 00365 // plD_state_svg() 00366 // 00367 // Handle change in PLStream state (color, pen width, fill attribute, etc). 00368 // 00369 // Nothing is done here because these attributes are aquired from 00370 // PLStream for each element that is drawn. 00371 //-------------------------------------------------------------------------- 00372 00373 void plD_state_svg( PLStream *PL_UNUSED( pls ), PLINT PL_UNUSED( op ) ) 00374 { 00375 } 00376 00377 //-------------------------------------------------------------------------- 00378 // svg_esc() 00379 // 00380 // Escape function. 00381 //-------------------------------------------------------------------------- 00382 00383 void plD_esc_svg( PLStream *pls, PLINT op, void *ptr ) 00384 { 00385 if ( svg_family_check( pls ) ) 00386 { 00387 return; 00388 } 00389 switch ( op ) 00390 { 00391 case PLESC_FILL: // fill polygon 00392 poly_line( pls, pls->dev_x, pls->dev_y, pls->dev_npts, 1 ); 00393 break; 00394 case PLESC_GRADIENT: // render gradient inside polygon 00395 gradient( pls, pls->dev_x, pls->dev_y, pls->dev_npts ); 00396 break; 00397 case PLESC_HAS_TEXT: // render text 00398 proc_str( pls, (EscText *) ptr ); 00399 break; 00400 } 00401 } 00402 00403 //-------------------------------------------------------------------------- 00404 // poly_line() 00405 // 00406 // Handles drawing filled and unfilled polygons 00407 //-------------------------------------------------------------------------- 00408 00409 void poly_line( PLStream *pls, short *xa, short *ya, PLINT npts, short fill ) 00410 { 00411 int i; 00412 SVG *aStream; 00413 00414 aStream = pls->dev; 00415 00416 svg_open( aStream, "polyline" ); 00417 if ( fill ) 00418 { 00419 // Two adjacent regions will put non-zero width boundary strokes on top 00420 // of each other on their common boundary. Thus, a stroke on the boundary 00421 // of a filled region is generally a bad idea when the fill is partially 00422 // opaque because the partial opacity of the two boundary strokes which 00423 // are on top of each other will mutually interfere and produce a 00424 // bad-looking result. On the other hand, for completely opaque fills 00425 // a boundary stroke is a good idea since if it is of sufficient width 00426 // it will keep the background from leaking through at the anti-aliased 00427 // edges of filled regions that have a common boundary with other 00428 // filled regions. 00429 if ( pls->curcolor.a < 0.99 ) 00430 { 00431 svg_attr_value( aStream, "stroke", "none" ); 00432 } 00433 else 00434 { 00435 svg_stroke_width( pls ); 00436 svg_stroke_color( pls ); 00437 } 00438 svg_fill_color( pls ); 00439 if ( pls->dev_eofill ) 00440 svg_attr_value( aStream, "fill-rule", "evenodd" ); 00441 else 00442 svg_attr_value( aStream, "fill-rule", "nonzero" ); 00443 } 00444 else 00445 { 00446 svg_stroke_width( pls ); 00447 svg_stroke_color( pls ); 00448 svg_attr_value( aStream, "fill", "none" ); 00449 } 00450 //svg_attr_value(aStream, "shape-rendering", "crispEdges"); 00451 svg_indent( aStream ); 00452 fprintf( aStream->svgFile, "points=\"" ); 00453 for ( i = 0; i < npts; i++ ) 00454 { 00455 fprintf( aStream->svgFile, "%.2f,%.2f ", (double) xa[i] / aStream->scale, (double) ya[i] / aStream->scale ); 00456 if ( ( ( i + 1 ) % 10 ) == 0 ) 00457 { 00458 fprintf( aStream->svgFile, "\n" ); 00459 svg_indent( aStream ); 00460 } 00461 } 00462 fprintf( aStream->svgFile, "\"/>\n" ); 00463 aStream->svgIndent -= 2; 00464 } 00465 00466 //-------------------------------------------------------------------------- 00467 // gradient() 00468 // 00469 // Draws gradient 00470 //-------------------------------------------------------------------------- 00471 00472 void gradient( PLStream *pls, short *xa, short *ya, PLINT npts ) 00473 { 00474 int i; 00475 // 27 should be the maximum needed below, but be generous. 00476 char buffer[50]; 00477 SVG *aStream; 00478 00479 aStream = pls->dev; 00480 00481 svg_open( aStream, "g>" ); 00482 svg_open( aStream, "defs>" ); 00483 svg_open( aStream, "linearGradient" ); 00484 // Allows ~2^31 unique gradient id's, gradient_index incremented below. 00485 sprintf( buffer, "MyGradient%010d", aStream->gradient_index ); 00486 svg_attr_value( aStream, "id", buffer ); 00487 svg_attr_value( aStream, "gradientUnits", "userSpaceOnUse" ); 00488 sprintf( buffer, "%.2f", pls->xgradient[0] / aStream->scale ); 00489 svg_attr_value( aStream, "x1", buffer ); 00490 sprintf( buffer, "%.2f", pls->ygradient[0] / aStream->scale ); 00491 svg_attr_value( aStream, "y1", buffer ); 00492 sprintf( buffer, "%.2f", pls->xgradient[1] / aStream->scale ); 00493 svg_attr_value( aStream, "x2", buffer ); 00494 sprintf( buffer, "%.2f", pls->ygradient[1] / aStream->scale ); 00495 svg_attr_value( aStream, "y2", buffer ); 00496 svg_general( aStream, ">\n" ); 00497 00498 for ( i = 0; i < pls->ncol1; i++ ) 00499 { 00500 svg_indent( aStream ); 00501 fprintf( aStream->svgFile, "<stop offset=\"%.3f\" ", 00502 (double) i / (double) ( pls->ncol1 - 1 ) ); 00503 fprintf( aStream->svgFile, "stop-color=\"#" ); 00504 write_hex( aStream->svgFile, pls->cmap1[i].r ); 00505 write_hex( aStream->svgFile, pls->cmap1[i].g ); 00506 write_hex( aStream->svgFile, pls->cmap1[i].b ); 00507 fprintf( aStream->svgFile, "\" " ); 00508 fprintf( aStream->svgFile, "stop-opacity=\"%.3f\"/>\n", pls->cmap1[i].a ); 00509 } 00510 00511 svg_close( aStream, "linearGradient" ); 00512 svg_close( aStream, "defs" ); 00513 svg_open( aStream, "polyline" ); 00514 sprintf( buffer, "url(#MyGradient%010d)", aStream->gradient_index++ ); 00515 svg_attr_value( aStream, "fill", buffer ); 00516 svg_indent( aStream ); 00517 fprintf( aStream->svgFile, "points=\"" ); 00518 for ( i = 0; i < npts; i++ ) 00519 { 00520 fprintf( aStream->svgFile, "%.2f,%.2f ", (double) xa[i] / aStream->scale, (double) ya[i] / aStream->scale ); 00521 if ( ( ( i + 1 ) % 10 ) == 0 ) 00522 { 00523 fprintf( aStream->svgFile, "\n" ); 00524 svg_indent( aStream ); 00525 } 00526 } 00527 fprintf( aStream->svgFile, "\"/>\n" ); 00528 aStream->svgIndent -= 2; 00529 svg_close( aStream, "g" ); 00530 } 00531 00532 //-------------------------------------------------------------------------- 00533 // proc_str() 00534 // 00535 // Processes strings for display. 00536 // 00537 // NOTE: 00538 // 00539 // (1) This was tested on Firefox and Camino where it seemed to display 00540 // text properly. However, it isn't obvious to me that these browsers 00541 // conform to the specification. Basically the issue is that some of 00542 // the text properties (i.e. dy) that you specify inside a tspan element 00543 // remain in force until the end of the text element. It would seem to 00544 // me that they should only apply inside the tspan tag. To get around 00545 // this, and because it was easier anyway, I used what is essentially 00546 // a list of tspan tags rather than a tree of tspan tags. Perhaps 00547 // better described as a tree with one branch? 00548 // 00549 // (2) To deal with the some whitespace annoyances, the entire text 00550 // element must be written on a single line. If there are lots of 00551 // format characters then this line might end up being too long 00552 // for some SVG implementations. 00553 // 00554 // (3) Text placement is not ideal. Vertical offset seems to be 00555 // particularly troublesome. 00556 // 00557 // (4) See additional notes in specify_font re. to sans / serif 00558 // 00559 //-------------------------------------------------------------------------- 00560 00561 void proc_str( PLStream *pls, EscText *args ) 00562 { 00563 char plplot_esc; 00564 static short which_clip = 0; 00565 short i; 00566 short totalTags = 1; 00567 short ucs4Len = (short) args->unicode_array_len; 00568 double ftHt, scaled_offset, scaled_ftHt; 00569 PLUNICODE fci; 00570 PLINT rcx[4], rcy[4]; 00571 static PLINT prev_rcx[4], prev_rcy[4]; 00572 PLFLT rotation, shear, stride, cos_rot, sin_rot, sin_shear, cos_shear; 00573 PLFLT t[4]; 00574 int glyph_size, sum_glyph_size; 00575 short if_write; 00576 // PLFLT *t = args->xform; 00577 PLUNICODE *ucs4 = args->unicode_array; 00578 SVG *aStream; 00579 PLFLT old_sscale, sscale, old_soffset, soffset, old_dup, ddup; 00580 PLINT level; 00581 PLINT same_clip; 00582 00583 // check that we got unicode 00584 if ( ucs4Len == 0 ) 00585 { 00586 printf( "Non unicode string passed to SVG driver, ignoring\n" ); 00587 return; 00588 } 00589 00590 // get plplot escape character and the current font 00591 plgesc( &plplot_esc ); 00592 plgfci( &fci ); 00593 00594 // determine the font height in points. 00595 ftHt = FONT_SIZE_RATIO * pls->chrht * POINTS_PER_INCH / 25.4; 00596 00597 // Setup & apply text clipping area if desired 00598 aStream = (SVG *) pls->dev; 00599 if ( aStream->textClipping ) 00600 { 00601 // Use PLplot core routine difilt_clip to appropriately 00602 // transform the coordinates of the clipping rectangle 00603 difilt_clip( rcx, rcy ); 00604 same_clip = TRUE; 00605 if ( which_clip == 0 ) 00606 { 00607 same_clip = FALSE; 00608 } 00609 else 00610 { 00611 for ( i = 0; i < 4; i++ ) 00612 { 00613 if ( rcx[i] != prev_rcx[i] || 00614 rcy[i] != prev_rcy[i] ) 00615 same_clip = FALSE; 00616 } 00617 } 00618 if ( !same_clip ) 00619 { 00620 svg_open( aStream, "clipPath" ); 00621 svg_attr_values( aStream, "id", "text-clipping%d", which_clip ); 00622 svg_general( aStream, ">\n" ); 00623 00624 // Output a polygon to represent the clipping region. 00625 svg_open( aStream, "polygon" ); 00626 svg_attr_values( aStream, 00627 "points", 00628 "%f,%f %f,%f %f,%f %f,%f", 00629 ( (PLFLT) rcx[0] ) / aStream->scale, 00630 ( (PLFLT) rcy[0] ) / aStream->scale, 00631 ( (PLFLT) rcx[1] ) / aStream->scale, 00632 ( (PLFLT) rcy[1] ) / aStream->scale, 00633 ( (PLFLT) rcx[2] ) / aStream->scale, 00634 ( (PLFLT) rcy[2] ) / aStream->scale, 00635 ( (PLFLT) rcx[3] ) / aStream->scale, 00636 ( (PLFLT) rcy[3] ) / aStream->scale ); 00637 svg_open_end( aStream ); 00638 00639 svg_close( aStream, "clipPath" ); 00640 for ( i = 0; i < 4; i++ ) 00641 { 00642 prev_rcx[i] = rcx[i]; 00643 prev_rcy[i] = rcy[i]; 00644 } 00645 which_clip++; 00646 } 00647 svg_open( aStream, "g" ); 00648 svg_attr_values( aStream, "clip-path", 00649 "url(#text-clipping%d)", which_clip - 1 ); 00650 svg_general( aStream, ">\n" ); 00651 } 00652 00653 // This draws the clipping region on the screen which can 00654 // be very helpful for debugging. 00655 00656 // 00657 // svg_open(aStream, "polygon"); 00658 // svg_attr_values(aStream, 00659 // "points", 00660 // "%f,%f %f,%f %f,%f %f,%f", 00661 // ((PLFLT)rcx[0])/aStream->scale, 00662 // ((PLFLT)rcy[0])/aStream->scale, 00663 // ((PLFLT)rcx[1])/aStream->scale, 00664 // ((PLFLT)rcy[1])/aStream->scale, 00665 // ((PLFLT)rcx[2])/aStream->scale, 00666 // ((PLFLT)rcy[2])/aStream->scale, 00667 // ((PLFLT)rcx[3])/aStream->scale, 00668 // ((PLFLT)rcy[3])/aStream->scale); 00669 // svg_stroke_width(pls); 00670 // svg_stroke_color(pls); 00671 // svg_attr_value(aStream, "fill", "none"); 00672 // svg_open_end(aStream); 00673 // 00674 00675 // Calculate the tranformation matrix for SVG based on the 00676 // transformation matrix provided by PLplot. 00677 plRotationShear( args->xform, &rotation, &shear, &stride ); 00678 // N.B. Experimentally, I (AWI) have found the svg rotation angle is 00679 // the negative of the libcairo rotation angle, and the svg shear angle 00680 // is pi minus the libcairo shear angle. 00681 rotation -= pls->diorot * PI / 2.0; 00682 cos_rot = cos( rotation ); 00683 sin_rot = -sin( rotation ); 00684 sin_shear = sin( shear ); 00685 cos_shear = -cos( shear ); 00686 t[0] = cos_rot * stride; 00687 t[1] = -sin_rot * stride; 00688 t[2] = cos_rot * sin_shear + sin_rot * cos_shear; 00689 t[3] = -sin_rot * sin_shear + cos_rot * cos_shear; 00690 00691 //-------------- 00692 // open text tag 00693 // -------------- 00694 00695 svg_open( aStream, "text" ); 00696 00697 svg_attr_value( aStream, "dominant-baseline", "no-change" ); 00698 00699 // set font color 00700 svg_fill_color( pls ); 00701 00702 // white space preserving mode 00703 svg_attr_value( aStream, "xml:space", "preserve" ); 00704 00705 // set the font size 00706 svg_attr_values( aStream, "font-size", "%d", (int) ftHt ); 00707 00708 // Apply coordinate transform for text display. 00709 // The transformation also defines the location of the text in x and y. 00710 svg_attr_values( aStream, "transform", "matrix(%f %f %f %f %f %f)", 00711 t[0], t[1], t[2], t[3], 00712 (double) ( args->x / aStream->scale ), 00713 (double) ( args->y / aStream->scale ) ); 00714 00715 00716 //---------------------------------------------------------- 00717 // Write the text with formatting 00718 // We just keep stacking up tspan tags, then close them all 00719 // after we have written out all of the text. 00720 // ---------------------------------------------------------- 00721 00722 // For if_write = 0, we write nothing and instead accumulate the 00723 // sum_glyph_size from the fontsize of the individual glyphs which 00724 // is then used to figure out the initial x position from text-anchor and 00725 // args->just that is used to write out the SVG xml for if_write = 1. 00726 00727 glyph_size = (int) ftHt; 00728 sum_glyph_size = 0; 00729 if_write = 0; 00730 while ( if_write < 2 ) 00731 { 00732 if ( if_write == 1 ) 00733 { 00734 //printf("number of characters = %f\n", sum_glyph_size/ftHt); 00735 // The above coordinate transform defines the _raw_ x position of the 00736 // text without justification so this attribute value depends on 00737 // text-anchor and args->just*sum_glyph_size 00738 // N.B. sum_glyph_size calculation only correct for monospaced fonts 00739 // so generally sum_glyph_size will be overestimated by various amounts 00740 // depending on what glyphs are to be rendered, the font, etc. However, 00741 // this correction is differential respect to the end points or the 00742 // middle so you should be okay so long as you don't deviate too far 00743 // from those anchor points. 00744 if ( args->just < 0.33 ) 00745 { 00746 svg_attr_value( aStream, "text-anchor", "start" ); // left justification 00747 svg_attr_values( aStream, "x", "%f", (double) ( -args->just * sum_glyph_size ) ); 00748 } 00749 else if ( args->just > 0.66 ) 00750 { 00751 svg_attr_value( aStream, "text-anchor", "end" ); // right justification 00752 svg_attr_values( aStream, "x", "%f", (double) ( ( 1. - args->just ) * sum_glyph_size ) ); 00753 } 00754 else 00755 { 00756 svg_attr_value( aStream, "text-anchor", "middle" ); // center 00757 svg_attr_values( aStream, "x", "%f", (double) ( ( 0.5 - args->just ) * sum_glyph_size ) ); 00758 } 00759 00760 // The text goes at zero in y since the above 00761 // coordinate transform defines the y position of the text 00762 svg_attr_values( aStream, "y", "%f", 00763 FONT_SHIFT_RATIO * 0.5 * ftHt + 00764 FONT_SHIFT_OFFSET ); 00765 00766 fprintf( aStream->svgFile, ">" ); 00767 00768 // specify the initial font 00769 specify_font( aStream->svgFile, fci ); 00770 } 00771 i = 0; 00772 scaled_ftHt = ftHt; 00773 level = 0; 00774 ddup = 0.; 00775 while ( i < ucs4Len ) 00776 { 00777 if ( ucs4[i] < PL_FCI_MARK ) // not a font change 00778 { 00779 if ( ucs4[i] != (PLUNICODE) plplot_esc ) // a character to display 00780 { 00781 if ( if_write ) 00782 { 00783 write_unicode( aStream->svgFile, ucs4[i] ); 00784 } 00785 else 00786 { 00787 sum_glyph_size += glyph_size; 00788 } 00789 i++; 00790 continue; 00791 } 00792 i++; 00793 if ( ucs4[i] == (PLUNICODE) plplot_esc ) // a escape character to display 00794 { 00795 if ( if_write ) 00796 { 00797 write_unicode( aStream->svgFile, ucs4[i] ); 00798 } 00799 else 00800 { 00801 sum_glyph_size += glyph_size; 00802 } 00803 i++; 00804 continue; 00805 } 00806 else 00807 { 00808 // super/subscript logic follows that in plstr routine (plsym.c) 00809 // for Hershey fonts. Factor of FONT_SHIFT_RATIO*0.80 is empirical 00810 // adjustment. 00811 if ( ucs4[i] == (PLUNICODE) 'u' ) // Superscript 00812 { 00813 plP_script_scale( TRUE, &level, 00814 &old_sscale, &sscale, &old_soffset, &soffset ); 00815 // The correction for the difference in magnitude 00816 // between the baseline and middle coordinate systems 00817 // for superscripts should be 00818 // 0.5*(base font size - superscript/subscript font size). 00819 old_dup = ddup; 00820 ddup = 0.5 * ( 1.0 - sscale ); 00821 if ( level <= 0 ) 00822 { 00823 scaled_offset = FONT_SHIFT_RATIO * ftHt * ( 0.80 * ( soffset - old_soffset ) - ( ddup - old_dup ) ); 00824 } 00825 else 00826 { 00827 scaled_offset = -FONT_SHIFT_RATIO * ftHt * ( 0.80 * ( soffset - old_soffset ) + ( ddup - old_dup ) ); 00828 } 00829 scaled_ftHt = sscale * ftHt; 00830 if ( if_write ) 00831 { 00832 totalTags++; 00833 fprintf( aStream->svgFile, "<tspan dy=\"%f\" font-size=\"%d\">", scaled_offset, (int) scaled_ftHt ); 00834 } 00835 else 00836 { 00837 glyph_size = (int) scaled_ftHt; 00838 } 00839 } 00840 if ( ucs4[i] == (PLUNICODE) 'd' ) // Subscript 00841 { 00842 plP_script_scale( FALSE, &level, 00843 &old_sscale, &sscale, &old_soffset, &soffset ); 00844 // The correction for the difference in magnitude 00845 // between the baseline and middle coordinate systems 00846 // for superscripts should be 00847 // 0.5*(base font size - superscript/subscript font size). 00848 old_dup = ddup; 00849 ddup = 0.5 * ( 1.0 - sscale ); 00850 if ( level < 0 ) 00851 { 00852 scaled_offset = FONT_SHIFT_RATIO * ftHt * ( 0.80 * ( soffset - old_soffset ) - ( ddup - old_dup ) ); 00853 } 00854 else 00855 { 00856 scaled_offset = -FONT_SHIFT_RATIO * ftHt * ( 0.80 * ( soffset - old_soffset ) + ( ddup - old_dup ) ); 00857 } 00858 scaled_ftHt = sscale * ftHt; 00859 if ( if_write ) 00860 { 00861 totalTags++; 00862 fprintf( aStream->svgFile, "<tspan dy=\"%f\" font-size=\"%d\">", scaled_offset, (int) scaled_ftHt ); 00863 } 00864 else 00865 { 00866 glyph_size = (int) scaled_ftHt; 00867 } 00868 } 00869 i++; 00870 } 00871 } 00872 else // a font change 00873 { 00874 if ( if_write ) 00875 { 00876 specify_font( aStream->svgFile, ucs4[i] ); 00877 totalTags++; 00878 } 00879 i++; 00880 } 00881 } 00882 if_write++; 00883 } 00884 00885 //---------------------------------------------- 00886 // close out all the tspan tags and the text tag 00887 // ---------------------------------------------- 00888 00889 for ( i = 0; i < totalTags; i++ ) 00890 { 00891 fprintf( aStream->svgFile, "</tspan>" ); 00892 } 00893 // The following commented out (by AWI) because it is a bad idea to 00894 // put line ends in the middle of a text tag. This was the key to 00895 // all the text rendering issues we had. 00896 //fprintf(svgFile,"\n"); 00897 // For the same reason use fprintf and svgIndent -= 2; 00898 // to close the text tag rather than svg_close("text"); since 00899 // we don't want indentation spaces entering the text. 00900 // svg_close("text"); 00901 fprintf( aStream->svgFile, "</text>\n" ); 00902 aStream->svgIndent -= 2; 00903 if ( aStream->textClipping ) 00904 { 00905 svg_close( aStream, "g" ); 00906 } 00907 } 00908 00909 //-------------------------------------------------------------------------- 00910 // svg_open () 00911 // 00912 // Used to open a new XML expression, sets the indent level appropriately 00913 //-------------------------------------------------------------------------- 00914 00915 void svg_open( SVG *aStream, const char *tag ) 00916 { 00917 svg_indent( aStream ); 00918 fprintf( aStream->svgFile, "<%s\n", tag ); 00919 aStream->svgIndent += 2; 00920 } 00921 00922 //-------------------------------------------------------------------------- 00923 // svg_open_end () 00924 // 00925 // Used to end the opening of a new XML expression i.e. add 00926 // the final ">". 00927 //-------------------------------------------------------------------------- 00928 00929 void svg_open_end( SVG *aStream ) 00930 { 00931 svg_indent( aStream ); 00932 fprintf( aStream->svgFile, "/>\n" ); 00933 aStream->svgIndent -= 2; 00934 } 00935 00936 //-------------------------------------------------------------------------- 00937 // svg_attr_value () 00938 // 00939 // Prints two strings to svgFile as a XML attribute value pair 00940 // i.e. foo="bar" 00941 //-------------------------------------------------------------------------- 00942 00943 void svg_attr_value( SVG *aStream, const char *attribute, const char *value ) 00944 { 00945 svg_indent( aStream ); 00946 fprintf( aStream->svgFile, "%s=\"%s\"\n", attribute, value ); 00947 } 00948 00949 //-------------------------------------------------------------------------- 00950 // svg_attr_values () 00951 // 00952 // Prints a string and a bunch of numbers / strings as a XML attribute 00953 // value pair i.e. foo="0 10 1.0 5.3 bar" 00954 // 00955 // This function is derived from an example in 00956 // "The C Programming Language" by Kernighan and Ritchie. 00957 // 00958 //-------------------------------------------------------------------------- 00959 00960 void svg_attr_values( SVG *aStream, const char *attribute, const char *format, ... ) 00961 { 00962 va_list ap; 00963 const char *p, *sval; 00964 int ival; 00965 double dval; 00966 00967 svg_indent( aStream ); 00968 fprintf( aStream->svgFile, "%s=\"", attribute ); 00969 va_start( ap, format ); 00970 for ( p = format; *p; p++ ) 00971 { 00972 if ( *p != '%' ) 00973 { 00974 fprintf( aStream->svgFile, "%c", *p ); 00975 continue; 00976 } 00977 switch ( *++p ) 00978 { 00979 case 'd': 00980 ival = va_arg( ap, int ); 00981 fprintf( aStream->svgFile, "%d", ival ); 00982 break; 00983 case 'f': 00984 dval = va_arg( ap, double ); 00985 fprintf( aStream->svgFile, "%f", dval ); 00986 break; 00987 case 'r': 00988 // r is non-standard, but use it here to format rounded value 00989 dval = va_arg( ap, double ); 00990 fprintf( aStream->svgFile, "%.2f", dval ); 00991 break; 00992 case 's': 00993 sval = va_arg( ap, char * ); 00994 fprintf( aStream->svgFile, "%s", sval ); 00995 break; 00996 default: 00997 fprintf( aStream->svgFile, "%c", *p ); 00998 break; 00999 } 01000 } 01001 fprintf( aStream->svgFile, "\"\n" ); 01002 va_end( ap ); 01003 } 01004 01005 //-------------------------------------------------------------------------- 01006 // svg_close () 01007 // 01008 // Used to close a XML expression, sets the indent level appropriately 01009 //-------------------------------------------------------------------------- 01010 01011 void svg_close( SVG *aStream, const char *tag ) 01012 { 01013 aStream->svgIndent -= 2; 01014 svg_indent( aStream ); 01015 if ( strlen( tag ) > 0 ) 01016 { 01017 fprintf( aStream->svgFile, "</%s>\n", tag ); 01018 } 01019 else 01020 { 01021 fprintf( aStream->svgFile, "/>\n" ); 01022 } 01023 } 01024 01025 //-------------------------------------------------------------------------- 01026 // svg_general () 01027 // 01028 // Used to print any text into the svgFile 01029 //-------------------------------------------------------------------------- 01030 01031 void svg_general( SVG *aStream, const char *text ) 01032 { 01033 svg_indent( aStream ); 01034 fprintf( aStream->svgFile, "%s", text ); 01035 } 01036 01037 //-------------------------------------------------------------------------- 01038 // svg_indent () 01039 // 01040 // Indents properly based on the current indent level 01041 //-------------------------------------------------------------------------- 01042 01043 void svg_indent( SVG *aStream ) 01044 { 01045 short i; 01046 for ( i = 0; i < aStream->svgIndent; i++ ) 01047 { 01048 fprintf( aStream->svgFile, " " ); 01049 } 01050 } 01051 01052 //-------------------------------------------------------------------------- 01053 // svg_stroke_width () 01054 // 01055 // sets the stroke width based on the current width 01056 //-------------------------------------------------------------------------- 01057 01058 void svg_stroke_width( PLStream *pls ) 01059 { 01060 SVG *aStream; 01061 01062 aStream = pls->dev; 01063 svg_indent( aStream ); 01064 fprintf( aStream->svgFile, "stroke-width=\"%e\"\n", pls->width ); 01065 } 01066 01067 //-------------------------------------------------------------------------- 01068 // svg_stroke_color () 01069 // 01070 // sets the stroke color based on the current color 01071 //-------------------------------------------------------------------------- 01072 01073 void svg_stroke_color( PLStream *pls ) 01074 { 01075 SVG *aStream; 01076 01077 aStream = pls->dev; 01078 svg_indent( aStream ); 01079 fprintf( aStream->svgFile, "stroke=\"#" ); 01080 write_hex( aStream->svgFile, pls->curcolor.r ); 01081 write_hex( aStream->svgFile, pls->curcolor.g ); 01082 write_hex( aStream->svgFile, pls->curcolor.b ); 01083 fprintf( aStream->svgFile, "\"\n" ); 01084 svg_indent( aStream ); 01085 fprintf( aStream->svgFile, "stroke-opacity=\"%f\"\n", pls->curcolor.a ); 01086 } 01087 01088 //-------------------------------------------------------------------------- 01089 // svg_fill_color () 01090 // 01091 // sets the fill color based on the current color 01092 //-------------------------------------------------------------------------- 01093 01094 void svg_fill_color( PLStream *pls ) 01095 { 01096 SVG *aStream; 01097 01098 aStream = pls->dev; 01099 svg_indent( aStream ); 01100 fprintf( aStream->svgFile, "fill=\"#" ); 01101 write_hex( aStream->svgFile, pls->curcolor.r ); 01102 write_hex( aStream->svgFile, pls->curcolor.g ); 01103 write_hex( aStream->svgFile, pls->curcolor.b ); 01104 fprintf( aStream->svgFile, "\"\n" ); 01105 svg_indent( aStream ); 01106 fprintf( aStream->svgFile, "fill-opacity=\"%f\"\n", pls->curcolor.a ); 01107 } 01108 01109 //-------------------------------------------------------------------------- 01110 // svg_fill_background_color () 01111 // 01112 // sets the background fill color based on the current background color 01113 //-------------------------------------------------------------------------- 01114 01115 void svg_fill_background_color( PLStream *pls ) 01116 { 01117 SVG *aStream; 01118 01119 aStream = pls->dev; 01120 svg_indent( aStream ); 01121 fprintf( aStream->svgFile, "fill=\"#" ); 01122 write_hex( aStream->svgFile, pls->cmap0[0].r ); 01123 write_hex( aStream->svgFile, pls->cmap0[0].g ); 01124 write_hex( aStream->svgFile, pls->cmap0[0].b ); 01125 fprintf( aStream->svgFile, "\"\n" ); 01126 svg_indent( aStream ); 01127 fprintf( aStream->svgFile, "fill-opacity=\"%f\"\n", pls->cmap0[0].a ); 01128 } 01129 01130 //-------------------------------------------------------------------------- 01131 // svg_family_check () 01132 // 01133 // support function to help supress more than one page if family file 01134 // output not specified by the user (e.g., with the -fam command-line option). 01135 //-------------------------------------------------------------------------- 01136 01137 int svg_family_check( PLStream *pls ) 01138 { 01139 if ( pls->family || pls->page == 1 ) 01140 { 01141 return 0; 01142 } 01143 else 01144 { 01145 if ( !already_warned ) 01146 { 01147 already_warned = 1; 01148 plwarn( "All pages after the first skipped because family file output not specified.\n" ); 01149 } 01150 return 1; 01151 } 01152 } 01153 01154 //-------------------------------------------------------------------------- 01155 // write_hex () 01156 // 01157 // writes a unsigned char as an appropriately formatted hex value 01158 //-------------------------------------------------------------------------- 01159 01160 void write_hex( FILE *svgFile, unsigned char val ) 01161 { 01162 if ( val < 16 ) 01163 { 01164 fprintf( svgFile, "0%X", val ); 01165 } 01166 else 01167 { 01168 fprintf( svgFile, "%X", val ); 01169 } 01170 } 01171 01172 //-------------------------------------------------------------------------- 01173 // write_unicode () 01174 // 01175 // writes a unicode character, appropriately formatted (i.e. &#xNNN) 01176 // with invalid xml characters replaced by ' '. 01177 //-------------------------------------------------------------------------- 01178 01179 void write_unicode( FILE *svgFile, PLUNICODE ucs4_char ) 01180 { 01181 if ( ucs4_char >= ' ' || ucs4_char == '\t' || ucs4_char == '\n' || ucs4_char == '\r' ) 01182 fprintf( svgFile, "&#x%x;", ucs4_char ); 01183 else 01184 fprintf( svgFile, "&#x%x;", ' ' ); 01185 } 01186 01187 //-------------------------------------------------------------------------- 01188 // specify_font () 01189 // 01190 // Note: 01191 // We don't actually specify a font, just the fonts properties. 01192 // The hope is that this will give the display program the freedom 01193 // to choose the font with the glyphs that it needs to display 01194 // the text. 01195 // 01196 // Known Issues: 01197 // (1) On OS-X 10.4 with Firefox and Camino the "serif" font-family 01198 // looks more like the "italic" font-style. 01199 // 01200 //-------------------------------------------------------------------------- 01201 01202 void specify_font( FILE *svgFile, PLUNICODE ucs4_char ) 01203 { 01204 fprintf( svgFile, "<tspan " ); 01205 01206 // sans, serif, mono, script, symbol 01207 01208 if ( ( ucs4_char & 0x00F ) == 0x000 ) 01209 { 01210 fprintf( svgFile, "font-family=\"sans-serif\" " ); 01211 } 01212 else if ( ( ucs4_char & 0x00F ) == 0x001 ) 01213 { 01214 fprintf( svgFile, "font-family=\"serif\" " ); 01215 } 01216 else if ( ( ucs4_char & 0x00F ) == 0x002 ) 01217 { 01218 fprintf( svgFile, "font-family=\"mono-space\" " ); 01219 } 01220 else if ( ( ucs4_char & 0x00F ) == 0x003 ) 01221 { 01222 fprintf( svgFile, "font-family=\"cursive\" " ); 01223 } 01224 else if ( ( ucs4_char & 0x00F ) == 0x004 ) 01225 { 01226 // this should be symbol, but that doesn't seem to be available 01227 fprintf( svgFile, "font-family=\"sans-serif\" " ); 01228 } 01229 01230 // normal, italic, oblique 01231 01232 if ( ( ucs4_char & 0x0F0 ) == 0x000 ) 01233 { 01234 fprintf( svgFile, "font-style=\"normal\" " ); 01235 } 01236 else if ( ( ucs4_char & 0x0F0 ) == 0x010 ) 01237 { 01238 fprintf( svgFile, "font-style=\"italic\" " ); 01239 } 01240 else if ( ( ucs4_char & 0x0F0 ) == 0x020 ) 01241 { 01242 fprintf( svgFile, "font-style=\"oblique\" " ); 01243 } 01244 01245 // normal, bold 01246 01247 if ( ( ucs4_char & 0xF00 ) == 0x000 ) 01248 { 01249 fprintf( svgFile, "font-weight=\"normal\">" ); 01250 } 01251 else if ( ( ucs4_char & 0xF00 ) == 0x100 ) 01252 { 01253 fprintf( svgFile, "font-weight=\"bold\">" ); 01254 } 01255 }