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