1 /***************************************************************************
2 *
3 * $Id: svg_out.c 186 2011-03-01 21:18:19Z Michael.McTernan $
4 *
5 * This file is part of mscgen, a message sequence chart renderer.
6 * Copyright (C) 2005 Michael C McTernan, Michael.McTernan.2001@cs.bris.ac.uk
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 **************************************************************************/
22
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 #include <math.h>
27 #include <stdio.h>
28 #include <errno.h>
29 #include <stdlib.h>
30 #include <assert.h>
31 #include <string.h>
32 #include "adraw_int.h"
33 #include "safe.h"
34 #include "utf8.h"
35
36 /***************************************************************************
37 * Local types
38 ***************************************************************************/
39
40 typedef struct SvgContextTag
41 {
42 /** Output file. */
43 FILE *of;
44
45 /** Current pen colour name. */
46 const char *penColName;
47
48 /** Current background pen colour name. */
49 const char *penBgColName;
50
51 int fontPoints;
52 }
53 SvgContext;
54
55 typedef struct
56 {
57 int capheight, xheight, ascender, descender;
58 int widths[256];
59 }
60 SvgCharMetric;
61
62 /** Helvetica character widths.
63 * This gives the width of each character is 1/1000ths of a point.
64 * The values are taken from the Adobe Font Metric file for Hevletica.
65 */
66 static const SvgCharMetric SvgHelvetica =
67 {
68 718, 523, 718, -207,
69 {
70 -1, -1, -1, -1, -1, -1, -1, -1,
71 -1, -1, -1, -1, -1, -1, -1, -1,
72 -1, -1, -1, -1, -1, -1, -1, -1,
73 -1, -1, -1, -1, -1, -1, -1, -1,
74 278, 278, 355, 556, 556, 889, 667, 222,
75 333, 333, 389, 584, 278, 333, 278, 278,
76 556, 556, 556, 556, 556, 556, 556, 556,
77 556, 556, 278, 278, 584, 584, 584, 556,
78 1015, 667, 667, 722, 722, 667, 611, 778,
79 722, 278, 500, 667, 556, 833, 722, 778,
80 667, 778, 722, 667, 611, 722, 667, 944,
81 667, 667, 611, 278, 278, 278, 469, 556,
82 222, 556, 556, 500, 556, 556, 278, 556,
83 556, 222, 222, 500, 222, 833, 556, 556,
84 556, 556, 333, 500, 278, 556, 500, 722,
85 500, 500, 500, 334, 260, 334, 584, -1,
86 -1, -1, -1, -1, -1, -1, -1, -1,
87 -1, -1, -1, -1, -1, -1, -1, -1,
88 -1, -1, -1, -1, -1, -1, -1, -1,
89 -1, -1, -1, -1, -1, -1, -1, -1,
90 -1, 333, 556, 556, 167, 556, 556, 556,
91 556, 191, 333, 556, 333, 333, 500, 500,
92 -1, 556, 556, 556, 278, -1, 537, 350,
93 222, 333, 333, 556, 1000, 1000, -1, 611,
94 -1, 333, 333, 333, 333, 333, 333, 333,
95 333, -1, 333, 333, -1, 333, 333, 333,
96 1000, -1, -1, -1, -1, -1, -1, -1,
97 -1, -1, -1, -1, -1, -1, -1, -1,
98 -1, 1000, -1, 370, -1, -1, -1, -1,
99 556, 778, 1000, 365, -1, -1, -1, -1,
100 -1, 889, -1, -1, -1, 278, -1, -1,
101 222, 611, 944, 611, -1, -1, -1, -1
102 }
103 };
104
105 /***************************************************************************
106 * Helper functions
107 ***************************************************************************/
108
109
110 /** Get the context pointer from an ADraw structure.
111 */
getSvgCtx(struct ADrawTag * ctx)112 static SvgContext *getSvgCtx(struct ADrawTag *ctx)
113 {
114 return (SvgContext *)ctx->internal;
115 }
116
117 /** Get the context pointer from an ADraw structure.
118 */
getSvgFile(struct ADrawTag * ctx)119 static FILE *getSvgFile(struct ADrawTag *ctx)
120 {
121 return getSvgCtx(ctx)->of;
122 }
123
getSvgPen(struct ADrawTag * ctx)124 static const char *getSvgPen(struct ADrawTag *ctx)
125 {
126 return getSvgCtx(ctx)->penColName;
127 }
128
getSvgBgPen(struct ADrawTag * ctx)129 static const char *getSvgBgPen(struct ADrawTag *ctx)
130 {
131 return getSvgCtx(ctx)->penBgColName;
132 }
133
134 /** Given a font metric measurement, return device dependent units.
135 * Font metric data is stored as 1/1000th of a point, and therefore
136 * needs to be multiplied by the font point size and divided by
137 * 1000 to give a value in device dependent units.
138 */
getSpace(struct ADrawTag * ctx,long thousanths)139 static int getSpace(struct ADrawTag *ctx, long thousanths)
140 {
141 return ((thousanths * getSvgCtx(ctx)->fontPoints) + 500) / 1000;
142 }
143
144
145 /** Compute a point on an ellipse.
146 * This computes the point on an ellipse.
147 *
148 * \param[in] cx,cy Center of the ellipse.
149 * \param[in] w,h Ellipse width and height.
150 * \param[in] a Angle in degrees.
151 * \param[in,out] x,y Pointer to be populated with result co-ordinates.
152 */
arcPoint(float cx,float cy,float w,float h,float a,unsigned int * x,unsigned int * y)153 static void arcPoint(float cx,
154 float cy,
155 float w,
156 float h,
157 float a,
158 unsigned int *x,
159 unsigned int *y)
160 {
161 float rad = (a * M_PI) / 180.0f;
162
163 /* Compute point, noting this is for SVG co-ordinate system */
164 *x = round(cx + ((w / 2.0f) * cos(rad)));
165 *y = round(cy + ((h / 2.0f) * sin(rad)));
166 }
167
168
169 /** Write out a line of text, escaping special characters.
170 */
writeEscaped(struct ADrawTag * ctx,const char * string)171 static void writeEscaped(struct ADrawTag *ctx, const char *string)
172 {
173 FILE *f = getSvgFile(ctx);
174
175 while(*string != '\0')
176 {
177 unsigned int code, bytes;
178
179 switch(*string)
180 {
181 case '<': fprintf(f, "<"); break;
182 case '>': fprintf(f, ">"); break;
183 case '"': fprintf(f, """); break;
184 case '&': fprintf(f, "&"); break;
185 default:
186 if(Utf8Decode(string, &code, &bytes))
187 {
188 fprintf(f, "&#x%x;", code);
189 string += bytes - 1;
190 }
191 else
192 {
193 fputc(*string, f);
194 }
195 break;
196
197 }
198
199 string++;
200 }
201 }
202
203
204
svgRect(struct ADrawTag * ctx,const char * colour,unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2)205 static void svgRect(struct ADrawTag *ctx,
206 const char *colour,
207 unsigned int x1,
208 unsigned int y1,
209 unsigned int x2,
210 unsigned int y2)
211 {
212 fprintf(getSvgFile(ctx),
213 "<polygon fill=\"%s\" points=\"%u,%u %u,%u %u,%u %u,%u\"/>\n",
214 colour,
215 x1, y1,
216 x2, y1,
217 x2, y2,
218 x1, y2);
219 }
220
svgColour(ADrawColour col)221 static const char *svgColour(ADrawColour col)
222 {
223 switch(col)
224 {
225 case ADRAW_COL_WHITE:
226 return "white";
227
228 case ADRAW_COL_BLACK:
229 return "black";
230
231 case ADRAW_COL_BLUE:
232 return "blue";
233
234 case ADRAW_COL_RED:
235 return "red";
236
237 case ADRAW_COL_GREEN:
238 return "green";
239
240 default:
241 return NULL;
242 }
243 }
244
245
246 /***************************************************************************
247 * API Functions
248 ***************************************************************************/
249
SvgTextWidth(struct ADrawTag * ctx,const char * string)250 unsigned int SvgTextWidth(struct ADrawTag *ctx,
251 const char *string)
252 {
253 unsigned long width = 0;
254
255 while(*string != '\0')
256 {
257 int i = *string & 0xff;
258 unsigned long w = SvgHelvetica.widths[i];
259
260 /* Ignore undefined characters */
261 width += w > 0 ? w : 0;
262
263 string++;
264 }
265
266 return getSpace(ctx, width);
267 }
268
269
SvgTextHeight(struct ADrawTag * ctx)270 int SvgTextHeight(struct ADrawTag *ctx)
271 {
272 return getSpace(ctx, SvgHelvetica.ascender - SvgHelvetica.descender);
273 }
274
275
SvgLine(struct ADrawTag * ctx,unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2)276 void SvgLine(struct ADrawTag *ctx,
277 unsigned int x1,
278 unsigned int y1,
279 unsigned int x2,
280 unsigned int y2)
281 {
282 fprintf(getSvgFile(ctx),
283 "<line x1=\"%u\" y1=\"%u\" x2=\"%u\" y2=\"%u\" stroke=\"%s\"/>\n",
284 x1, y1, x2, y2, getSvgPen(ctx));
285
286 }
287
288
SvgDottedLine(struct ADrawTag * ctx,unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2)289 void SvgDottedLine(struct ADrawTag *ctx,
290 unsigned int x1,
291 unsigned int y1,
292 unsigned int x2,
293 unsigned int y2)
294 {
295 fprintf(getSvgFile(ctx),
296 "<line x1=\"%u\" y1=\"%u\" x2=\"%u\" y2=\"%u\" stroke=\"%s\" stroke-dasharray=\"2,2\"/>\n",
297 x1, y1, x2, y2, getSvgPen(ctx));
298 }
299
300
SvgTextR(struct ADrawTag * ctx,unsigned int x,unsigned int y,const char * string)301 void SvgTextR(struct ADrawTag *ctx,
302 unsigned int x,
303 unsigned int y,
304 const char *string)
305 {
306 SvgContext *context = getSvgCtx(ctx);
307
308 svgRect(ctx, getSvgBgPen(ctx), x - 2, y - SvgTextHeight(ctx) + 1, x + SvgTextWidth(ctx, string), y - 1);
309
310 y += getSpace(ctx, SvgHelvetica.descender);
311
312 fprintf(getSvgFile(ctx),
313 "<text x=\"%u\" y=\"%u\" textLength=\"%u\" font-family=\"Helvetica\" font-size=\"%u\" fill=\"%s\">\n",
314 x - 1, y, SvgTextWidth(ctx, string), context->fontPoints, context->penColName);
315 writeEscaped(ctx, string);
316 fprintf(getSvgFile(ctx), "\n</text>\n");
317 }
318
319
SvgTextL(struct ADrawTag * ctx,unsigned int x,unsigned int y,const char * string)320 void SvgTextL(struct ADrawTag *ctx,
321 unsigned int x,
322 unsigned int y,
323 const char *string)
324 {
325 SvgContext *context = getSvgCtx(ctx);
326
327 svgRect(ctx, getSvgBgPen(ctx), x - (SvgTextWidth(ctx, string) + 2), y - SvgTextHeight(ctx) + 1, x, y - 1);
328
329 y += getSpace(ctx, SvgHelvetica.descender);
330
331 fprintf(getSvgFile(ctx),
332 "<text x=\"%u\" y=\"%u\" textLength=\"%u\" font-family=\"Helvetica\" font-size=\"%u\" fill=\"%s\" text-anchor=\"end\">\n",
333 x, y, SvgTextWidth(ctx, string), context->fontPoints, context->penColName);
334 writeEscaped(ctx, string);
335 fprintf(getSvgFile(ctx), "\n</text>\n");
336
337
338 }
339
340
SvgTextC(struct ADrawTag * ctx,unsigned int x,unsigned int y,const char * string)341 void SvgTextC(struct ADrawTag *ctx,
342 unsigned int x,
343 unsigned int y,
344 const char *string)
345 {
346 SvgContext *context = getSvgCtx(ctx);
347 unsigned int hw = SvgTextWidth(ctx, string) / 2;
348
349 svgRect(ctx, getSvgBgPen(ctx), x - (hw + 2), y - SvgTextHeight(ctx) + 1, x + hw, y - 1);
350
351 y += getSpace(ctx, SvgHelvetica.descender);
352
353 fprintf(getSvgFile(ctx),
354 "<text x=\"%u\" y=\"%u\" textLength=\"%u\" font-family=\"Helvetica\" font-size=\"%u\" fill=\"%s\" text-anchor=\"middle\">\n\n",
355 x, y, SvgTextWidth(ctx, string), context->fontPoints, context->penColName);
356 writeEscaped(ctx, string);
357 fprintf(getSvgFile(ctx), "\n</text>\n");
358 }
359
360
SvgFilledTriangle(struct ADrawTag * ctx,unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2,unsigned int x3,unsigned int y3)361 void SvgFilledTriangle(struct ADrawTag *ctx,
362 unsigned int x1,
363 unsigned int y1,
364 unsigned int x2,
365 unsigned int y2,
366 unsigned int x3,
367 unsigned int y3)
368 {
369
370 fprintf(getSvgFile(ctx),
371 "<polygon fill=\"%s\" points=\"%u,%u %u,%u %u,%u\"/>\n",
372 getSvgPen(ctx),
373 x1, y1,
374 x2, y2,
375 x3, y3);
376 }
377
378
SvgFilledCircle(struct ADrawTag * ctx,unsigned int x,unsigned int y,unsigned int r)379 void SvgFilledCircle(struct ADrawTag *ctx,
380 unsigned int x,
381 unsigned int y,
382 unsigned int r)
383 {
384 fprintf(getSvgFile(ctx),
385 "<circle fill=\"%s\" cx=\"%u\" cy=\"%u\" r=\"%u\"/>\n",
386 getSvgPen(ctx),
387 x, y, r);
388 }
389
390
SvgFilledRectangle(struct ADrawTag * ctx,unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2)391 void SvgFilledRectangle(struct ADrawTag *ctx,
392 unsigned int x1,
393 unsigned int y1,
394 unsigned int x2,
395 unsigned int y2)
396 {
397 svgRect(ctx, getSvgPen(ctx), x1, y1, x2, y2);
398 }
399
400
SvgArc(struct ADrawTag * ctx,unsigned int cx,unsigned int cy,unsigned int w,unsigned int h,unsigned int s,unsigned int e)401 void SvgArc(struct ADrawTag *ctx,
402 unsigned int cx,
403 unsigned int cy,
404 unsigned int w,
405 unsigned int h,
406 unsigned int s,
407 unsigned int e)
408 {
409 unsigned int sx, sy, ex, ey;
410
411 /* Get start and end x,y */
412 arcPoint(cx, cy, w, h, s, &sx, &sy);
413 arcPoint(cx, cy, w, h, e, &ex, &ey);
414
415 fprintf(getSvgFile(ctx),
416 "<path d=\"M %u %u A%u,%u 0 0,1 %u,%u\" stroke=\"%s\" fill=\"none\"/>",
417 sx, sy, w / 2, h / 2, ex, ey, getSvgPen(ctx));
418 }
419
420
SvgDottedArc(struct ADrawTag * ctx,unsigned int cx,unsigned int cy,unsigned int w,unsigned int h,unsigned int s,unsigned int e)421 void SvgDottedArc(struct ADrawTag *ctx,
422 unsigned int cx,
423 unsigned int cy,
424 unsigned int w,
425 unsigned int h,
426 unsigned int s,
427 unsigned int e)
428 {
429 unsigned int sx, sy, ex, ey;
430
431 /* Get start and end x,y */
432 arcPoint(cx, cy, w, h, s, &sx, &sy);
433 arcPoint(cx, cy, w, h, e, &ex, &ey);
434
435 fprintf(getSvgFile(ctx),
436 "<path d=\"M %u %u A%u,%u 0 0,1 %u,%u\" stroke=\"%s\" fill=\"none\" stroke-dasharray=\"2,2\"/>",
437 sx, sy, w / 2, h / 2, ex, ey, getSvgPen(ctx));
438 }
439
440
SvgSetPen(struct ADrawTag * ctx,ADrawColour col)441 void SvgSetPen(struct ADrawTag *ctx,
442 ADrawColour col)
443 {
444 static char colCmd[10];
445
446 getSvgCtx(ctx)->penColName = svgColour(col);
447 if(getSvgCtx(ctx)->penColName == NULL)
448 {
449 /* Print the RGB value into the local storage */
450 sprintf(colCmd, "#%06X", col);
451
452 /* Now set the colour name to the local store */
453 getSvgCtx(ctx)->penColName = colCmd;
454 }
455 }
456
457
SvgSetBgPen(struct ADrawTag * ctx,ADrawColour col)458 void SvgSetBgPen(struct ADrawTag *ctx,
459 ADrawColour col)
460 {
461 static char colCmd[10];
462
463 getSvgCtx(ctx)->penBgColName = svgColour(col);
464 if(getSvgCtx(ctx)->penBgColName == NULL)
465 {
466 /* Print the RGB value into the local storage */
467 sprintf(colCmd, "#%06X", col);
468
469 /* Now set the colour name to the local store */
470 getSvgCtx(ctx)->penBgColName = colCmd;
471 }
472 }
473
474
SvgSetFontSize(struct ADrawTag * ctx,ADrawFontSize size)475 void SvgSetFontSize(struct ADrawTag *ctx,
476 ADrawFontSize size)
477 {
478 SvgContext *context = getSvgCtx(ctx);
479
480 switch(size)
481 {
482 case ADRAW_FONT_TINY:
483 context->fontPoints = 8;
484 break;
485
486 case ADRAW_FONT_SMALL:
487 context->fontPoints = 12;
488 break;
489
490 default:
491 assert(0);
492 }
493
494 }
495
496
SvgClose(struct ADrawTag * ctx)497 Boolean SvgClose(struct ADrawTag *ctx)
498 {
499 SvgContext *context = getSvgCtx(ctx);
500
501 /* Close the SVG */
502 fprintf(context->of, "</svg>\n");
503
504 /* Close the output file */
505 if(context->of != stdout)
506 {
507 fclose(context->of);
508 }
509
510 /* Free and destroy context */
511 free(context);
512 ctx->internal = NULL;
513
514 return TRUE;
515 }
516
517
518
SvgInit(unsigned int w,unsigned int h,const char * file,struct ADrawTag * outContext)519 Boolean SvgInit(unsigned int w,
520 unsigned int h,
521 const char *file,
522 struct ADrawTag *outContext)
523 {
524 SvgContext *context;
525
526 /* Create context */
527 context = outContext->internal = malloc_s(sizeof(SvgContext));
528 if(context == NULL)
529 {
530 return FALSE;
531 }
532
533 /* Open the output file */
534 if(strcmp(file, "-") == 0)
535 {
536 context->of = stdout;
537 }
538 else
539 {
540 context->of = fopen(file, "wb");
541 if(!context->of)
542 {
543 fprintf(stderr, "SvgInit: Failed to open output file '%s': %s\n", file, strerror(errno));
544 return FALSE;
545 }
546 }
547
548 /* Set the initial pen state */
549 SvgSetPen(outContext, ADRAW_COL_BLACK);
550 SvgSetBgPen(outContext, ADRAW_COL_WHITE);
551
552 /* Default to small font */
553 SvgSetFontSize(outContext, ADRAW_FONT_SMALL);
554
555 fprintf(context->of, "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n"
556 " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
557
558 fprintf(context->of, "<svg version=\"1.1\"\n"
559 " width=\"%upx\" height=\"%upx\"\n"
560 " viewBox=\"0 0 %u %u\"\n"
561 " xmlns=\"http://www.w3.org/2000/svg\" shape-rendering=\"crispEdges\"\n"
562 " stroke-width=\"1\" text-rendering=\"geometricPrecision\">\n",
563 w, h, w, h);
564
565 /* Now fill in the function pointers */
566 outContext->line = SvgLine;
567 outContext->dottedLine = SvgDottedLine;
568 outContext->textL = SvgTextL;
569 outContext->textC = SvgTextC;
570 outContext->textR = SvgTextR;
571 outContext->textWidth = SvgTextWidth;
572 outContext->textHeight = SvgTextHeight;
573 outContext->filledRectangle = SvgFilledRectangle;
574 outContext->filledTriangle = SvgFilledTriangle;
575 outContext->filledCircle = SvgFilledCircle;
576 outContext->arc = SvgArc;
577 outContext->dottedArc = SvgDottedArc;
578 outContext->setPen = SvgSetPen;
579 outContext->setBgPen = SvgSetBgPen;
580 outContext->setFontSize = SvgSetFontSize;
581 outContext->close = SvgClose;
582
583 return TRUE;
584 }
585
586 /* END OF FILE */
587