1 /***************************************************************************
2  *
3  * $Id: main.c 187 2011-03-03 21:48:02Z Michael.McTernan@gmail.com $
4  *
5  * This file is part of mscgen, a message sequence chart renderer.
6  * Copyright (C) 2010 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 /***************************************************************************
24  * Include Files
25  ***************************************************************************/
26 
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30 #include <stdio.h>
31 #include <string.h>
32 #include <stdlib.h>
33 #ifdef  HAVE_LIMITS_H
34 #include <limits.h>
35 #endif
36 #ifdef HAVE_UNISTD_H
37 #include <unistd.h>
38 #endif
39 #include <errno.h>
40 #include <ctype.h>
41 #include <assert.h>
42 #include "cmdparse.h"
43 #include "lexer.h"
44 #include "usage.h"
45 #include "adraw.h"
46 #include "safe.h"
47 #include "msc.h"
48 
49 /***************************************************************************
50  * Macro definitions
51  ***************************************************************************/
52 
53 #define M_Max(a, b) (((a) > (b)) ? (a) : (b))
54 #define M_Min(a, b) (((a) < (b)) ? (a) : (b))
55 
56 /***************************************************************************
57  * Types
58  ***************************************************************************/
59 
60 /** Structure for holding global options.
61  * This structure groups all the options that affect the text output into
62  * one structure.
63  */
64 typedef struct GlobalOptionsTag
65 {
66     /** Ideal width of output canvas.
67      * If this value allows the entitySpacing to be increased, then
68      * entitySpacing will be set to the larger value of it's original
69      * value and idealCanvasWidth / number of entities.
70      */
71     unsigned int idealCanvasWidth;
72 
73     /** Horizontal spacing between entities. */
74     unsigned int entitySpacing;
75 
76     /** Gap at the top of the page. */
77     unsigned int entityHeadGap;
78 
79     /** Vertical spacing between arcs. */
80     unsigned int arcSpacing;
81 
82     /** Arc gradient.
83      * Y offset of arc head, relative to tail, in pixels.
84      */
85     int          arcGradient;
86 
87     /** Gap between adjacent boxes. */
88     unsigned int boxSpacing;
89 
90     /** Minimum distance between box edges and text. */
91     unsigned int boxInternalBorder;
92 
93     /** Radius of rounded box corner arcs. */
94     unsigned int rboxArc;
95 
96     /** Size of 'corner' added to note boxes. */
97     unsigned int noteCorner;
98 
99     /** Anguluar box slope in pixels. */
100     unsigned int aboxSlope;
101 
102     /** If TRUE, wrap arc text as well as box contents. */
103     Boolean      wordWrapArcLabels;
104 
105     /** Horizontal width of the arrow heads. */
106     unsigned int arrowWidth;
107 
108     /** Vertical depth of the arrow heads. */
109     unsigned int arrowHeight;
110 
111     /** Height of an arc which loops back to itself. */
112     unsigned int loopArcHeight;
113 
114     /** Horizontal gap between text and horizontal lines. */
115     unsigned int textHGapPre;
116 
117     /** Horizontal gap between text and horizontal lines. */
118     unsigned int textHGapPost;
119 }
120 GlobalOptions;
121 
122 
123 /** Information about each out row.
124  */
125 typedef struct
126 {
127     /** Minimum Y value. */
128     unsigned int ymin;
129 
130     /** Y position of the arc on the row. */
131     unsigned int arcliney;
132 
133     /** Maximum Y value. */
134     unsigned int ymax;
135 
136     /** Maximum lines of text on the row. */
137     unsigned int maxTextLines;
138 }
139 RowInfo;
140 
141 /***************************************************************************
142  * Local Variables.
143  ***************************************************************************/
144 
145 static Boolean gInputFilePresent = FALSE;
146 static char    gInputFile[4096];
147 
148 static Boolean gOutputFilePresent = FALSE;
149 static char    gOutputFile[4096];
150 
151 static Boolean gOutTypePresent = FALSE;
152 static char    gOutType[10];
153 
154 static Boolean gDumpLicencePresent = FALSE;
155 
156 static Boolean gPrintParsePresent = FALSE;
157 
158 static Boolean gOutputFontPresent = FALSE;
159 static char    gOutputFont[256];
160 
161 /** Command line switches.
162  * This gives the command line switches that can be interpreted by mscgen.
163  */
164 static CmdSwitch gClSwitches[] =
165 {
166     {"-i",     &gInputFilePresent,  "%4096[^?]", gInputFile },
167     {"-o",     &gOutputFilePresent, "%4096[^?]", gOutputFile },
168     {"-T",     &gOutTypePresent,    "%10[^?]",   gOutType },
169     {"-l",     &gDumpLicencePresent,NULL,        NULL },
170     {"-p",     &gPrintParsePresent, NULL,        NULL },
171     {"-F",     &gOutputFontPresent, "%256[^?]",  gOutputFont }
172 };
173 
174 
175 static GlobalOptions gOpts =
176 {
177     600,    /* idealCanvasWidth */
178 
179     80,     /* entitySpacing */
180     20,     /* entityHeadGap */
181     6,      /* arcSpacing */
182     0,      /* arcGradient */
183     8,      /* boxSpacing */
184     4,      /* boxInternalBorder */
185     6,      /* rboxArc */
186     12,     /* noteCorner */
187     6,      /* aboxSlope */
188     FALSE,  /* wordWrapArcLabels */
189 
190     /* Arrow options */
191     10, 6,
192 
193     /* loopArcHeight */
194     12,
195 
196     /* textHGapPre, textHGapPost */
197     2, 2
198 };
199 
200 /** The drawing. */
201 static ADraw drw;
202 
203 /** Name of a file to be removed by deleteTmp(). */
204 static char *deleteTmpFilename = NULL;
205 
206 /***************************************************************************
207  * Functions
208  ***************************************************************************/
209 
210 /** Delete the file named in deleteTmpFilename.
211  * This function is registered with atexit() to delete a possible temporary
212  * file used when generating image map files.
213  */
deleteTmp()214 static void deleteTmp()
215 {
216     if(deleteTmpFilename)
217     {
218         unlink(deleteTmpFilename);
219     }
220 }
221 
222 /** Remove any file extension from the passed filename.
223  */
trimExtension(char * s)224 static void trimExtension(char *s)
225 {
226     int l = strlen(s);
227 
228     while(l > 0)
229     {
230         l--;
231         switch(s[l])
232         {
233             case '.':
234                 /* Don't truncate hidden files */
235                 if(l > 0 && s[l - 1] != '\\' && s[l -1] != '/')
236                 {
237                     s[l] = '\0';
238                 }
239                 return;
240             case '/':
241             case '\\':
242                 return;
243         }
244     }
245 }
246 
247 
248 /** Check if some arc type indicates a box.
249  */
isBoxArc(const MscArcType a)250 static Boolean isBoxArc(const MscArcType a)
251 {
252     return a == MSC_ARC_BOX || a == MSC_ARC_RBOX  ||
253            a == MSC_ARC_ABOX || a== MSC_ARC_NOTE;
254 }
255 
256 
257 /** Count the number of lines in some string.
258  * This counts line breaks that are written as a literal '\n' in the line to
259  * determine how many lines of output are needed.
260  *
261  * \param[in] l  Pointer to the input string to inspect.
262  * \returns      The count of '\n' characters appearing in the input string + 1.
263  */
countLines(const char * l)264 static unsigned int countLines(const char *l)
265 {
266     unsigned int c = 1;
267 
268     do
269     {
270         c++;
271 
272         l = strstr(l, "\\n");
273         if(l) l += 2;
274     }
275     while(l != NULL);
276 
277     return c;
278 }
279 
280 
281 /** Word wrap a line of text until the first line is less than \a width wide.
282  * This removes words from the input line and builds them into a 2nd new
283  * string until the input line is shorter than the supplied width.  The
284  * input string is directly truncated, while the remaining characters are
285  * returned in a new memory allocation.  On return, the input line of text
286  * will be shorter than \a width, while the newly returned string will contain
287  * all the remaining characters.
288  *
289  * If the input line is already shorter than \a width, the function returns
290  * NULL and does not modify the input line of text.
291  *
292  * \param[in,out] l     Input line of text which maybe modified if needed.
293  * \param[in]     width Maximum allowable text line width.
294  * \returns       NULL if \a l was already less then \a width long,
295  *                 otherwise a new string giving the remained of the string.
296  */
splitStringToWidth(char * l,unsigned int width)297 static char *splitStringToWidth(char *l, unsigned int width)
298 {
299     char *p = l + strlen(l);
300     char *orig = NULL;
301     int   m, n;
302 
303     if(drw.textWidth(&drw, l) > width)
304     {
305         /* Duplicate the original string */
306         orig = strdup_s(l);
307 
308         /* Now remove words from the line until it fits the available width */
309         do
310         {
311             /* Skip back 1 word */
312             while(!isspace(*p) && p > l)
313             {
314                 p--;
315             }
316 
317             if(p > l)
318             {
319                 *p = '\0';
320             }
321         }
322         while(drw.textWidth(&drw, l) > width && p > l);
323 
324         /* Check if the first word is bigger than the available space;
325          *  we need to hyphenate in this case.
326          */
327         if(p == l)
328         {
329             const unsigned int hyphenWidth = drw.textWidth(&drw, "-");
330 
331             /* Find the end of the first word */
332             while(!isspace(*p) && *p != '\0')
333             {
334                 p++;
335             }
336 
337             /* Start removing characters from the word */
338             do
339             {
340                 *p = '\0';
341                 p--;
342             }
343             while(drw.textWidth(&drw, l) + hyphenWidth > width && p > l);
344 
345             /* Add a hyphen */
346             *p = '-';
347         }
348 
349         /* Copy the remaining line to the start of the string */
350         m = 0;
351         n = (p - l);
352 
353         while(isspace(orig[n]) && orig[n] != '\0')
354         {
355             n++;
356         }
357 
358         do
359         {
360             orig[m++] = orig[n++];
361         }
362         while(orig[m - 1] != '\0');
363     }
364 
365     return orig;
366 }
367 
368 
369 /** Split an input arc label into lines, word-wrapping if needed.
370  * This takes the literal label supplied from the input and splits it into an
371  * array of char * text lines.  Splitting is first done according to literal
372  * '\n' character sequences added by the user, then according to word wrapping
373  * to fit available space, if appropriate.
374  *
375  * \param[in]     m         The MSC for which the lines are to be split.
376  * \param[in]     arcType   The type of the arc being labelled.
377  * \param[in,out] lines     Pointer to be filled with output line array.
378  * \param[in]     label     Original arc label from input file.
379  * \param[in]     startCol  Column in which the arc starts.
380  * \param[in]     endCol    Column in which the arc ends, or -1 for broadcast arcs.
381  *
382  * \note The returned strings and array must be free()'d.  freeLabelLines() can
383  *        be used for this purpose.
384  */
computeLabelLines(Msc m,const MscArcType arcType,char *** lines,const char * label,int startCol,int endCol)385 static unsigned int computeLabelLines(Msc               m,
386                                       const MscArcType  arcType,
387                                       char           ***lines,
388                                       const char       *label,
389                                       int               startCol,
390                                       int               endCol)
391 {
392     unsigned int  width;
393     unsigned int  nAllocLines = 8;
394     char        **retLines = malloc_s(sizeof(char *) * nAllocLines);
395     unsigned int  c = 0;
396 
397     assert(startCol >= 0 && startCol < (signed)MscGetNumEntities(m));
398     assert(startCol >= -1 && startCol < (signed)MscGetNumEntities(m));
399 
400     /* Compute available width for text */
401     if(isBoxArc(arcType) || gOpts.wordWrapArcLabels)
402     {
403         if(endCol == -1)
404         {
405             /* This is a special case for a broadcast arc */
406             width = gOpts.entitySpacing * MscGetNumEntities(m);
407         }
408         else if(startCol < endCol)
409         {
410             width = gOpts.entitySpacing * (1 + (endCol - startCol));
411         }
412         else
413         {
414             width = gOpts.entitySpacing * (1 + (startCol - endCol));
415         }
416 
417         /* Reduce the width due to the box borders */
418         if(isBoxArc(arcType))
419         {
420             width -= (gOpts.boxSpacing + gOpts.boxInternalBorder) * 2;
421         }
422 
423         if(arcType == MSC_ARC_NOTE)
424         {
425             width -= gOpts.noteCorner;
426         }
427     }
428     else
429     {
430         width = UINT_MAX;
431     }
432 
433     /* Split the input label into lines */
434     while(label != NULL)
435     {
436         /* First split around user specified lines with literal '\n' */
437         char *nextLine = strstr(label, "\\n");
438         if(nextLine)
439         {
440             const int lineLen = nextLine - label;
441 
442             /* Allocate storage and duplicate the line */
443             retLines[c] = malloc_s(lineLen + 1);
444             memcpy(retLines[c], label, lineLen);
445             retLines[c][lineLen] = '\0';
446 
447             /* Advance the label */
448             label = nextLine + 2;
449         }
450         else
451         {
452             /* Duplicate the final line */
453             retLines[c] = strdup_s(label);
454             label = NULL;
455         }
456 
457         /* Now split the line as required to wrap into the space available */
458         do
459         {
460             /* Check if more storage maybe needed */
461             if(c + 2 >= nAllocLines)
462             {
463                 nAllocLines += 8;
464                 retLines = realloc_s(retLines, sizeof(char *) * nAllocLines);
465             }
466 
467             retLines[c + 1] = splitStringToWidth(retLines[c], width);
468             c++;
469         }
470         while(retLines[c] != NULL);
471     }
472 
473     /* Return the array of lines and the count */
474     *lines = retLines;
475 
476     return c;
477 }
478 
479 
480 /** Free memory allocated for the label lines.
481  */
freeLabelLines(unsigned int n,char ** lines)482 static void freeLabelLines(unsigned int n, char **lines)
483 {
484     while(n > 0)
485     {
486         n--;
487         free(lines[n]);
488     }
489 
490     free(lines);
491 }
492 
493 
494 /** Get some line from a string containing '\n' delimiters.
495  * Given a string that contains literal '\n' delimiters, return a subset in
496  * a passed buffer that gives the nth line.
497  *
498  * \param[in] string  The string to parse.
499  * \param[in] line    The line number to return from the string, which should
500  *                     count from 0.
501  * \param[in] out     Pointer to a buffer to fill with line data.
502  * \param[in] outLen  The length of the buffer pointed to by \a out, in bytes.
503  * \returns  A pointer to \a out.
504  */
getLine(const char * string,unsigned int line,char * const out,const unsigned int outLen)505 static char *getLine(const char        *string,
506                      unsigned int       line,
507                      char *const        out,
508                      const unsigned int outLen)
509 {
510     const char  *lineStart, *lineEnd;
511     unsigned int lineLen;
512 
513     /* Setup for the loop */
514     lineEnd = NULL;
515     line++;
516 
517     do
518     {
519         /* Check if this is the first or a repeat iteration */
520         if(lineEnd)
521         {
522             lineStart = lineEnd + 2;
523         }
524         else
525         {
526             lineStart = string;
527         }
528 
529         /* Search for next delimited */
530         lineEnd = strstr(lineStart, "\\n");
531 
532         line--;
533     }
534     while(line > 0 && lineEnd != NULL);
535 
536     /* Determine the length of the line */
537     if(lineEnd != NULL)
538     {
539         lineLen = lineEnd - lineStart;
540     }
541     else
542     {
543         lineLen = strlen(string) - (lineStart - string);
544     }
545 
546     /* Clamp the length to the buffer */
547     if(lineLen > outLen - 1)
548     {
549         lineLen = outLen - 1;
550     }
551 
552     /* Copy desired characters */
553     memcpy(out, lineStart, lineLen);
554 
555     /* NULL terminate */
556     out[lineLen] = '\0';
557 
558     return out;
559 }
560 
561 
562 /** Check if some arc name indicates a broadcast entity.
563  */
isBroadcastArc(const char * entity)564 static Boolean isBroadcastArc(const char *entity)
565 {
566     return entity != NULL && (strcmp(entity, "*") == 0);
567 }
568 
569 
570 /** Get the skip value in pixels for some the current arc in the Msc.
571  */
getArcGradient(Msc m,const RowInfo * rowInfo,unsigned int row)572 static int getArcGradient(Msc m, const RowInfo *rowInfo, unsigned int row)
573 {
574     const char   *s = MscGetCurrentArcAttrib(m, MSC_ATTR_ARC_SKIP);
575     unsigned int  v = gOpts.arcGradient;
576 
577     if(s != NULL && rowInfo != NULL)
578     {
579         const unsigned int rowCount = MscGetNumArcs(m) - MscGetNumParallelArcs(m);
580         unsigned int       skip;
581 
582         if(sscanf(s, "%u", &skip) == 1)
583         {
584             unsigned int ystart = rowInfo[row].arcliney;
585             unsigned int yend   = rowInfo[M_Min(rowCount - 1, row + skip)].arcliney;
586 
587             v += yend - ystart;
588         }
589         else
590         {
591             fprintf(stderr, "Warning: Non-integer arcskip value: %s\n", s);
592         }
593     }
594 
595     return v;
596 }
597 
598 
599 /** Add a point to the output imagemap.
600  * If \a ismap and \a url are non-NULL, this function will add a rectangle
601  * to the imagemap according to the parameters passed.
602  *
603  * \param ismap  The file to which the imagemap should be rendered.
604  * \param url    The URL to which the imagemap area should link.
605  * \param x1     The x coordinate for the upper left point.
606  * \param y2     The y coordinate for the upper left point.
607  * \param x2     The x coordinate for the lower right point.
608  * \param y2     The y coordinate for the lower right point.
609  */
ismapRect(FILE * ismap,const char * url,unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2)610 static void ismapRect(FILE        *ismap,
611                       const char  *url,
612                       unsigned int x1,
613                       unsigned int y1,
614                       unsigned int x2,
615                       unsigned int y2)
616 {
617     if(ismap && url)
618     {
619         assert(x1 <= x2); assert(y1 <= y2);
620 
621         fprintf(ismap,
622                 "rect %s %d,%d %d,%d\n",
623                 url,
624                 x1, y1,
625                 x2, y2);
626     }
627 #if 0
628     /* For debug render a cross onto the output */
629     drw.line(&drw, x1, y1, x2, y2);
630     drw.line(&drw, x2, y1, x1, y2);
631 #endif
632 }
633 
634 
635 /** Draw an arrow pointing to the right.
636  * \param x     The x co-ordinate for the end point for the arrow head.
637  * \param y     The y co-ordinate for the end point for the arrow head.
638  * \param type  The arc type, which controls the format of the arrow head.
639  */
arrowR(unsigned int x,unsigned int y,MscArcType type)640 static void arrowR(unsigned int x,
641                    unsigned int y,
642                    MscArcType   type)
643 {
644     switch(type)
645     {
646         case MSC_ARC_SIGNAL: /* Unfilled half */
647             drw.line(&drw,
648                      x, y,
649                      x - gOpts.arrowWidth, y + gOpts.arrowHeight);
650             break;
651 
652         case MSC_ARC_DOUBLE:
653         case MSC_ARC_METHOD: /* Filled */
654         case MSC_ARC_RETVAL: /* Filled, dotted arc (not rendered here) */
655             drw.filledTriangle(&drw,
656                                x, y,
657                                x - gOpts.arrowWidth, y + gOpts.arrowHeight,
658                                x - gOpts.arrowWidth, y - gOpts.arrowHeight);
659             break;
660 
661         case MSC_ARC_CALLBACK: /* Non-filled */
662             drw.line(&drw,
663                      x, y,
664                      x - gOpts.arrowWidth, y + gOpts.arrowHeight);
665             drw.line(&drw,
666                      x - gOpts.arrowWidth, y - gOpts.arrowHeight,
667                      x, y);
668             break;
669 
670         default:
671             assert(0);
672             break;
673     }
674 }
675 
676 
677 /** Draw an arrow pointing to the left.
678  * \param x     The x co-ordinate for the end point for the arrow head.
679  * \param y     The y co-ordinate for the end point for the arrow head.
680  * \param type  The arc type, which controls the format of the arrow head.
681  */
arrowL(unsigned int x,unsigned int y,MscArcType type)682 static void arrowL(unsigned int x,
683                    unsigned int y,
684                    MscArcType   type)
685 {
686     switch(type)
687     {
688         case MSC_ARC_SIGNAL: /* Unfilled half */
689             drw.line(&drw,
690                      x, y,
691                      x + gOpts.arrowWidth, y + gOpts.arrowHeight);
692             break;
693 
694         case MSC_ARC_DOUBLE:
695         case MSC_ARC_METHOD: /* Filled */
696         case MSC_ARC_RETVAL: /* Filled, dotted arc (not rendered here) */
697             drw.filledTriangle(&drw,
698                                x, y,
699                                x + gOpts.arrowWidth, y + gOpts.arrowHeight,
700                                x + gOpts.arrowWidth, y - gOpts.arrowHeight);
701             break;
702 
703         case MSC_ARC_CALLBACK: /* Non-filled */
704             drw.line(&drw,
705                      x, y,
706                      x + gOpts.arrowWidth, y + gOpts.arrowHeight);
707             drw.line(&drw,
708                      x, y,
709                      x + gOpts.arrowWidth, y - gOpts.arrowHeight);
710             break;
711 
712         default:
713             assert(0);
714             break;
715     }
716 }
717 
718 
719 /** Render some entity text.
720  * Draw the text for some entity.
721  * \param  ismap       If not \a NULL, write an ismap description here.
722  * \param  x           The x position at which the entity text should be centered.
723  * \param  y           The y position where the text should be placed.
724  * \param  entLabel    The label to render, which maybe \a NULL in which case
725  *                       no ouput is produced.
726  * \param  entUrl      The URL for rendering the label as a hyperlink.  This
727  *                       maybe \a NULL if not required.
728  * \param  entId       The text identifier for the arc.
729  * \param  entIdUrl    The URL for rendering the test identifier as a hyperlink.
730  *                       This maybe \a NULL if not required.
731  * \param  entColour   The text colour name or specification for the entity text.
732  *                      If NULL, use default colouring scheme.
733  * \param  entBgColour The text background colour name or specification for the
734  *                      entity text. If NULL, use default colouring scheme.
735  */
entityText(FILE * ismap,unsigned int x,unsigned int y,const char * entLabel,const char * entUrl,const char * entId,const char * entIdUrl,const char * entColour,const char * entBgColour)736 static void entityText(FILE             *ismap,
737                        unsigned int      x,
738                        unsigned int      y,
739                        const char       *entLabel,
740                        const char       *entUrl,
741                        const char       *entId,
742                        const char       *entIdUrl,
743                        const char       *entColour,
744                        const char       *entBgColour)
745 {
746     if(entLabel)
747     {
748         const unsigned int lines = countLines(entLabel);
749         unsigned int       l;
750         char               lineBuffer[1024];
751 
752         /* Adjust y to be above the writing line */
753         y -= drw.textHeight(&drw) * (lines - 1);
754 
755         for(l = 0; l < lines - 1; l++)
756         {
757             char         *lineLabel = getLine(entLabel, l, lineBuffer, sizeof(lineBuffer));
758             unsigned int  width     = drw.textWidth(&drw, lineLabel);
759 
760             /* Push text down one line */
761             y += drw.textHeight(&drw);
762 
763             /* Check if a URL is associated */
764             if(entUrl)
765             {
766                 /* If no explict colour has been set, make URLS blue */
767                 drw.setPen(&drw, ADRAW_COL_BLUE);
768 
769                 /* Image map output */
770                 ismapRect(ismap,
771                           entUrl,
772                           x - (width / 2), y - drw.textHeight(&drw),
773                           x + (width / 2), y);
774             }
775 
776             /* Set to the explicit colours if directed */
777             if(entColour != NULL)
778             {
779                 drw.setPen(&drw, ADrawGetColour(entColour));
780             }
781 
782             if(entBgColour != NULL)
783             {
784                 drw.setBgPen(&drw, ADrawGetColour(entBgColour));
785             }
786 
787             /* Render text and restore pen */
788             drw.textC (&drw, x, y, lineLabel);
789             drw.setPen(&drw, ADRAW_COL_BLACK);
790             drw.setBgPen(&drw, ADRAW_COL_WHITE);
791 
792             /* Render the Id of the title, if specified and for first line only */
793             if(entId && l == 0)
794             {
795                 unsigned int idwidth;
796                 int          idx, idy;
797 
798                 idy = y - drw.textHeight(&drw);
799                 idx = x + (width / 2);
800 
801                 drw.setFontSize(&drw, ADRAW_FONT_TINY);
802 
803                 idwidth = drw.textWidth(&drw, entId);
804                 idy    += (drw.textHeight(&drw) + 1) / 2;
805 
806                 if(entIdUrl)
807                 {
808                     drw.setPen(&drw, ADRAW_COL_BLUE);
809                     drw.textR (&drw, idx, idy, entId);
810                     drw.setPen(&drw, ADRAW_COL_BLACK);
811 
812                     /* Image map output */
813                     ismapRect(ismap,
814                               entIdUrl,
815                               idx, idy - drw.textHeight(&drw),
816                               idx + idwidth, idy);
817                 }
818                 else
819                 {
820                     drw.textR(&drw, idx, idy, entId);
821                 }
822 
823                 drw.setFontSize(&drw, ADRAW_FONT_SMALL);
824             }
825         }
826     }
827 }
828 
829 
830 /** Compute the output canvas size required for some MSC.
831  * This computes the dimensions for the canvas as well as the height for each
832  * row.
833  *
834  * \param[in]     m    The MSC to analyse.
835  * \param[in,out] w    Pointer to be filled with the output width.
836  * \param[in,out] h    Pointer to be filled with the output height.
837  * \returns  An array giving the height of each row.
838  */
computeCanvasSize(Msc m,unsigned int * w,unsigned int * h)839 static RowInfo *computeCanvasSize(Msc           m,
840                                   unsigned int *w,
841                                   unsigned int *h)
842 {
843     const unsigned int rowCount = MscGetNumArcs(m) - MscGetNumParallelArcs(m);
844     const unsigned int textHeight = drw.textHeight(&drw);
845     RowInfo      *rowHeight;
846     unsigned int  nextYmin, ymin, ymax, yskipmax, row;
847 
848     /* Allocate storage for the height of each row */
849     rowHeight = zalloc_s(sizeof(RowInfo) * rowCount);
850     row = 0;
851 
852     nextYmin = ymin = gOpts.entityHeadGap;
853     yskipmax = 0;
854 
855     MscResetArcIterator(m);
856     do
857     {
858         const MscArcType   arcType           = MscGetCurrentArcType(m);
859         const int          arcGradient       = isBoxArc(arcType) ? 0 : getArcGradient(m, NULL, 0);
860         char             **arcLabelLines     = NULL;
861         unsigned int       arcLabelLineCount = 0;
862         int                startCol = -1, endCol = -1;
863 
864         if(arcType == MSC_ARC_PARALLEL)
865         {
866             assert(row > 0);
867 
868             row--;
869 
870             ymin = rowHeight[row].ymin;
871             nextYmin = rowHeight[row].ymax;
872         }
873         else
874         {
875             /* Get the entity indices */
876             if(arcType != MSC_ARC_DISCO && arcType != MSC_ARC_DIVIDER && arcType != MSC_ARC_SPACE)
877             {
878                 startCol = MscGetEntityIndex(m, MscGetCurrentArcSource(m));
879                 endCol   = MscGetEntityIndex(m, MscGetCurrentArcDest(m));
880             }
881             else
882             {
883                 /* Discontinuity or parallel arc spans whole chart */
884                 startCol = 0;
885                 endCol   = MscGetNumEntities(m) - 1;
886             }
887 
888             /* Work out how the label fits the gap between entities */
889             arcLabelLineCount = computeLabelLines(m, arcType, &arcLabelLines,
890                                                   MscGetCurrentArcAttrib(m, MSC_ATTR_LABEL),
891                                                   startCol, endCol);
892 
893             assert(row < rowCount);
894 
895             /* Update the max line count for the row */
896             if(arcLabelLineCount > rowHeight[row].maxTextLines)
897             {
898                 rowHeight[row].maxTextLines = arcLabelLineCount;
899             }
900 
901             freeLabelLines(arcLabelLineCount, arcLabelLines);
902 
903             /* Compute the height of this arc */
904             if(arcType != MSC_ARC_DISCO && arcType != MSC_ARC_DIVIDER && arcType != MSC_ARC_SPACE)
905             {
906                 ymax = ymin + gOpts.arcSpacing;
907                 ymax += (M_Max(rowHeight[row].maxTextLines, 2) * textHeight);
908             }
909             else
910             {
911                 ymax = ymin + gOpts.arcSpacing;
912                 ymax += (M_Max(rowHeight[row].maxTextLines, 1) * textHeight);
913             }
914 
915             /* Update next potential row start */
916             if(ymax > nextYmin)
917             {
918                 nextYmin = ymax;
919             }
920 
921             /* Compute the dimensions for the completed row */
922             rowHeight[row].ymin     = ymin;
923             rowHeight[row].ymax     = nextYmin - gOpts.arcSpacing;
924             rowHeight[row].arcliney = rowHeight[row].ymin + (rowHeight[row].ymax - rowHeight[row].ymin) / 2;
925             row++;
926 
927             /* Start new row */
928             ymin = nextYmin;
929         }
930 
931         /* Keep a track of where the gradient may cause the graph to end */
932         if(ymax + arcGradient > ymax)
933         {
934             yskipmax = ymax + arcGradient;
935         }
936 
937     }
938     while(MscNextArc(m));
939 
940     if(ymax < yskipmax)
941         ymax = yskipmax;
942 
943     /* Set the return values */
944     *w = MscGetNumEntities(m) * gOpts.entitySpacing;
945     *h = ymax;
946 
947     return rowHeight;
948 }
949 
950 
951 /** Draw vertical lines stemming from entities.
952  * This function will draw a single segment of the vertical line that
953  * drops from an entity.
954  *
955  * \param m          The \a Msc for which the lines are drawn
956  * \param ymin       Top of the row.
957  * \param ymax       Bottom of the row.
958  * \param dotted     If #TRUE, produce a dotted line, otherwise solid.
959  * \param colourRefs Colour references for each entity.
960  */
entityLines(Msc m,const unsigned int ymin,const unsigned int ymax,Boolean dotted,const ADrawColour * colourRefs)961 static void entityLines(Msc                m,
962                         const unsigned int ymin,
963                         const unsigned int ymax,
964                         Boolean            dotted,
965                         const ADrawColour *colourRefs)
966 {
967     unsigned int t;
968 
969     for(t = 0; t < MscGetNumEntities(m); t++)
970     {
971         unsigned int x = (gOpts.entitySpacing / 2) + (gOpts.entitySpacing * t);
972 
973         drw.setPen(&drw, colourRefs[t]);
974 
975         if(dotted)
976         {
977             drw.dottedLine(&drw, x, ymin, x, ymax);
978         }
979         else
980         {
981             drw.line(&drw, x, ymin, x, ymax);
982         }
983     }
984 
985     drw.setPen(&drw, ADRAW_COL_BLACK);
986 
987 }
988 
989 
990 
991 /** Draw vertical lines and boxes stemming from entities.
992  * \param ymin          Top of the row.
993  * \param ymax          Bottom of the row.
994  * \param boxStart      Column in which the box starts.
995  * \param boxEnd        Column in which the box ends.
996  * \param boxType       The type of box to draw, MSC_ARC_BOX, MSC_ARC_RBOX etc.
997  * \param lineColour    Colour of the lines to use for rendering the box.
998  * \param bgColour      Background colour for rendering the box.
999  */
arcBox(unsigned int ymin,unsigned int ymax,unsigned int boxStart,unsigned int boxEnd,MscArcType boxType,const char * lineColour,const char * bgColour)1000 static void arcBox(unsigned int       ymin,
1001                    unsigned int       ymax,
1002                    unsigned int       boxStart,
1003                    unsigned int       boxEnd,
1004                    MscArcType         boxType,
1005                    const char        *lineColour,
1006                    const char        *bgColour)
1007 {
1008     unsigned int t;
1009 
1010     /* Ensure the start is less than or equal to the end */
1011     if(boxStart > boxEnd)
1012     {
1013         t = boxEnd;
1014         boxEnd = boxStart;
1015         boxStart = t;
1016     }
1017 
1018     /* Now draw the box */
1019     unsigned int x1 = (gOpts.entitySpacing * boxStart) + gOpts.boxSpacing;
1020     unsigned int x2 = gOpts.entitySpacing * (boxEnd + 1) - gOpts.boxSpacing;
1021     unsigned int ymid = (ymin + ymax) / 2;
1022 
1023     /* Set colour for the background area */
1024     if(bgColour != NULL)
1025     {
1026         drw.setPen(&drw, ADrawGetColour(bgColour));
1027     }
1028     else
1029     {
1030         drw.setPen(&drw, ADRAW_COL_WHITE);
1031     }
1032 
1033     /* Draw the background to overwrite the entity lines */
1034     switch(boxType)
1035     {
1036         case MSC_ARC_BOX:
1037             drw.filledRectangle(&drw, x1, ymin, x2, ymax);
1038             break;
1039 
1040         case MSC_ARC_RBOX:
1041             drw.filledRectangle(&drw, x1 + gOpts.rboxArc, ymin, x2 - gOpts.rboxArc, ymax);
1042             drw.filledRectangle(&drw, x1, ymin + gOpts.rboxArc, x2, ymax - gOpts.rboxArc);
1043             drw.filledCircle(&drw, x1 + gOpts.rboxArc, ymin + gOpts.rboxArc, gOpts.rboxArc);
1044             drw.filledCircle(&drw, x2 - gOpts.rboxArc, ymin + gOpts.rboxArc, gOpts.rboxArc);
1045             drw.filledCircle(&drw, x1 + gOpts.rboxArc, ymax - gOpts.rboxArc, gOpts.rboxArc);
1046             drw.filledCircle(&drw, x2 - gOpts.rboxArc, ymax - gOpts.rboxArc, gOpts.rboxArc);
1047             break;
1048 
1049         case MSC_ARC_NOTE:
1050             drw.filledRectangle(&drw, x1, ymin, x2 - gOpts.noteCorner, ymax);
1051             drw.filledRectangle(&drw, x1, ymin + gOpts.noteCorner, x2, ymax);
1052             drw.filledTriangle(&drw, x2 - gOpts.noteCorner, ymin,
1053                                      x2, ymin + gOpts.noteCorner,
1054                                      x2 - gOpts.noteCorner, ymin + gOpts.noteCorner);
1055             break;
1056 
1057         case MSC_ARC_ABOX:
1058             drw.filledRectangle(&drw, x1 + gOpts.aboxSlope, ymin, x2 - gOpts.aboxSlope, ymax);
1059             drw.filledTriangle(&drw, x1 + gOpts.aboxSlope, ymin,
1060                                      x1 + gOpts.aboxSlope, ymax,
1061                                      x1, ymid);
1062             drw.filledTriangle(&drw, x2 - gOpts.aboxSlope, ymin,
1063                                      x2 - gOpts.aboxSlope, ymax,
1064                                      x2, ymid);
1065             break;
1066 
1067         default:
1068             assert(0);
1069     }
1070 
1071     /* Setup the colour for rendering the boxes */
1072     if(lineColour)
1073     {
1074         drw.setPen(&drw, ADrawGetColour(lineColour));
1075     }
1076     else
1077     {
1078         drw.setPen(&drw, ADRAW_COL_BLACK);
1079     }
1080 
1081     /* Draw the outline */
1082     switch(boxType)
1083     {
1084         case MSC_ARC_BOX:
1085             drw.line(&drw, x1, ymin, x2, ymin);
1086             drw.line(&drw, x1, ymax, x2, ymax);
1087             drw.line(&drw, x1, ymin, x1, ymax);
1088             drw.line(&drw, x2, ymin, x2, ymax);
1089             break;
1090 
1091         case MSC_ARC_NOTE:
1092             drw.line(&drw, x1, ymin, x2 - gOpts.noteCorner, ymin);
1093             drw.line(&drw, x1, ymax, x2, ymax);
1094             drw.line(&drw, x1, ymin, x1, ymax);
1095             drw.line(&drw, x2, ymin + gOpts.noteCorner, x2, ymax);
1096             drw.line(&drw, x2 - gOpts.noteCorner, ymin,
1097                            x2, ymin + gOpts.noteCorner);
1098             drw.line(&drw, x2 - gOpts.noteCorner, ymin,
1099                            x2 - gOpts.noteCorner, ymin + gOpts.noteCorner);
1100             drw.line(&drw, x2, ymin + gOpts.noteCorner,
1101                            x2 - gOpts.noteCorner, ymin + gOpts.noteCorner);
1102             break;
1103 
1104         case MSC_ARC_RBOX:
1105             drw.line(&drw, x1 + gOpts.rboxArc, ymin, x2 - gOpts.rboxArc, ymin);
1106             drw.line(&drw, x1 + gOpts.rboxArc, ymax, x2 - gOpts.rboxArc, ymax);
1107             drw.line(&drw, x1, ymin + gOpts.rboxArc, x1, ymax - gOpts.rboxArc);
1108             drw.line(&drw, x2, ymin + gOpts.rboxArc, x2, ymax - gOpts.rboxArc);
1109 
1110             drw.arc(&drw, x1 + gOpts.rboxArc,
1111                     ymin + gOpts.rboxArc, gOpts.rboxArc * 2, gOpts.rboxArc * 2,
1112                     180, 270);
1113             drw.arc(&drw, x2 - gOpts.rboxArc,
1114                     ymin + gOpts.rboxArc, gOpts.rboxArc * 2, gOpts.rboxArc * 2,
1115                     270, 0);
1116             drw.arc(&drw, x2 - gOpts.rboxArc,
1117                     ymax - gOpts.rboxArc, gOpts.rboxArc * 2, gOpts.rboxArc * 2,
1118                     0, 90);
1119             drw.arc(&drw, x1 + gOpts.rboxArc,
1120                     ymax - gOpts.rboxArc, gOpts.rboxArc * 2, gOpts.rboxArc * 2,
1121                     90, 180);
1122             break;
1123 
1124         case MSC_ARC_ABOX:
1125             drw.line(&drw, x1 + gOpts.aboxSlope, ymin, x2 - gOpts.aboxSlope, ymin);
1126             drw.line(&drw, x1 + gOpts.aboxSlope, ymax, x2 - gOpts.aboxSlope, ymax);
1127             drw.line(&drw, x1 + gOpts.aboxSlope, ymin, x1, ymid);
1128             drw.line(&drw, x1, ymid, x1 + gOpts.aboxSlope, ymax);
1129             drw.line(&drw, x2 - gOpts.aboxSlope, ymin, x2, ymid);
1130             drw.line(&drw, x2, ymid, x2 - gOpts.aboxSlope, ymax);
1131             break;
1132 
1133         default:
1134             assert(0);
1135     }
1136 
1137     /* Restore the pen colour if needed */
1138     if(lineColour)
1139     {
1140         drw.setPen(&drw, ADRAW_COL_BLACK);
1141     }
1142 }
1143 
1144 
1145 /** Render text on an arc.
1146  * Draw the text on some arc.
1147  * \param m              The Msc for which the text is being rendered.
1148  * \param ismap          If not \a NULL, write an ismap description here.
1149  * \param outwidth       Width of the output image.
1150  * \param ymid           Co-ordinate of the row on which the text should be aligned.
1151  * \param startCol       The column at which the arc being labelled starts.
1152  * \param endCol         The column at which the arc being labelled ends.
1153  * \param arcLabelLineCount  Count of lines of text in arcLabelLines.
1154  * \param arcLabelLines  Array of lines of text from 0 to arcLabelLineCount - 1.
1155  * \param arcUrl         The URL for rendering the label as a hyperlink.  This
1156  *                        maybe \a NULL if not required.
1157  * \param arcId          The text identifier for the arc.
1158  * \param arcIdUrl       The URL for rendering the test identifier as a hyperlink.
1159  *                        This maybe \a NULL if not required.
1160  * \param arcTextColour  Colour for the arc text, or NULL to use default.
1161  * \param arcTextColour  Colour for the arc text backgroun, or NULL to use default.
1162  * \param arcType        The type of arc, used to control output semantics.
1163  */
arcText(Msc m,FILE * ismap,unsigned int outwidth,unsigned int ymid,int ygradient,unsigned int startCol,unsigned int endCol,const unsigned int arcLabelLineCount,char ** arcLabelLines,const char * arcUrl,const char * arcId,const char * arcIdUrl,const char * arcTextColour,const char * arcTextBgColour,const MscArcType arcType)1164 static void arcText(Msc                m,
1165                     FILE              *ismap,
1166                     unsigned int       outwidth,
1167                     unsigned int       ymid,
1168                     int                ygradient,
1169                     unsigned int       startCol,
1170                     unsigned int       endCol,
1171                     const unsigned int arcLabelLineCount,
1172                     char             **arcLabelLines,
1173                     const char        *arcUrl,
1174                     const char        *arcId,
1175                     const char        *arcIdUrl,
1176                     const char        *arcTextColour,
1177                     const char        *arcTextBgColour,
1178                     const MscArcType   arcType)
1179 {
1180     unsigned int l;
1181     unsigned int y;
1182 
1183     /* A single line of normal text is above the midline */
1184     if(arcLabelLineCount == 1 && !isBoxArc(arcType) &&
1185        arcType != MSC_ARC_DISCO && arcType != MSC_ARC_DIVIDER &&
1186        arcType != MSC_ARC_SPACE)
1187     {
1188         y = ymid + (ygradient / 2) - drw.textHeight(&drw);
1189     }
1190     else /* Text is vertically centered on the midline */
1191     {
1192         int yoff = ygradient - (drw.textHeight(&drw) * arcLabelLineCount);
1193         y = ymid + (yoff / 2);
1194     }
1195 
1196     for(l = 0; l < arcLabelLineCount; l++)
1197     {
1198         const char *lineLabel = arcLabelLines[l];
1199         unsigned int width = drw.textWidth(&drw, lineLabel);
1200         int x = ((startCol + endCol + 1) * gOpts.entitySpacing) / 2;
1201 
1202         y += drw.textHeight(&drw);
1203 
1204         if(startCol != endCol || isBoxArc(arcType))
1205         {
1206             /* Produce central aligned text */
1207             x -= width / 2;
1208         }
1209         else if(startCol < (MscGetNumEntities(m) / 2))
1210         {
1211             /* Form text to the right */
1212             x += gOpts.textHGapPre;
1213         }
1214         else
1215         {
1216             /* Form text to the left */
1217             x -= width + gOpts.textHGapPost;
1218         }
1219 
1220         /* Clip against edges of image */
1221         if(x + width > outwidth)
1222         {
1223             x = outwidth - width;
1224         }
1225 
1226         if(x < 0)
1227         {
1228             x = 0;
1229         }
1230 
1231         /* Check if a URL is associated */
1232         if(arcUrl)
1233         {
1234             /* Default to blue */
1235             drw.setPen(&drw, ADRAW_COL_BLUE);
1236 
1237             /* Image map output */
1238             ismapRect(ismap,
1239                       arcUrl,
1240                       x, y - drw.textHeight(&drw),
1241                       x + width, y);
1242         }
1243 
1244 
1245         /* Set to the explicit colours if directed */
1246         if(arcTextColour != NULL)
1247         {
1248             drw.setPen(&drw, ADrawGetColour(arcTextColour));
1249         }
1250 
1251         if(arcTextBgColour != NULL)
1252         {
1253             drw.setBgPen(&drw, ADrawGetColour(arcTextBgColour));
1254         }
1255 
1256         /* Render text and restore pen */
1257         drw.textR (&drw, x, y, lineLabel);
1258         drw.setPen(&drw, ADRAW_COL_BLACK);
1259         drw.setBgPen(&drw, ADRAW_COL_WHITE);
1260 
1261         /* Render the Id of the arc, if specified and for the first line*/
1262         if(arcId && l == 0)
1263         {
1264             unsigned int idwidth;
1265             int          idx, idy;
1266 
1267             idy = y - drw.textHeight(&drw);
1268             idx = x + width;
1269 
1270             drw.setFontSize(&drw, ADRAW_FONT_TINY);
1271 
1272             idwidth = drw.textWidth(&drw, arcId);
1273             idy    += (drw.textHeight(&drw) + 1) / 2;
1274 
1275             if(arcIdUrl)
1276             {
1277                 drw.setPen(&drw, ADRAW_COL_BLUE);
1278 
1279                 /* Image map output */
1280                 ismapRect(ismap,
1281                           arcIdUrl,
1282                           idx, idy - drw.textHeight(&drw),
1283                           idx + idwidth, idy);
1284             }
1285 
1286             /* Render text and restore pen and font */
1287             drw.textR (&drw, idx, idy, arcId);
1288             drw.setPen(&drw, ADRAW_COL_BLACK);
1289             drw.setFontSize(&drw, ADRAW_FONT_SMALL);
1290         }
1291     }
1292 }
1293 
1294 
1295 /** Render the line and arrow head for some arc.
1296  * This will draw the arc line and arrow head between two columns,
1297  * noting that if the start and end column are the same, an arc is
1298  * rendered.
1299  * \param  m           The Msc for which the text is being rendered.
1300  * \param  ymin        Top of row.
1301  * \param  ymax        Bottom of row.
1302  * \param  ygradient   The gradient of the arc which alters the y position a
1303  *                      the ending column.
1304  * \param  startCol    Starting column for the arc.
1305  * \param  endCol      Column at which the arc terminates.
1306  * \param  hasArrows   If true, draw arc arrows, otherwise omit them.
1307  * \param  hasBiArrows If true, has arrows in both directions.
1308  * \param  arcType     The type of the arc, which dictates its rendered style.
1309  */
arcLine(Msc m,unsigned int y,unsigned int ygradient,unsigned int startCol,unsigned int endCol,const char * arcLineCol,Boolean hasArrows,const int hasBiArrows,const MscArcType arcType)1310 static void arcLine(Msc               m,
1311                     unsigned int      y,
1312                     unsigned int      ygradient,
1313                     unsigned int      startCol,
1314                     unsigned int      endCol,
1315                     const char       *arcLineCol,
1316                     Boolean           hasArrows,
1317                     const int         hasBiArrows,
1318                     const MscArcType  arcType)
1319 {
1320     const unsigned int sx = (startCol * gOpts.entitySpacing) +
1321                              (gOpts.entitySpacing / 2);
1322     const unsigned int dx = (endCol * gOpts.entitySpacing) +
1323                              (gOpts.entitySpacing / 2);
1324 
1325     /* Check if an explicit line colour is requested */
1326     if(arcLineCol != NULL)
1327     {
1328         drw.setPen(&drw, ADrawGetColour(arcLineCol));
1329     }
1330 
1331     if(startCol != endCol)
1332     {
1333         /* Draw the line */
1334         if(arcType == MSC_ARC_RETVAL)
1335         {
1336             drw.dottedLine(&drw, sx, y, dx, y + ygradient);
1337         }
1338         else if(arcType == MSC_ARC_DOUBLE)
1339         {
1340             drw.line(&drw, sx, y - 1, dx, y - 1 + ygradient);
1341             drw.line(&drw, sx, y + 1, dx, y + 1 + ygradient);
1342         }
1343         else if(arcType == MSC_ARC_LOSS)
1344         {
1345             signed int   span = dx - sx;
1346             unsigned int mx = sx + (span / 4) * 3;
1347 
1348             drw.line(&drw, sx, y, mx, y + ygradient);
1349             hasArrows = 0;
1350 
1351             drw.line(&drw, mx - 4, y + ygradient - 4, mx + 4, y + ygradient + 4);
1352             drw.line(&drw, mx + 4, y + ygradient - 4, mx - 4, y + ygradient + 4);
1353         }
1354         else
1355         {
1356             drw.line(&drw, sx, y, dx, y + ygradient);
1357         }
1358 
1359         /* Now the arrow heads */
1360         if(hasArrows)
1361         {
1362             if(startCol < endCol)
1363             {
1364                 arrowR(dx, y + ygradient, arcType);
1365             }
1366             else
1367             {
1368                 arrowL(dx, y + ygradient, arcType);
1369             }
1370 
1371             if(hasBiArrows)
1372             {
1373                 if(startCol < endCol)
1374                 {
1375                     arrowL(sx, y + ygradient, arcType);
1376                 }
1377                 else
1378                 {
1379                     arrowR(sx, y + ygradient, arcType);
1380                 }
1381             }
1382         }
1383     }
1384     else if(startCol < (MscGetNumEntities(m) / 2))
1385     {
1386         /* Arc looping to the left */
1387         if(arcType == MSC_ARC_RETVAL)
1388         {
1389             drw.dottedArc(&drw,
1390                           sx, y,
1391                           gOpts.entitySpacing,
1392                           gOpts.loopArcHeight,
1393                           90,
1394                           270);
1395         }
1396         else if(arcType == MSC_ARC_DOUBLE)
1397         {
1398             drw.arc(&drw,
1399                     sx, y - 1,
1400                     gOpts.entitySpacing,
1401                     gOpts.loopArcHeight,
1402                     90,
1403                     270);
1404             drw.arc(&drw,
1405                     sx, y + 1,
1406                     gOpts.entitySpacing,
1407                     gOpts.loopArcHeight,
1408                     90,
1409                     270);
1410         }
1411         else if(arcType == MSC_ARC_LOSS)
1412         {
1413             unsigned int px, py;
1414 
1415             drw.arc(&drw,
1416                     sx, y - 1,
1417                     gOpts.entitySpacing - 8,
1418                     gOpts.loopArcHeight,
1419                     180 - 45,
1420                     270);
1421 
1422             hasArrows = FALSE;
1423 
1424             /* Get co-ordinates of the arc end-point */
1425             ADrawComputeArcPoint(sx, y - 1, gOpts.entitySpacing - 8,
1426                                  gOpts.loopArcHeight, 180 - 45,
1427                                  &px, &py);
1428 
1429             /* Draw a cross */
1430             drw.line(&drw, px - 4, py - 4, px + 4, py + 4);
1431             drw.line(&drw, px + 4, py - 4, px - 4, py + 4);
1432         }
1433         else
1434         {
1435             drw.arc(&drw,
1436                     sx, y,
1437                     gOpts.entitySpacing - 4,
1438                     gOpts.loopArcHeight,
1439                     90,
1440                     270);
1441         }
1442 
1443         if(hasArrows)
1444         {
1445             arrowR(dx, y + (gOpts.loopArcHeight / 2), arcType);
1446         }
1447     }
1448     else
1449     {
1450         /* Arc looping to right */
1451         if(arcType == MSC_ARC_RETVAL)
1452         {
1453             drw.dottedArc(&drw,
1454                           sx, y,
1455                           gOpts.entitySpacing,
1456                           gOpts.loopArcHeight,
1457                           270,
1458                           90);
1459         }
1460         else if(arcType == MSC_ARC_DOUBLE)
1461         {
1462             drw.arc(&drw,
1463                     sx, y - 1,
1464                     gOpts.entitySpacing,
1465                     gOpts.loopArcHeight,
1466                     270,
1467                     90);
1468             drw.arc(&drw,
1469                     sx, y + 1,
1470                     gOpts.entitySpacing,
1471                     gOpts.loopArcHeight,
1472                     270,
1473                     90);
1474         }
1475         else if(arcType == MSC_ARC_LOSS)
1476         {
1477             unsigned int px, py;
1478 
1479             drw.arc(&drw,
1480                     sx, y - 1,
1481                     gOpts.entitySpacing - 8,
1482                     gOpts.loopArcHeight,
1483                     270,
1484                     45);
1485 
1486             hasArrows = FALSE;
1487 
1488             /* Get co-ordinates of the arc end-point */
1489             ADrawComputeArcPoint(sx, y - 1, gOpts.entitySpacing - 8,
1490                                  gOpts.loopArcHeight, 45,
1491                                  &px, &py);
1492 
1493             /* Draw a cross */
1494             drw.line(&drw, px - 4, py - 4, px + 4, py + 4);
1495             drw.line(&drw, px + 4, py - 4, px - 4, py + 4);
1496         }
1497         else
1498         {
1499             drw.arc(&drw,
1500                     sx, y,
1501                     gOpts.entitySpacing,
1502                     gOpts.loopArcHeight,
1503                     270,
1504                     90);
1505         }
1506 
1507         if(hasArrows)
1508         {
1509             arrowL(dx, y + (gOpts.loopArcHeight / 2), arcType);
1510         }
1511     }
1512 
1513     /* Restore pen if needed */
1514     if(arcLineCol != NULL)
1515     {
1516         drw.setPen(&drw, ADRAW_COL_BLACK);
1517     }
1518 }
1519 
1520 
1521 /* Perform post-parsing validation of the MSC.
1522  *  This checks the passed MSC for various rules which can't easily be tested
1523  *  at parse time.
1524  */
checkMsc(Msc m)1525 Boolean checkMsc(Msc m)
1526 {
1527     /* Check all arc entites are known */
1528     MscResetArcIterator(m);
1529     do
1530     {
1531         const MscArcType arcType  = MscGetCurrentArcType(m);
1532 
1533         if(arcType != MSC_ARC_PARALLEL && arcType != MSC_ARC_DISCO &&
1534            arcType != MSC_ARC_DIVIDER && arcType != MSC_ARC_SPACE)
1535         {
1536             const char *src = MscGetCurrentArcSource(m);
1537             const char *dst = MscGetCurrentArcDest(m);
1538             const int   startCol = MscGetEntityIndex(m, src);
1539             const int   endCol   = MscGetEntityIndex(m, dst);
1540 
1541             /* Check the start column is valid */
1542             if(startCol == -1)
1543             {
1544                 fprintf(stderr, "Error detected at line %u: Unknown source entity '%s'.\n",
1545                         MscGetCurrentArcInputLine(m), src);
1546                 return FALSE;
1547             }
1548 
1549             if(endCol == -1 && !isBroadcastArc(dst))
1550             {
1551                 fprintf(stderr, "Error detected at line %u: Unknown destination entity '%s'.\n",
1552                         MscGetCurrentArcInputLine(m), dst);
1553                 return FALSE;
1554             }
1555         }
1556     }
1557     while(MscNextArc(m));
1558 
1559     return TRUE;
1560 }
1561 
1562 
main(const int argc,const char * argv[])1563 int main(const int argc, const char *argv[])
1564 {
1565     FILE            *ismap = NULL;
1566     ADrawOutputType  outType;
1567     ADrawColour     *entColourRef;
1568     char            *outImage;
1569     Msc              m;
1570     unsigned int     w, h, row, col;
1571     RowInfo         *rowInfo;
1572     Boolean          addLines;
1573     float            f;
1574 
1575     /* Parse the command line options */
1576     if(!CmdParse(gClSwitches, sizeof(gClSwitches) / sizeof(CmdSwitch), argc - 1, &argv[1], "-i"))
1577     {
1578         Usage();
1579         return EXIT_FAILURE;
1580     }
1581 
1582     if(gDumpLicencePresent)
1583     {
1584         Licence();
1585         return EXIT_SUCCESS;
1586     }
1587 
1588     /* Check that the output type was specified */
1589     if(!gOutTypePresent)
1590     {
1591         fprintf(stderr, "-T <type> must be specified on the command line\n");
1592         Usage();
1593         return EXIT_FAILURE;
1594     }
1595 
1596     /* Check that the output filename was specified */
1597     if(!gOutputFilePresent)
1598     {
1599         if(!gInputFilePresent || strcmp(gInputFile, "-") == 0)
1600         {
1601             fprintf(stderr, "-o <filename> must be specified on the command line if -i is not used or input is from stdin\n");
1602             Usage();
1603             return EXIT_FAILURE;
1604         }
1605 
1606         gOutputFilePresent = TRUE;
1607         snprintf(gOutputFile, sizeof(gOutputFile), "%s", gInputFile);
1608         trimExtension(gOutputFile);
1609         strncat(gOutputFile, ".", sizeof(gOutputFile) - (strlen(gOutputFile) + 1));
1610         strncat(gOutputFile, gOutType, sizeof(gOutputFile) - (strlen(gOutputFile) + 1));
1611     }
1612 #ifdef USE_FREETYPE
1613     /* Check for an output font name from the environment */
1614     if(!gOutputFontPresent)
1615     {
1616         const char *envFont = getenv("MSCGEN_FONT");
1617         const int   bufLen  = sizeof(gOutputFont);
1618 
1619         if(!envFont)
1620         {
1621             /* Pick a default font */
1622             snprintf(gOutputFont, bufLen, "helvetica");
1623         }
1624         else if(snprintf(gOutputFont, bufLen, "%s", envFont) >= bufLen)
1625         {
1626             fprintf(stderr, "MSCGEN_FONT font name too long (must be < %d characters)\n",
1627                     bufLen);
1628             return EXIT_FAILURE;
1629         }
1630     }
1631 #else
1632     if(gOutputFontPresent)
1633     {
1634       fprintf(stderr, "Note: -F option specified but ignored since mscgen was not built\n"
1635                       "      with USE_FREETYPE.\n");
1636     }
1637 #endif
1638 
1639 #ifdef __WIN32__
1640     /* On Windows, create a temporary file */
1641     deleteTmpFilename = tempnam(NULL, "mscgen");
1642     if(!deleteTmpFilename)
1643     {
1644         perror("tempnam() failed");
1645         return EXIT_FAILURE;
1646     }
1647 
1648     /* Schedule the temp file to be deleted */
1649     atexit(deleteTmp);
1650 #endif
1651 
1652     /* Determine the output type */
1653     if(strcmp(gOutType, "png") == 0)
1654     {
1655         outType  = ADRAW_FMT_PNG;
1656         outImage = gOutputFile;
1657     }
1658     else if(strcmp(gOutType, "eps") == 0)
1659     {
1660         outType  = ADRAW_FMT_EPS;
1661         outImage = gOutputFile;
1662     }
1663     else if(strcmp(gOutType, "svg") == 0)
1664     {
1665         outType  = ADRAW_FMT_SVG;
1666         outImage = gOutputFile;
1667     }
1668     else if(strcmp(gOutType, "ismap") == 0)
1669     {
1670 #ifdef __WIN32__
1671         /* Use the temp file */
1672         outType  = ADRAW_FMT_PNG;
1673         outImage = deleteTmpFilename;
1674 #else
1675         static char tmpTemplate[] = "/tmp/mscgenXXXXXX";
1676         int h;
1677 
1678         outType  = ADRAW_FMT_PNG;
1679         outImage = tmpTemplate;
1680 
1681         /* Create temporary file */
1682         h = mkstemp(tmpTemplate);
1683         if(h == -1)
1684         {
1685             perror("mkstemp() failed");
1686             return EXIT_FAILURE;
1687         }
1688 
1689         /* Close the file handle */
1690         close(h);
1691 
1692         /* Schedule the temp file to be deleted */
1693         deleteTmpFilename = outImage;
1694         atexit(deleteTmp);
1695 #endif
1696     }
1697     else
1698     {
1699         fprintf(stderr, "Unknown output format '%s'\n", gOutType);
1700         Usage();
1701         return EXIT_FAILURE;
1702     }
1703 
1704     /* Parse input, either from a file, or stdin */
1705     if(gInputFilePresent && !strcmp(gInputFile, "-") == 0)
1706     {
1707         FILE *in = fopen(gInputFile, "r");
1708 
1709         if(!in)
1710         {
1711             fprintf(stderr, "Failed to open input file '%s'\n", gInputFile);
1712             return EXIT_FAILURE;
1713         }
1714         m = MscParse(in);
1715         fclose(in);
1716     }
1717     else
1718     {
1719         m = MscParse(stdin);
1720     }
1721 
1722     /* Check if the parse was okay and the MSC good */
1723     if(!m || !checkMsc(m))
1724     {
1725         return EXIT_FAILURE;
1726     }
1727 
1728     /* Print the parse output if requested */
1729     if(gPrintParsePresent)
1730     {
1731         MscPrint(m);
1732     }
1733 
1734 #ifndef USE_FREETYPE
1735     if(outType == ADRAW_FMT_PNG && lex_getutf8())
1736     {
1737         fprintf(stderr, "Warning: Optional UTF-8 byte-order-mark detected at start of input, but mscgen\n"
1738                         "         was not configured to use FreeType for text rendering.  Rendering of\n"
1739                         "         UTF-8 characters in PNG output may be incorrect.\n");
1740     }
1741 #endif
1742 
1743     /* Check if an ismap file should also be generated */
1744     if(strcmp(gOutType, "ismap") == 0)
1745     {
1746         ismap = fopen(gOutputFile, "w");
1747         if(!ismap)
1748         {
1749             fprintf(stderr, "Failed to open output file '%s': %s\n", gOutputFile, strerror(errno));
1750             return EXIT_FAILURE;
1751         }
1752     }
1753 
1754     /* Open the drawing context with dummy dimensions */
1755 #ifdef __WIN32__
1756     if(!ADrawOpen(10, 10, deleteTmpFilename, gOutputFont, outType, &drw))
1757 #else
1758     if(!ADrawOpen(10, 10, "/dev/null", gOutputFont, outType, &drw))
1759 #endif
1760     {
1761         fprintf(stderr, "Failed to create output context\n");
1762         return EXIT_FAILURE;
1763     }
1764 
1765     /* Now compute ideal canvas size, which may use text metrics */
1766     if(MscGetOptAsFloat(m, MSC_OPT_WIDTH, &f))
1767     {
1768         gOpts.idealCanvasWidth = f;
1769     }
1770     else if(MscGetOptAsFloat(m, MSC_OPT_HSCALE, &f))
1771     {
1772         gOpts.idealCanvasWidth *= f;
1773     }
1774 
1775     /* Set the arc gradient if needed */
1776     if(MscGetOptAsFloat(m, MSC_OPT_ARCGRADIENT, &f))
1777     {
1778         gOpts.arcGradient = (int)f;
1779         gOpts.arcSpacing += gOpts.arcGradient;
1780     }
1781 
1782     /* Check if word wrapping on arcs other than boxes should be used */
1783     MscGetOptAsBoolean(m, MSC_OPT_WORDWRAPARCS, &gOpts.wordWrapArcLabels);
1784 
1785     /* Work out the entitySpacing */
1786     if(gOpts.idealCanvasWidth / MscGetNumEntities(m) > gOpts.entitySpacing)
1787     {
1788         gOpts.entitySpacing = gOpts.idealCanvasWidth / MscGetNumEntities(m);
1789     }
1790 
1791     /* Work out the entityHeadGap */
1792     MscResetEntityIterator(m);
1793     for(col = 0; col < MscGetNumEntities(m); col++)
1794     {
1795         unsigned int lines = countLines(MscGetCurrentEntAttrib(m, MSC_ATTR_LABEL));
1796         unsigned int gap;
1797 
1798         /* Get the required gap */
1799         gap = lines * drw.textHeight(&drw);
1800         if(gap > gOpts.entityHeadGap)
1801         {
1802             gOpts.entityHeadGap = gap;
1803         }
1804 
1805         MscNextEntity(m);
1806     }
1807 
1808     /* Work out the width and height of the canvas */
1809     rowInfo = computeCanvasSize(m, &w , &h);
1810 
1811     if(gPrintParsePresent)
1812     {
1813         unsigned int t;
1814 
1815         printf("\nRow heights:\n");
1816 
1817         for(t = 0; t < MscGetNumArcs(m) - MscGetNumParallelArcs(m); t++)
1818         {
1819             printf(" %3u: min=%u arcliney=%u max=%u maxTextLines=%u\n",
1820                    t, rowInfo[t].ymin, rowInfo[t].arcliney, rowInfo[t].ymax, rowInfo[t].maxTextLines);
1821         }
1822     }
1823 
1824     /* Close the temporary output file */
1825     drw.close(&drw);
1826 
1827     /* Open the output */
1828     if(!ADrawOpen(w, h, outImage, gOutputFont, outType, &drw))
1829     {
1830         fprintf(stderr, "Failed to create output context\n");
1831         return EXIT_FAILURE;
1832     }
1833 
1834     /* Allocate storage for entity heading colours */
1835     entColourRef = malloc_s(MscGetNumEntities(m) * sizeof(ADrawColour));
1836 
1837     /* Draw the entity headings */
1838     MscResetEntityIterator(m);
1839     for(col = 0; col < MscGetNumEntities(m); col++)
1840     {
1841         unsigned int x = (gOpts.entitySpacing / 2) + (gOpts.entitySpacing * col);
1842         const char  *line;
1843 
1844         /* Titles */
1845         entityText(ismap,
1846                    x,
1847                    gOpts.entityHeadGap - (drw.textHeight(&drw) / 2),
1848                    MscGetCurrentEntAttrib(m, MSC_ATTR_LABEL),
1849                    MscGetCurrentEntAttrib(m, MSC_ATTR_URL),
1850                    MscGetCurrentEntAttrib(m, MSC_ATTR_ID),
1851                    MscGetCurrentEntAttrib(m, MSC_ATTR_IDURL),
1852                    MscGetCurrentEntAttrib(m, MSC_ATTR_TEXT_COLOUR),
1853                    MscGetCurrentEntAttrib(m, MSC_ATTR_TEXT_BGCOLOUR));
1854 
1855         /* Get the colours */
1856         line = MscGetCurrentEntAttrib(m, MSC_ATTR_LINE_COLOUR);
1857         if(line != NULL)
1858         {
1859             entColourRef[col] = ADrawGetColour(line);
1860         }
1861         else
1862         {
1863             entColourRef[col] = ADRAW_COL_BLACK;
1864         }
1865 
1866         MscNextEntity(m);
1867     }
1868 
1869     /* Draw the arcs */
1870     addLines = TRUE;
1871     row = 0;
1872 
1873     MscResetArcIterator(m);
1874     do
1875     {
1876         const MscArcType   arcType           = MscGetCurrentArcType(m);
1877         const char        *arcUrl            = MscGetCurrentArcAttrib(m, MSC_ATTR_URL);
1878         const char        *arcId             = MscGetCurrentArcAttrib(m, MSC_ATTR_ID);
1879         const char        *arcIdUrl          = MscGetCurrentArcAttrib(m, MSC_ATTR_IDURL);
1880         const char        *arcTextColour     = MscGetCurrentArcAttrib(m, MSC_ATTR_TEXT_COLOUR);
1881         const char        *arcTextBgColour   = MscGetCurrentArcAttrib(m, MSC_ATTR_TEXT_BGCOLOUR);
1882         const char        *arcLineColour     = MscGetCurrentArcAttrib(m, MSC_ATTR_LINE_COLOUR);
1883         const int          arcGradient       = isBoxArc(arcType) ? 0 : getArcGradient(m, rowInfo, row);
1884         const int          arcHasArrows      = MscGetCurrentArcAttrib(m, MSC_ATTR_NO_ARROWS) == NULL;
1885         const int          arcHasBiArrows    = MscGetCurrentArcAttrib(m, MSC_ATTR_BI_ARROWS) != NULL;
1886         char             **arcLabelLines     = NULL;
1887         unsigned int       arcLabelLineCount = 0;
1888         int                startCol = -1, endCol = -1;
1889 
1890         if(arcType == MSC_ARC_PARALLEL)
1891         {
1892             addLines = FALSE;
1893 
1894             /* Rewind the row */
1895             assert(row > 0);
1896             row--;
1897         }
1898         else
1899         {
1900             const unsigned int ymin = rowInfo[row].ymin;
1901             const unsigned int ymid = rowInfo[row].arcliney;
1902             const unsigned int ymax = rowInfo[row].ymax;
1903 #if 0
1904             /* For debug, mark the row spacing */
1905             drw.line(&drw, 0, ymin, 10, ymin);
1906             drw.line(&drw, 0, ymid, 5, ymid);
1907             drw.line(&drw, 0, ymax, 10, ymax);
1908 #endif
1909             /* Get the entity indices */
1910             if(arcType != MSC_ARC_DISCO && arcType != MSC_ARC_DIVIDER && arcType != MSC_ARC_SPACE)
1911             {
1912                 startCol = MscGetEntityIndex(m, MscGetCurrentArcSource(m));
1913                 endCol   = MscGetEntityIndex(m, MscGetCurrentArcDest(m));
1914 
1915                 /* Check that the start column is known and the end column is
1916                  *  known, or that it's a broadcast arc
1917                  */
1918                 assert(startCol != -1);
1919                 assert(endCol != -1 || isBroadcastArc(MscGetCurrentArcDest(m)));
1920 
1921                 /* Check for entity colouring if not set explicity on the arc */
1922                 if(arcTextColour == NULL)
1923                 {
1924                     arcTextColour = MscGetEntAttrib(m, startCol, MSC_ATTR_ARC_TEXT_COLOUR);
1925                 }
1926 
1927                 if(arcTextBgColour == NULL)
1928                 {
1929                     arcTextBgColour = MscGetEntAttrib(m, startCol, MSC_ATTR_ARC_TEXT_BGCOLOUR);
1930                 }
1931 
1932                 if(arcLineColour == NULL)
1933                 {
1934                     arcLineColour = MscGetEntAttrib(m, startCol, MSC_ATTR_ARC_LINE_COLOUR);
1935                 }
1936 
1937             }
1938             else
1939             {
1940                 /* Discontinuity or parallel arc spans whole chart */
1941                 startCol = 0;
1942                 endCol   = MscGetNumEntities(m) - 1;
1943             }
1944 
1945             /* Work out how the label fits the gap between entities */
1946             arcLabelLineCount = computeLabelLines(m, arcType, &arcLabelLines,
1947                                                   MscGetCurrentArcAttrib(m, MSC_ATTR_LABEL),
1948                                                   startCol, endCol);
1949 
1950             /* Check if this is a broadcast message */
1951             if(isBroadcastArc(MscGetCurrentArcDest(m)))
1952             {
1953                 unsigned int t;
1954 
1955                 /* Add in the entity lines */
1956                 if(addLines)
1957                 {
1958                     entityLines(m, ymin, ymax + gOpts.arcSpacing, FALSE, entColourRef);
1959                 }
1960 
1961                 /* Draw arcs to each entity */
1962                 for(t = 0; t < MscGetNumEntities(m); t++)
1963                 {
1964                     if((signed)t != startCol)
1965                     {
1966                         arcLine(m, ymid, arcGradient, startCol,
1967                                 t, arcLineColour, arcHasArrows,
1968                                 arcHasBiArrows, arcType);
1969                     }
1970                 }
1971 
1972                 /* Fix up the start/end columns to span chart */
1973                 startCol = 0;
1974                 endCol   = MscGetNumEntities(m) - 1;
1975             }
1976             else
1977             {
1978                 /* Check if it is a box, discontinuity arc etc... */
1979                 if(isBoxArc(arcType))
1980                 {
1981                     if(addLines)
1982                     {
1983                         entityLines(m, ymin, ymax + gOpts.arcSpacing, FALSE, entColourRef);
1984                     }
1985                     arcBox(ymin, ymax, startCol, endCol, arcType, arcLineColour, arcTextBgColour);
1986                 }
1987                 else if(arcType == MSC_ARC_DISCO)
1988                 {
1989                     if(addLines)
1990                     {
1991                         entityLines(m, ymin, ymax + gOpts.arcSpacing, TRUE /* dotted */, entColourRef);
1992                     }
1993                 }
1994                 else if(arcType == MSC_ARC_DIVIDER || arcType == MSC_ARC_SPACE)
1995                 {
1996                     if(addLines)
1997                     {
1998                         entityLines(m, ymin, ymax + gOpts.arcSpacing, FALSE, entColourRef);
1999                     }
2000 
2001                     /* Dividers also have a horizontal line at the middle */
2002                     if(arcType == MSC_ARC_DIVIDER)
2003                     {
2004                         const unsigned int margin = gOpts.entitySpacing / 4;
2005 
2006                         if(arcLineColour != NULL)
2007                         {
2008                             drw.setPen(&drw, ADrawGetColour(arcLineColour));
2009                         }
2010 
2011                         /* Draw line through middle of text */
2012                         drw.dottedLine(&drw,
2013                                         margin, ymid,
2014                                         (MscGetNumEntities(m) * gOpts.entitySpacing) - margin,  ymid);
2015 
2016                         if(arcLineColour != NULL)
2017                         {
2018                             drw.setPen(&drw, ADRAW_COL_BLACK);
2019                         }
2020                     }
2021                 }
2022                 else
2023                 {
2024                     if(addLines)
2025                     {
2026                         entityLines(m, ymin, ymax + gOpts.arcSpacing, FALSE, entColourRef);
2027                     }
2028                     arcLine(m, ymid, arcGradient, startCol, endCol, arcLineColour,
2029                             arcHasArrows, arcHasBiArrows, arcType);
2030                 }
2031             }
2032 
2033             /* All may have text */
2034             if(arcLabelLineCount > 0)
2035             {
2036                 arcText(m, ismap, w, ymid, arcGradient,
2037                         startCol, endCol,
2038                         arcLabelLineCount, arcLabelLines,
2039                         arcUrl, arcId, arcIdUrl,
2040                         arcTextColour, arcTextBgColour, arcType);
2041             }
2042 
2043             freeLabelLines(arcLabelLineCount, arcLabelLines);
2044 
2045             /* Advance the row */
2046             row++;
2047             addLines = TRUE;
2048         }
2049     }
2050     while(MscNextArc(m));
2051 
2052     /* Skip arcs may require the entity lines to be extended */
2053     entityLines(m,
2054                 rowInfo[(MscGetNumArcs(m) - MscGetNumParallelArcs(m)) - 1].ymax,
2055                 h, FALSE, entColourRef);
2056 
2057     /* Close the image map if needed */
2058     if(ismap)
2059     {
2060         fclose(ismap);
2061     }
2062 
2063 #ifndef NDEBUG
2064     /* Free the allocated memory to allow leak detection */
2065     free(entColourRef);
2066     free(rowInfo);
2067     MscFree(m);
2068 #endif
2069 
2070     /* Close the context */
2071     drw.close(&drw);
2072 
2073     return EXIT_SUCCESS;
2074 }
2075 
2076 /* END OF FILE */
2077