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, "&lt;"); break;
182             case '>': fprintf(f, "&gt;"); break;
183             case '"': fprintf(f, "&quot;"); break;
184             case '&': fprintf(f, "&amp;"); 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