summaryrefslogtreecommitdiff
path: root/deps/libmscgen/mscgen_api.c
diff options
context:
space:
mode:
Diffstat (limited to 'deps/libmscgen/mscgen_api.c')
-rw-r--r--deps/libmscgen/mscgen_api.c1876
1 files changed, 1876 insertions, 0 deletions
diff --git a/deps/libmscgen/mscgen_api.c b/deps/libmscgen/mscgen_api.c
new file mode 100644
index 0000000..e2cdcb9
--- /dev/null
+++ b/deps/libmscgen/mscgen_api.c
@@ -0,0 +1,1876 @@
+#include "mscgen_api.h"
+#include "mscgen_config.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <errno.h>
+#include <ctype.h>
+#include <assert.h>
+#include "mscgen_lexer.h"
+#include "mscgen_adraw.h"
+#include "mscgen_msc.h"
+#include "mscgen_safe.h"
+
+/***************************************************************************
+ * Macro definitions
+ ***************************************************************************/
+
+#define M_Max(a, b) (((a) > (b)) ? (a) : (b))
+#define M_Min(a, b) (((a) < (b)) ? (a) : (b))
+
+/***************************************************************************
+ * Types
+ ***************************************************************************/
+
+/** Structure for holding global options.
+ * This structure groups all the options that affect the text output into
+ * one structure.
+ */
+typedef struct GlobalOptionsTag
+{
+ /** Ideal width of output canvas.
+ * If this value allows the entitySpacing to be increased, then
+ * entitySpacing will be set to the larger value of it's original
+ * value and idealCanvasWidth / number of entities.
+ */
+ unsigned int idealCanvasWidth;
+
+ /** Horizontal spacing between entities. */
+ unsigned int entitySpacing;
+
+ /** Gap at the top of the page. */
+ unsigned int entityHeadGap;
+
+ /** Vertical spacing between arcs. */
+ unsigned int arcSpacing;
+
+ /** Arc gradient.
+ * Y offset of arc head, relative to tail, in pixels.
+ */
+ int arcGradient;
+
+ /** Gap between adjacent boxes. */
+ unsigned int boxSpacing;
+
+ /** Minimum distance between box edges and text. */
+ unsigned int boxInternalBorder;
+
+ /** Radius of rounded box corner arcs. */
+ unsigned int rboxArc;
+
+ /** Size of 'corner' added to note boxes. */
+ unsigned int noteCorner;
+
+ /** Angular box slope in pixels. */
+ unsigned int aboxSlope;
+
+ /** If TRUE, wrap arc text as well as box contents. */
+ Boolean wordWrapArcLabels;
+
+ /** Horizontal width of the arrow heads. */
+ unsigned int arrowWidth;
+
+ /** Vertical depth of the arrow heads. */
+ unsigned int arrowHeight;
+
+ /** Height of an arc which loops back to itself. */
+ unsigned int loopArcHeight;
+
+ /** Horizontal gap between text and horizontal lines. */
+ unsigned int textHGapPre;
+
+ /** Horizontal gap between text and horizontal lines. */
+ unsigned int textHGapPost;
+}
+GlobalOptions;
+
+/** Information about each out row.
+ */
+typedef struct
+{
+ /** Minimum Y value. */
+ unsigned int ymin;
+
+ /** Y position of the arc on the row. */
+ unsigned int arcliney;
+
+ /** Maximum Y value. */
+ unsigned int ymax;
+
+ /** Maximum lines of text on the row. */
+ unsigned int maxTextLines;
+}
+RowInfo;
+
+
+typedef struct ContextTag
+{
+ GlobalOptions opts;
+ ADraw drw;
+} Context;
+
+
+/***************************************************************************
+ * Local Variables.
+ ***************************************************************************/
+
+static const GlobalOptions gDefaultOpts =
+{
+ 600, /* idealCanvasWidth */
+
+ 80, /* entitySpacing */
+ 20, /* entityHeadGap */
+ 6, /* arcSpacing */
+ 0, /* arcGradient */
+ 8, /* boxSpacing */
+ 4, /* boxInternalBorder */
+ 6, /* rboxArc */
+ 12, /* noteCorner */
+ 6, /* aboxSlope */
+ FALSE, /* wordWrapArcLabels */
+
+ /* Arrow options */
+ 10, 6,
+
+ /* loopArcHeight */
+ 12,
+
+ /* textHGapPre, textHGapPost */
+ 2, 2
+};
+
+
+/***************************************************************************
+ * Functions
+ ***************************************************************************/
+
+/** Count the number of lines in some string.
+ * This counts line breaks that are written as a literal '\n' in the line to
+ * determine how many lines of output are needed.
+ *
+ * \param[in] l Pointer to the input string to inspect.
+ * \returns The count of '\n' characters appearing in the input string + 1.
+ */
+static unsigned int countLines(const char *l)
+{
+ unsigned int c = 1;
+
+ do
+ {
+ c++;
+
+ l = strstr(l, "\\n");
+ if (l) l += 2;
+ } while (l != NULL);
+
+ return c;
+}
+
+/** Check if some arc type indicates a box.
+ */
+static Boolean isBoxArc(const MscArcType a)
+{
+ return a == MSC_ARC_BOX || a == MSC_ARC_RBOX ||
+ a == MSC_ARC_ABOX || a== MSC_ARC_NOTE;
+}
+
+/** Get the skip value in pixels for some the current arc in the Msc.
+ */
+static int getArcGradient(Context *ctx, Msc m, const RowInfo *rowInfo, unsigned int row)
+{
+ const char *s = MscGetCurrentArcAttrib(m, MSC_ATTR_ARC_SKIP);
+ unsigned int v = ctx->opts.arcGradient;
+
+ if (s != NULL && rowInfo != NULL)
+ {
+ const unsigned int rowCount = MscGetNumArcs(m) - MscGetNumParallelArcs(m);
+ unsigned int skip;
+
+ if (sscanf(s, "%u", &skip) == 1)
+ {
+ unsigned int ystart = rowInfo[row].arcliney;
+ unsigned int yend = rowInfo[M_Min(rowCount - 1, row + skip)].arcliney;
+
+ v += yend - ystart;
+ }
+ else
+ {
+ fprintf(stderr, "Warning: Non-integer arcskip value: %s\n", s);
+ }
+ }
+
+ return v;
+}
+
+
+/** Check if some arc name indicates a broadcast entity.
+ */
+static Boolean isBroadcastArc(const char *entity)
+{
+ return entity != NULL && (strcmp(entity, "*") == 0);
+}
+
+/** Free memory allocated for the label lines.
+ */
+static void freeLabelLines(unsigned int n, char **lines)
+{
+ while (n > 0)
+ {
+ n--;
+ free(lines[n]);
+ }
+
+ free(lines);
+}
+
+
+/** Word wrap a line of text until the first line is less than \a width wide.
+ * This removes words from the input line and builds them into a 2nd new
+ * string until the input line is shorter than the supplied width. The
+ * input string is directly truncated, while the remaining characters are
+ * returned in a new memory allocation. On return, the input line of text
+ * will be shorter than \a width, while the newly returned string will contain
+ * all the remaining characters.
+ *
+ * If the input line is already shorter than \a width, the function returns
+ * NULL and does not modify the input line of text.
+ *
+ * \param[in,out] l Input line of text which maybe modified if needed.
+ * \param[in] width Maximum allowable text line width.
+ * \returns NULL if \a l was already less then \a width long,
+ * otherwise a new string giving the remained of the string.
+ */
+static char *splitStringToWidth(Context *ctx, char *l, unsigned int width)
+{
+ char *p = l + strlen(l);
+ char *orig = NULL;
+ int m, n;
+
+ if (ctx->drw.textWidth(&ctx->drw, l) > width)
+ {
+ /* Duplicate the original string */
+ orig = strdup_s(l);
+
+ /* Now remove words from the line until it fits the available width */
+ do
+ {
+ /* Skip back 1 word */
+ while (!isspace(*p) && p > l)
+ {
+ p--;
+ }
+
+ if (p > l)
+ {
+ *p = '\0';
+ }
+ }
+ while (ctx->drw.textWidth(&ctx->drw, l) > width && p > l);
+
+ /* Check if the first word is bigger than the available space;
+ * we need to hyphenate in this case.
+ */
+ if (p == l)
+ {
+ const unsigned int hyphenWidth = ctx->drw.textWidth(&ctx->drw, "-");
+
+ /* Find the end of the first word */
+ while (!isspace(*p) && *p != '\0')
+ {
+ p++;
+ }
+
+ /* Start removing characters from the word */
+ do
+ {
+ *p = '\0';
+ p--;
+ }
+ while (ctx->drw.textWidth(&ctx->drw, l) + hyphenWidth > width && p > l);
+
+ /* Add a hyphen */
+ *p = '-';
+ }
+
+ /* Copy the remaining line to the start of the string */
+ m = 0;
+ n = (int)(p - l);
+
+ while (isspace(orig[n]) && orig[n] != '\0')
+ {
+ n++;
+ }
+
+ do
+ {
+ orig[m++] = orig[n++];
+ }
+ while (orig[m - 1] != '\0');
+ }
+
+ return orig;
+}
+
+
+/** Split an input arc label into lines, word-wrapping if needed.
+ * This takes the literal label supplied from the input and splits it into an
+ * array of char * text lines. Splitting is first done according to literal
+ * '\n' character sequences added by the user, then according to word wrapping
+ * to fit available space, if appropriate.
+ *
+ * \param[in] m The MSC for which the lines are to be split.
+ * \param[in] arcType The type of the arc being labelled.
+ * \param[in,out] lines Pointer to be filled with output line array.
+ * \param[in] label Original arc label from input file.
+ * \param[in] startCol Column in which the arc starts.
+ * \param[in] endCol Column in which the arc ends, or -1 for broadcast arcs.
+ *
+ * \note The returned strings and array must be free()'d. freeLabelLines() can
+ * be used for this purpose.
+ */
+static unsigned int computeLabelLines(Context *ctx,
+ Msc m,
+ const MscArcType arcType,
+ char ***lines,
+ const char *label,
+ int startCol,
+ int endCol)
+{
+ unsigned int width;
+ unsigned int nAllocLines = 8;
+ char **retLines = malloc_s(sizeof(char *) * nAllocLines);
+ unsigned int c = 0;
+
+ assert(startCol >= 0 && startCol < (signed)MscGetNumEntities(m));
+ assert(startCol >= -1 && startCol < (signed)MscGetNumEntities(m));
+
+ /* Compute available width for text */
+ if (isBoxArc(arcType) || ctx->opts.wordWrapArcLabels)
+ {
+ if (endCol == -1)
+ {
+ /* This is a special case for a broadcast arc */
+ width = ctx->opts.entitySpacing * MscGetNumEntities(m);
+ }
+ else if (startCol < endCol)
+ {
+ width = ctx->opts.entitySpacing * (1 + (endCol - startCol));
+ }
+ else
+ {
+ width = ctx->opts.entitySpacing * (1 + (startCol - endCol));
+ }
+
+ /* Reduce the width due to the box borders */
+ if (isBoxArc(arcType))
+ {
+ width -= (ctx->opts.boxSpacing + ctx->opts.boxInternalBorder) * 2;
+ }
+
+ if (arcType == MSC_ARC_NOTE)
+ {
+ width -= ctx->opts.noteCorner;
+ }
+ }
+ else
+ {
+ width = UINT_MAX;
+ }
+
+ /* Split the input label into lines */
+ while (label != NULL)
+ {
+ /* First split around user specified lines with literal '\n' */
+ char *nextLine = strstr(label, "\\n");
+ if (nextLine)
+ {
+ const int lineLen = (int)(nextLine - label);
+
+ /* Allocate storage and duplicate the line */
+ retLines[c] = malloc_s(lineLen + 1);
+ memcpy(retLines[c], label, lineLen);
+ retLines[c][lineLen] = '\0';
+
+ /* Advance the label */
+ label = nextLine + 2;
+ }
+ else
+ {
+ /* Duplicate the final line */
+ retLines[c] = strdup_s(label);
+ label = NULL;
+ }
+
+ /* Now split the line as required to wrap into the space available */
+ do
+ {
+ /* Check if more storage maybe needed */
+ if (c + 2 >= nAllocLines)
+ {
+ nAllocLines += 8;
+ retLines = realloc_s(retLines, sizeof(char *) * nAllocLines);
+ }
+
+ retLines[c + 1] = splitStringToWidth(ctx, retLines[c], width);
+ c++;
+ }
+ while (retLines[c] != NULL);
+ }
+
+ /* Return the array of lines and the count */
+ *lines = retLines;
+
+ return c;
+}
+
+
+
+/** Compute the output canvas size required for some MSC.
+ * This computes the dimensions for the canvas as well as the height for each
+ * row.
+ *
+ * \param[in] m The MSC to analyse.
+ * \param[in,out] w Pointer to be filled with the output width.
+ * \param[in,out] h Pointer to be filled with the output height.
+ * \returns An array giving the height of each row.
+ */
+static RowInfo *computeCanvasSize(Context *ctx,
+ Msc m,
+ unsigned int *w,
+ unsigned int *h)
+{
+ const unsigned int rowCount = MscGetNumArcs(m) - MscGetNumParallelArcs(m);
+ const unsigned int textHeight = ctx->drw.textHeight(&ctx->drw);
+ RowInfo *rowHeight;
+ unsigned int nextYmin, ymin, ymax, yskipmax, row;
+
+ /* Allocate storage for the height of each row */
+ rowHeight = zalloc_s(sizeof(RowInfo) * rowCount);
+ row = 0;
+
+ nextYmin = ymin = ctx->opts.entityHeadGap;
+ yskipmax = 0;
+
+ ymax = 0;
+ MscResetArcIterator(m);
+ do
+ {
+ const MscArcType arcType = MscGetCurrentArcType(m);
+ const int arcGradient = isBoxArc(arcType) ? 0 : getArcGradient(ctx, m, NULL, 0);
+ char **arcLabelLines = NULL;
+ unsigned int arcLabelLineCount = 0;
+ int startCol = -1, endCol = -1;
+
+ if (arcType == MSC_ARC_PARALLEL)
+ {
+ assert(row > 0);
+
+ row--;
+
+ ymin = rowHeight[row].ymin;
+ nextYmin = rowHeight[row].ymax;
+ }
+ else
+ {
+ /* Get the entity indices */
+ if (arcType != MSC_ARC_DISCO && arcType != MSC_ARC_DIVIDER && arcType != MSC_ARC_SPACE)
+ {
+ startCol = MscGetEntityIndex(m, MscGetCurrentArcSource(m));
+ endCol = MscGetEntityIndex(m, MscGetCurrentArcDest(m));
+ }
+ else
+ {
+ /* Discontinuity or parallel arc spans whole chart */
+ startCol = 0;
+ endCol = MscGetNumEntities(m) - 1;
+ }
+
+ /* Work out how the label fits the gap between entities */
+ arcLabelLineCount = computeLabelLines(ctx, m, arcType, &arcLabelLines,
+ MscGetCurrentArcAttrib(m, MSC_ATTR_LABEL),
+ startCol, endCol);
+
+ assert(row < rowCount);
+
+ /* Update the max line count for the row */
+ if (arcLabelLineCount > rowHeight[row].maxTextLines)
+ {
+ rowHeight[row].maxTextLines = arcLabelLineCount;
+ }
+
+ freeLabelLines(arcLabelLineCount, arcLabelLines);
+
+ /* Compute the height of this arc */
+ if (arcType != MSC_ARC_DISCO && arcType != MSC_ARC_DIVIDER && arcType != MSC_ARC_SPACE)
+ {
+ ymax = ymin + ctx->opts.arcSpacing;
+ ymax += (M_Max(rowHeight[row].maxTextLines, 2) * textHeight);
+ }
+ else
+ {
+ ymax = ymin + ctx->opts.arcSpacing;
+ ymax += (M_Max(rowHeight[row].maxTextLines, 1) * textHeight);
+ }
+
+ /* Update next potential row start */
+ if (ymax > nextYmin)
+ {
+ nextYmin = ymax;
+ }
+
+ /* Compute the dimensions for the completed row */
+ rowHeight[row].ymin = ymin;
+ rowHeight[row].ymax = nextYmin - ctx->opts.arcSpacing;
+ rowHeight[row].arcliney = rowHeight[row].ymin + (rowHeight[row].ymax - rowHeight[row].ymin) / 2;
+ row++;
+
+ /* Start new row */
+ ymin = nextYmin;
+ }
+
+ /* Keep a track of where the gradient may cause the graph to end */
+ if (ymax + arcGradient > ymax)
+ {
+ yskipmax = ymax + arcGradient;
+ }
+
+ }
+ while (MscNextArc(m));
+
+ if (ymax < yskipmax)
+ ymax = yskipmax;
+
+ /* Set the return values */
+ *w = MscGetNumEntities(m) * ctx->opts.entitySpacing;
+ *h = ymax;
+
+ return rowHeight;
+}
+
+/** Add a point to the output imagemap.
+ * If \a ismap and \a url are non-NULL, this function will add a rectangle
+ * to the imagemap according to the parameters passed.
+ *
+ * \param ismap The file to which the imagemap should be rendered.
+ * \param url The URL to which the imagemap area should link.
+ * \param x1 The x coordinate for the upper left point.
+ * \param y2 The y coordinate for the upper left point.
+ * \param x2 The x coordinate for the lower right point.
+ * \param y2 The y coordinate for the lower right point.
+ */
+static void ismapRect(FILE *ismap,
+ const char *url,
+ unsigned int x1,
+ unsigned int y1,
+ unsigned int x2,
+ unsigned int y2)
+{
+ if(ismap && url)
+ {
+ assert(x1 <= x2); assert(y1 <= y2);
+
+ fprintf(ismap,
+ "rect %s %d,%d %d,%d\n",
+ url,
+ x1, y1,
+ x2, y2);
+ }
+#if 0
+ /* For debug render a cross onto the output */
+ drw.line(&drw, x1, y1, x2, y2);
+ drw.line(&drw, x2, y1, x1, y2);
+#endif
+}
+
+
+/** Get some line from a string containing '\n' delimiters.
+ * Given a string that contains literal '\n' delimiters, return a subset in
+ * a passed buffer that gives the nth line.
+ *
+ * \param[in] string The string to parse.
+ * \param[in] line The line number to return from the string, which should
+ * count from 0.
+ * \param[in] out Pointer to a buffer to fill with line data.
+ * \param[in] outLen The length of the buffer pointed to by \a out, in bytes.
+ * \returns A pointer to \a out.
+ */
+static char *getLine(const char *string,
+ unsigned int line,
+ char *const out,
+ const unsigned int outLen)
+{
+ const char *lineStart, *lineEnd;
+ unsigned int lineLen;
+
+ /* Setup for the loop */
+ lineEnd = NULL;
+ line++;
+
+ do
+ {
+ /* Check if this is the first or a repeat iteration */
+ if(lineEnd)
+ {
+ lineStart = lineEnd + 2;
+ }
+ else
+ {
+ lineStart = string;
+ }
+
+ /* Search for next delimited */
+ lineEnd = strstr(lineStart, "\\n");
+
+ line--;
+ }
+ while (line > 0 && lineEnd != NULL);
+
+ /* Determine the length of the line */
+ if(lineEnd != NULL)
+ {
+ lineLen = (unsigned int)(lineEnd - lineStart);
+ }
+ else
+ {
+ lineLen = (unsigned int)(strlen(string) - (lineStart - string));
+ }
+
+ /* Clamp the length to the buffer */
+ if(lineLen > outLen - 1)
+ {
+ lineLen = outLen - 1;
+ }
+
+ /* Copy desired characters */
+ memcpy(out, lineStart, lineLen);
+
+ /* NULL terminate */
+ out[lineLen] = '\0';
+
+ return out;
+}
+
+
+/** Render some entity text.
+ * Draw the text for some entity.
+ * \param ismap If not \a NULL, write an ismap description here.
+ * \param x The x position at which the entity text should be centered.
+ * \param y The y position where the text should be placed.
+ * \param entLabel The label to render, which maybe \a NULL in which case
+ * no output is produced.
+ * \param entUrl The URL for rendering the label as a hyperlink. This
+ * maybe \a NULL if not required.
+ * \param entId The text identifier for the arc.
+ * \param entIdUrl The URL for rendering the test identifier as a hyperlink.
+ * This maybe \a NULL if not required.
+ * \param entColour The text colour name or specification for the entity text.
+ * If NULL, use default colouring scheme.
+ * \param entBgColour The text background colour name or specification for the
+ * entity text. If NULL, use default colouring scheme.
+ */
+static void entityText(Context *ctx,
+ 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)
+{
+ if(entLabel)
+ {
+ const unsigned int lines = countLines(entLabel);
+ unsigned int l;
+ char lineBuffer[1024];
+
+ /* Adjust y to be above the writing line */
+ y -= ctx->drw.textHeight(&ctx->drw) * (lines - 1);
+
+ for (l = 0; l < lines - 1; l++)
+ {
+ char *lineLabel = getLine(entLabel, l, lineBuffer, sizeof(lineBuffer));
+ unsigned int width = ctx->drw.textWidth(&ctx->drw, lineLabel);
+
+ /* Push text down one line */
+ y += ctx->drw.textHeight(&ctx->drw);
+
+ /* Check if a URL is associated */
+ if(entUrl)
+ {
+ /* If no explicit colour has been set, make URLS blue */
+ ctx->drw.setPen(&ctx->drw, ADRAW_COL_BLUE);
+
+ /* Image map output */
+ ismapRect(ismap,
+ entUrl,
+ x - (width / 2), y - ctx->drw.textHeight(&ctx->drw),
+ x + (width / 2), y);
+ }
+
+ /* Set to the explicit colours if directed */
+ if(entColour != NULL)
+ {
+ ctx->drw.setPen(&ctx->drw, ADrawGetColour(entColour));
+ }
+
+ if(entBgColour != NULL)
+ {
+ ctx->drw.setBgPen(&ctx->drw, ADrawGetColour(entBgColour));
+ }
+
+ /* Render text and restore pen */
+ ctx->drw.textC (&ctx->drw, x, y, lineLabel);
+ ctx->drw.setPen(&ctx->drw, ADRAW_COL_BLACK);
+ ctx->drw.setBgPen(&ctx->drw, ADRAW_COL_WHITE);
+
+ /* Render the Id of the title, if specified and for first line only */
+ if(entId && l == 0)
+ {
+ unsigned int idwidth;
+ int idx, idy;
+
+ idy = y - ctx->drw.textHeight(&ctx->drw);
+ idx = x + (width / 2);
+
+ ctx->drw.setFontSize(&ctx->drw, ADRAW_FONT_TINY);
+
+ idwidth = ctx->drw.textWidth(&ctx->drw, entId);
+ idy += (ctx->drw.textHeight(&ctx->drw) + 1) / 2;
+
+ if(entIdUrl)
+ {
+ ctx->drw.setPen(&ctx->drw, ADRAW_COL_BLUE);
+ ctx->drw.textR (&ctx->drw, idx, idy, entId);
+ ctx->drw.setPen(&ctx->drw, ADRAW_COL_BLACK);
+
+ /* Image map output */
+ ismapRect(ismap,
+ entIdUrl,
+ idx, idy - ctx->drw.textHeight(&ctx->drw),
+ idx + idwidth, idy);
+ }
+ else
+ {
+ ctx->drw.textR(&ctx->drw, idx, idy, entId);
+ }
+
+ ctx->drw.setFontSize(&ctx->drw, ADRAW_FONT_SMALL);
+ }
+ }
+ }
+}
+
+/** Draw vertical lines stemming from entities.
+ * This function will draw a single segment of the vertical line that
+ * drops from an entity.
+ *
+ * \param m The \a Msc for which the lines are drawn
+ * \param ymin Top of the row.
+ * \param ymax Bottom of the row.
+ * \param dotted If #TRUE, produce a dotted line, otherwise solid.
+ * \param colourRefs Colour references for each entity.
+ */
+static void entityLines(Context *ctx,
+ Msc m,
+ const unsigned int ymin,
+ const unsigned int ymax,
+ Boolean dotted,
+ const ADrawColour *colourRefs)
+{
+ unsigned int t;
+
+ for (t = 0; t < MscGetNumEntities(m); t++)
+ {
+ unsigned int x = (ctx->opts.entitySpacing / 2) + (ctx->opts.entitySpacing * t);
+
+ ctx->drw.setPen(&ctx->drw, colourRefs[t]);
+
+ if(dotted)
+ {
+ ctx->drw.dottedLine(&ctx->drw, x, ymin, x, ymax);
+ }
+ else
+ {
+ ctx->drw.line(&ctx->drw, x, ymin, x, ymax);
+ }
+ }
+
+ ctx->drw.setPen(&ctx->drw, ADRAW_COL_BLACK);
+
+}
+
+/** Draw an arrow pointing to the right.
+ * \param x The x co-ordinate for the end point for the arrow head.
+ * \param y The y co-ordinate for the end point for the arrow head.
+ * \param type The arc type, which controls the format of the arrow head.
+ */
+static void arrowR(Context *ctx,
+ unsigned int x,
+ unsigned int y,
+ MscArcType type)
+{
+ switch(type)
+ {
+ case MSC_ARC_SIGNAL: /* Unfilled half */
+ ctx->drw.line(&ctx->drw,
+ x, y,
+ x - ctx->opts.arrowWidth, y + ctx->opts.arrowHeight);
+ break;
+
+ case MSC_ARC_DOUBLE:
+ case MSC_ARC_METHOD: /* Filled */
+ case MSC_ARC_RETVAL: /* Filled, dotted arc (not rendered here) */
+ ctx->drw.filledTriangle(&ctx->drw,
+ x, y,
+ x - ctx->opts.arrowWidth, y + ctx->opts.arrowHeight,
+ x - ctx->opts.arrowWidth, y - ctx->opts.arrowHeight);
+ break;
+
+ case MSC_ARC_CALLBACK: /* Non-filled */
+ ctx->drw.line(&ctx->drw,
+ x, y,
+ x - ctx->opts.arrowWidth, y + ctx->opts.arrowHeight);
+ ctx->drw.line(&ctx->drw,
+ x - ctx->opts.arrowWidth, y - ctx->opts.arrowHeight,
+ x, y);
+ break;
+
+ default:
+ assert(0);
+ break;
+ }
+}
+
+
+/** Draw an arrow pointing to the left.
+ * \param x The x co-ordinate for the end point for the arrow head.
+ * \param y The y co-ordinate for the end point for the arrow head.
+ * \param type The arc type, which controls the format of the arrow head.
+ */
+static void arrowL(Context *ctx,
+ unsigned int x,
+ unsigned int y,
+ MscArcType type)
+{
+ switch(type)
+ {
+ case MSC_ARC_SIGNAL: /* Unfilled half */
+ ctx->drw.line(&ctx->drw,
+ x, y,
+ x + ctx->opts.arrowWidth, y + ctx->opts.arrowHeight);
+ break;
+
+ case MSC_ARC_DOUBLE:
+ case MSC_ARC_METHOD: /* Filled */
+ case MSC_ARC_RETVAL: /* Filled, dotted arc (not rendered here) */
+ ctx->drw.filledTriangle(&ctx->drw,
+ x, y,
+ x + ctx->opts.arrowWidth, y + ctx->opts.arrowHeight,
+ x + ctx->opts.arrowWidth, y - ctx->opts.arrowHeight);
+ break;
+
+ case MSC_ARC_CALLBACK: /* Non-filled */
+ ctx->drw.line(&ctx->drw,
+ x, y,
+ x + ctx->opts.arrowWidth, y + ctx->opts.arrowHeight);
+ ctx->drw.line(&ctx->drw,
+ x, y,
+ x + ctx->opts.arrowWidth, y - ctx->opts.arrowHeight);
+ break;
+
+ default:
+ assert(0);
+ break;
+ }
+}
+
+/** Draw vertical lines and boxes stemming from entities.
+ * \param ymin Top of the row.
+ * \param ymax Bottom of the row.
+ * \param boxStart Column in which the box starts.
+ * \param boxEnd Column in which the box ends.
+ * \param boxType The type of box to draw, MSC_ARC_BOX, MSC_ARC_RBOX etc.
+ * \param lineColour Colour of the lines to use for rendering the box.
+ * \param bgColour Background colour for rendering the box.
+ */
+static void arcBox(Context *ctx,
+ unsigned int ymin,
+ unsigned int ymax,
+ unsigned int boxStart,
+ unsigned int boxEnd,
+ MscArcType boxType,
+ const char *lineColour,
+ const char *bgColour)
+{
+ unsigned int t;
+
+ /* Ensure the start is less than or equal to the end */
+ if(boxStart > boxEnd)
+ {
+ t = boxEnd;
+ boxEnd = boxStart;
+ boxStart = t;
+ }
+
+ /* Now draw the box */
+ unsigned int x1 = (ctx->opts.entitySpacing * boxStart) + ctx->opts.boxSpacing;
+ unsigned int x2 = ctx->opts.entitySpacing * (boxEnd + 1) - ctx->opts.boxSpacing;
+ unsigned int ymid = (ymin + ymax) / 2;
+
+ /* Set colour for the background area */
+ if(bgColour != NULL)
+ {
+ ctx->drw.setPen(&ctx->drw, ADrawGetColour(bgColour));
+ }
+ else
+ {
+ ctx->drw.setPen(&ctx->drw, ADRAW_COL_WHITE);
+ }
+
+ /* Draw the background to overwrite the entity lines */
+ switch(boxType)
+ {
+ case MSC_ARC_BOX:
+ ctx->drw.filledRectangle(&ctx->drw, x1, ymin, x2, ymax);
+ break;
+
+ case MSC_ARC_RBOX:
+ ctx->drw.filledRectangle(&ctx->drw, x1 + ctx->opts.rboxArc, ymin, x2 - ctx->opts.rboxArc, ymax);
+ ctx->drw.filledRectangle(&ctx->drw, x1, ymin + ctx->opts.rboxArc, x2, ymax - ctx->opts.rboxArc);
+ ctx->drw.filledCircle(&ctx->drw, x1 + ctx->opts.rboxArc, ymin + ctx->opts.rboxArc, ctx->opts.rboxArc);
+ ctx->drw.filledCircle(&ctx->drw, x2 - ctx->opts.rboxArc, ymin + ctx->opts.rboxArc, ctx->opts.rboxArc);
+ ctx->drw.filledCircle(&ctx->drw, x1 + ctx->opts.rboxArc, ymax - ctx->opts.rboxArc, ctx->opts.rboxArc);
+ ctx->drw.filledCircle(&ctx->drw, x2 - ctx->opts.rboxArc, ymax - ctx->opts.rboxArc, ctx->opts.rboxArc);
+ break;
+
+ case MSC_ARC_NOTE:
+ ctx->drw.filledRectangle(&ctx->drw, x1, ymin, x2 - ctx->opts.noteCorner, ymax);
+ ctx->drw.filledRectangle(&ctx->drw, x1, ymin + ctx->opts.noteCorner, x2, ymax);
+ ctx->drw.filledTriangle(&ctx->drw, x2 - ctx->opts.noteCorner, ymin,
+ x2, ymin + ctx->opts.noteCorner,
+ x2 - ctx->opts.noteCorner, ymin + ctx->opts.noteCorner);
+ break;
+
+ case MSC_ARC_ABOX:
+ ctx->drw.filledRectangle(&ctx->drw, x1 + ctx->opts.aboxSlope, ymin, x2 - ctx->opts.aboxSlope, ymax);
+ ctx->drw.filledTriangle(&ctx->drw, x1 + ctx->opts.aboxSlope, ymin,
+ x1 + ctx->opts.aboxSlope, ymax,
+ x1, ymid);
+ ctx->drw.filledTriangle(&ctx->drw, x2 - ctx->opts.aboxSlope, ymin,
+ x2 - ctx->opts.aboxSlope, ymax,
+ x2, ymid);
+ break;
+
+ default:
+ assert(0);
+ }
+
+ /* Setup the colour for rendering the boxes */
+ if(lineColour)
+ {
+ ctx->drw.setPen(&ctx->drw, ADrawGetColour(lineColour));
+ }
+ else
+ {
+ ctx->drw.setPen(&ctx->drw, ADRAW_COL_BLACK);
+ }
+
+ /* Draw the outline */
+ switch(boxType)
+ {
+ case MSC_ARC_BOX:
+ ctx->drw.line(&ctx->drw, x1, ymin, x2, ymin);
+ ctx->drw.line(&ctx->drw, x1, ymax, x2, ymax);
+ ctx->drw.line(&ctx->drw, x1, ymin, x1, ymax);
+ ctx->drw.line(&ctx->drw, x2, ymin, x2, ymax);
+ break;
+
+ case MSC_ARC_NOTE:
+ ctx->drw.line(&ctx->drw, x1, ymin, x2 - ctx->opts.noteCorner, ymin);
+ ctx->drw.line(&ctx->drw, x1, ymax, x2, ymax);
+ ctx->drw.line(&ctx->drw, x1, ymin, x1, ymax);
+ ctx->drw.line(&ctx->drw, x2, ymin + ctx->opts.noteCorner, x2, ymax);
+ ctx->drw.line(&ctx->drw, x2 - ctx->opts.noteCorner, ymin,
+ x2, ymin + ctx->opts.noteCorner);
+ ctx->drw.line(&ctx->drw, x2 - ctx->opts.noteCorner, ymin,
+ x2 - ctx->opts.noteCorner, ymin + ctx->opts.noteCorner);
+ ctx->drw.line(&ctx->drw, x2, ymin + ctx->opts.noteCorner,
+ x2 - ctx->opts.noteCorner, ymin + ctx->opts.noteCorner);
+ break;
+
+ case MSC_ARC_RBOX:
+ ctx->drw.line(&ctx->drw, x1 + ctx->opts.rboxArc, ymin, x2 - ctx->opts.rboxArc, ymin);
+ ctx->drw.line(&ctx->drw, x1 + ctx->opts.rboxArc, ymax, x2 - ctx->opts.rboxArc, ymax);
+ ctx->drw.line(&ctx->drw, x1, ymin + ctx->opts.rboxArc, x1, ymax - ctx->opts.rboxArc);
+ ctx->drw.line(&ctx->drw, x2, ymin + ctx->opts.rboxArc, x2, ymax - ctx->opts.rboxArc);
+
+ ctx->drw.arc(&ctx->drw, x1 + ctx->opts.rboxArc,
+ ymin + ctx->opts.rboxArc, ctx->opts.rboxArc * 2, ctx->opts.rboxArc * 2,
+ 180, 270);
+ ctx->drw.arc(&ctx->drw, x2 - ctx->opts.rboxArc,
+ ymin + ctx->opts.rboxArc, ctx->opts.rboxArc * 2, ctx->opts.rboxArc * 2,
+ 270, 0);
+ ctx->drw.arc(&ctx->drw, x2 - ctx->opts.rboxArc,
+ ymax - ctx->opts.rboxArc, ctx->opts.rboxArc * 2, ctx->opts.rboxArc * 2,
+ 0, 90);
+ ctx->drw.arc(&ctx->drw, x1 + ctx->opts.rboxArc,
+ ymax - ctx->opts.rboxArc, ctx->opts.rboxArc * 2, ctx->opts.rboxArc * 2,
+ 90, 180);
+ break;
+
+ case MSC_ARC_ABOX:
+ ctx->drw.line(&ctx->drw, x1 + ctx->opts.aboxSlope, ymin, x2 - ctx->opts.aboxSlope, ymin);
+ ctx->drw.line(&ctx->drw, x1 + ctx->opts.aboxSlope, ymax, x2 - ctx->opts.aboxSlope, ymax);
+ ctx->drw.line(&ctx->drw, x1 + ctx->opts.aboxSlope, ymin, x1, ymid);
+ ctx->drw.line(&ctx->drw, x1, ymid, x1 + ctx->opts.aboxSlope, ymax);
+ ctx->drw.line(&ctx->drw, x2 - ctx->opts.aboxSlope, ymin, x2, ymid);
+ ctx->drw.line(&ctx->drw, x2, ymid, x2 - ctx->opts.aboxSlope, ymax);
+ break;
+
+ default:
+ assert(0);
+ }
+
+ /* Restore the pen colour if needed */
+ if(lineColour)
+ {
+ ctx->drw.setPen(&ctx->drw, ADRAW_COL_BLACK);
+ }
+}
+
+
+/** Render text on an arc.
+ * Draw the text on some arc.
+ * \param m The Msc for which the text is being rendered.
+ * \param ismap If not \a NULL, write an ismap description here.
+ * \param outwidth Width of the output image.
+ * \param ymid Co-ordinate of the row on which the text should be aligned.
+ * \param startCol The column at which the arc being labelled starts.
+ * \param endCol The column at which the arc being labelled ends.
+ * \param arcLabelLineCount Count of lines of text in arcLabelLines.
+ * \param arcLabelLines Array of lines of text from 0 to arcLabelLineCount - 1.
+ * \param arcUrl The URL for rendering the label as a hyperlink. This
+ * maybe \a NULL if not required.
+ * \param arcId The text identifier for the arc.
+ * \param arcIdUrl The URL for rendering the test identifier as a hyperlink.
+ * This maybe \a NULL if not required.
+ * \param arcTextColour Colour for the arc text, or NULL to use default.
+ * \param arcTextColour Colour for the arc text background, or NULL to use default.
+ * \param arcType The type of arc, used to control output semantics.
+ */
+static void arcText(Context *ctx,
+ 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)
+{
+ unsigned int l;
+ unsigned int y;
+
+ /* A single line of normal text is above the midline */
+ if(arcLabelLineCount == 1 && !isBoxArc(arcType) &&
+ arcType != MSC_ARC_DISCO && arcType != MSC_ARC_DIVIDER &&
+ arcType != MSC_ARC_SPACE)
+ {
+ y = ymid + (ygradient / 2) - ctx->drw.textHeight(&ctx->drw);
+ }
+ else /* Text is vertically centered on the midline */
+ {
+ int yoff = ygradient - (ctx->drw.textHeight(&ctx->drw) * arcLabelLineCount);
+ y = ymid + (yoff / 2);
+ }
+
+ for (l = 0; l < arcLabelLineCount; l++)
+ {
+ const char *lineLabel = arcLabelLines[l];
+ unsigned int width = ctx->drw.textWidth(&ctx->drw, lineLabel);
+ int x = ((startCol + endCol + 1) * ctx->opts.entitySpacing) / 2;
+
+ y += ctx->drw.textHeight(&ctx->drw);
+
+ if(startCol != endCol || isBoxArc(arcType))
+ {
+ /* Produce central aligned text */
+ x -= width / 2;
+ }
+ else if(startCol < (MscGetNumEntities(m) / 2))
+ {
+ /* Form text to the right */
+ x += ctx->opts.textHGapPre;
+ }
+ else
+ {
+ /* Form text to the left */
+ x -= width + ctx->opts.textHGapPost;
+ }
+
+ /* Clip against edges of image */
+ if(x + width > outwidth)
+ {
+ x = outwidth - width;
+ }
+
+ if(x < 0)
+ {
+ x = 0;
+ }
+
+ /* Check if a URL is associated */
+ if(arcUrl)
+ {
+ /* Default to blue */
+ ctx->drw.setPen(&ctx->drw, ADRAW_COL_BLUE);
+
+ /* Image map output */
+ ismapRect(ismap,
+ arcUrl,
+ x, y - ctx->drw.textHeight(&ctx->drw),
+ x + width, y);
+ }
+
+
+ /* Set to the explicit colours if directed */
+ if(arcTextColour != NULL)
+ {
+ ctx->drw.setPen(&ctx->drw, ADrawGetColour(arcTextColour));
+ }
+
+ if(arcTextBgColour != NULL)
+ {
+ ctx->drw.setBgPen(&ctx->drw, ADrawGetColour(arcTextBgColour));
+ }
+
+ /* Render text and restore pen */
+ ctx->drw.textR (&ctx->drw, x, y, lineLabel);
+ ctx->drw.setPen(&ctx->drw, ADRAW_COL_BLACK);
+ ctx->drw.setBgPen(&ctx->drw, ADRAW_COL_WHITE);
+
+ /* Render the Id of the arc, if specified and for the first line*/
+ if(arcId && l == 0)
+ {
+ unsigned int idwidth;
+ int idx, idy;
+
+ idy = y - ctx->drw.textHeight(&ctx->drw);
+ idx = x + width;
+
+ ctx->drw.setFontSize(&ctx->drw, ADRAW_FONT_TINY);
+
+ idwidth = ctx->drw.textWidth(&ctx->drw, arcId);
+ idy += (ctx->drw.textHeight(&ctx->drw) + 1) / 2;
+
+ if(arcIdUrl)
+ {
+ ctx->drw.setPen(&ctx->drw, ADRAW_COL_BLUE);
+
+ /* Image map output */
+ ismapRect(ismap,
+ arcIdUrl,
+ idx, idy - ctx->drw.textHeight(&ctx->drw),
+ idx + idwidth, idy);
+ }
+
+ /* Render text and restore pen and font */
+ ctx->drw.textR (&ctx->drw, idx, idy, arcId);
+ ctx->drw.setPen(&ctx->drw, ADRAW_COL_BLACK);
+ ctx->drw.setFontSize(&ctx->drw, ADRAW_FONT_SMALL);
+ }
+ }
+}
+
+
+/** Render the line and arrow head for some arc.
+ * This will draw the arc line and arrow head between two columns,
+ * noting that if the start and end column are the same, an arc is
+ * rendered.
+ * \param m The Msc for which the text is being rendered.
+ * \param ymin Top of row.
+ * \param ymax Bottom of row.
+ * \param ygradient The gradient of the arc which alters the y position a
+ * the ending column.
+ * \param startCol Starting column for the arc.
+ * \param endCol Column at which the arc terminates.
+ * \param hasArrows If true, draw arc arrows, otherwise omit them.
+ * \param hasBiArrows If true, has arrows in both directions.
+ * \param arcType The type of the arc, which dictates its rendered style.
+ */
+static void arcLine(Context *ctx,
+ 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)
+{
+ const unsigned int sx = (startCol * ctx->opts.entitySpacing) +
+ (ctx->opts.entitySpacing / 2);
+ const unsigned int dx = (endCol * ctx->opts.entitySpacing) +
+ (ctx->opts.entitySpacing / 2);
+
+ /* Check if an explicit line colour is requested */
+ if(arcLineCol != NULL)
+ {
+ ctx->drw.setPen(&ctx->drw, ADrawGetColour(arcLineCol));
+ }
+
+ if(startCol != endCol)
+ {
+ /* Draw the line */
+ if(arcType == MSC_ARC_RETVAL)
+ {
+ ctx->drw.dottedLine(&ctx->drw, sx, y, dx, y + ygradient);
+ }
+ else if(arcType == MSC_ARC_DOUBLE)
+ {
+ ctx->drw.line(&ctx->drw, sx, y - 1, dx, y - 1 + ygradient);
+ ctx->drw.line(&ctx->drw, sx, y + 1, dx, y + 1 + ygradient);
+ }
+ else if(arcType == MSC_ARC_LOSS)
+ {
+ signed int span = dx - sx;
+ unsigned int mx = sx + (span / 4) * 3;
+
+ ctx->drw.line(&ctx->drw, sx, y, mx, y + ygradient);
+ hasArrows = 0;
+
+ ctx->drw.line(&ctx->drw, mx - 4, y + ygradient - 4, mx + 4, y + ygradient + 4);
+ ctx->drw.line(&ctx->drw, mx + 4, y + ygradient - 4, mx - 4, y + ygradient + 4);
+ }
+ else
+ {
+ ctx->drw.line(&ctx->drw, sx, y, dx, y + ygradient);
+ }
+
+ /* Now the arrow heads */
+ if(hasArrows)
+ {
+ if(startCol < endCol)
+ {
+ arrowR(ctx, dx, y + ygradient, arcType);
+ }
+ else
+ {
+ arrowL(ctx, dx, y + ygradient, arcType);
+ }
+
+ if(hasBiArrows)
+ {
+ if(startCol < endCol)
+ {
+ arrowL(ctx, sx, y + ygradient, arcType);
+ }
+ else
+ {
+ arrowR(ctx, sx, y + ygradient, arcType);
+ }
+ }
+ }
+ }
+ else if(startCol < (MscGetNumEntities(m) / 2))
+ {
+ /* Arc looping to the left */
+ if(arcType == MSC_ARC_RETVAL)
+ {
+ ctx->drw.dottedArc(&ctx->drw,
+ sx, y,
+ ctx->opts.entitySpacing,
+ ctx->opts.loopArcHeight,
+ 90,
+ 270);
+ }
+ else if(arcType == MSC_ARC_DOUBLE)
+ {
+ ctx->drw.arc(&ctx->drw,
+ sx, y - 1,
+ ctx->opts.entitySpacing,
+ ctx->opts.loopArcHeight,
+ 90,
+ 270);
+ ctx->drw.arc(&ctx->drw,
+ sx, y + 1,
+ ctx->opts.entitySpacing,
+ ctx->opts.loopArcHeight,
+ 90,
+ 270);
+ }
+ else if(arcType == MSC_ARC_LOSS)
+ {
+ unsigned int px, py;
+
+ ctx->drw.arc(&ctx->drw,
+ sx, y - 1,
+ ctx->opts.entitySpacing - 8,
+ ctx->opts.loopArcHeight,
+ 180 - 45,
+ 270);
+
+ hasArrows = FALSE;
+
+ /* Get co-ordinates of the arc end-point */
+ ADrawComputeArcPoint((float)sx, (float)(y - 1), (float)(ctx->opts.entitySpacing - 8),
+ (float)ctx->opts.loopArcHeight, 180.0f - 45.0f,
+ &px, &py);
+
+ /* Draw a cross */
+ ctx->drw.line(&ctx->drw, px - 4, py - 4, px + 4, py + 4);
+ ctx->drw.line(&ctx->drw, px + 4, py - 4, px - 4, py + 4);
+ }
+ else
+ {
+ ctx->drw.arc(&ctx->drw,
+ sx, y,
+ ctx->opts.entitySpacing - 4,
+ ctx->opts.loopArcHeight,
+ 90,
+ 270);
+ }
+
+ if(hasArrows)
+ {
+ arrowR(ctx, dx, y + (ctx->opts.loopArcHeight / 2), arcType);
+ }
+ }
+ else
+ {
+ /* Arc looping to right */
+ if(arcType == MSC_ARC_RETVAL)
+ {
+ ctx->drw.dottedArc(&ctx->drw,
+ sx, y,
+ ctx->opts.entitySpacing,
+ ctx->opts.loopArcHeight,
+ 270,
+ 90);
+ }
+ else if(arcType == MSC_ARC_DOUBLE)
+ {
+ ctx->drw.arc(&ctx->drw,
+ sx, y - 1,
+ ctx->opts.entitySpacing,
+ ctx->opts.loopArcHeight,
+ 270,
+ 90);
+ ctx->drw.arc(&ctx->drw,
+ sx, y + 1,
+ ctx->opts.entitySpacing,
+ ctx->opts.loopArcHeight,
+ 270,
+ 90);
+ }
+ else if(arcType == MSC_ARC_LOSS)
+ {
+ unsigned int px, py;
+
+ ctx->drw.arc(&ctx->drw,
+ sx, y - 1,
+ ctx->opts.entitySpacing - 8,
+ ctx->opts.loopArcHeight,
+ 270,
+ 45);
+
+ hasArrows = FALSE;
+
+ /* Get co-ordinates of the arc end-point */
+ ADrawComputeArcPoint((float)sx, (float)(y - 1),
+ (float)(ctx->opts.entitySpacing - 8),
+ (float)ctx->opts.loopArcHeight, 45.0f,
+ &px, &py);
+
+ /* Draw a cross */
+ ctx->drw.line(&ctx->drw, px - 4, py - 4, px + 4, py + 4);
+ ctx->drw.line(&ctx->drw, px + 4, py - 4, px - 4, py + 4);
+ }
+ else
+ {
+ ctx->drw.arc(&ctx->drw,
+ sx, y,
+ ctx->opts.entitySpacing,
+ ctx->opts.loopArcHeight,
+ 270,
+ 90);
+ }
+
+ if(hasArrows)
+ {
+ arrowL(ctx, dx, y + (ctx->opts.loopArcHeight / 2), arcType);
+ }
+ }
+
+ /* Restore pen if needed */
+ if(arcLineCol != NULL)
+ {
+ ctx->drw.setPen(&ctx->drw, ADRAW_COL_BLACK);
+ }
+}
+
+
+
+/* Perform post-parsing validation of the MSC.
+ * This checks the passed MSC for various rules which can't easily be tested
+ * at parse time.
+ */
+static Boolean checkMsc(Msc m)
+{
+ /* Check all arc entities are known */
+ MscResetArcIterator(m);
+ do
+ {
+ const MscArcType arcType = MscGetCurrentArcType(m);
+
+ if (arcType != MSC_ARC_PARALLEL && arcType != MSC_ARC_DISCO &&
+ arcType != MSC_ARC_DIVIDER && arcType != MSC_ARC_SPACE)
+ {
+ const char *src = MscGetCurrentArcSource(m);
+ const char *dst = MscGetCurrentArcDest(m);
+ const int startCol = MscGetEntityIndex(m, src);
+ const int endCol = MscGetEntityIndex(m, dst);
+
+ /* Check the start column is valid */
+ if (startCol == -1)
+ {
+ fprintf(stderr, "Error detected at line %u: Unknown source entity '%s'.\n",
+ MscGetCurrentArcInputLine(m), src);
+ return FALSE;
+ }
+
+ if (endCol == -1 && !isBroadcastArc(dst))
+ {
+ fprintf(stderr, "Error detected at line %u: Unknown destination entity '%s'.\n",
+ MscGetCurrentArcInputLine(m), dst);
+ return FALSE;
+ }
+ }
+ }
+ while (MscNextArc(m));
+
+ return TRUE;
+}
+
+
+int mscgen_generate(const char *inputFile,
+ const char *outputFile,
+ mscgen_format_t format
+ )
+{
+ //printf("mscgen_generate(in=%s,out=%s,format=%d)\n",inputFile,outputFile,format);
+ ADrawOutputType outType;
+ ADrawColour *entColourRef = NULL;
+ const char *outImage = outputFile;
+ Msc m;
+ float f;
+ unsigned int w, h, row, col;
+ RowInfo *rowInfo = NULL;
+ Boolean addLines;
+ FILE *ismap = NULL;
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ const char *nullFile = "nul";
+#else
+ const char *nullFile = "/dev/null";
+#endif
+ Context ctx;
+ ctx.opts = gDefaultOpts;
+
+ switch (format)
+ {
+ case mscgen_format_png:
+ outType = ADRAW_FMT_PNG;
+ break;
+ case mscgen_format_eps:
+ outType = ADRAW_FMT_EPS;
+ break;
+ case mscgen_format_svg:
+ outType = ADRAW_FMT_SVG;
+ break;
+ case mscgen_format_pngmap:
+ outType = ADRAW_FMT_PNG;
+ outImage = nullFile;
+ break;
+ case mscgen_format_svgmap:
+ outType = ADRAW_FMT_SVG;
+ outImage = nullFile;
+ break;
+ }
+ /* open file */
+ FILE *in = fopen(inputFile, "r");
+ if (!in)
+ {
+ fprintf(stderr, "Failed to open input file '%s'\n", inputFile);
+ return MSCGEN_FILE_ERROR;
+ }
+ /* parse file */
+ m = MscParse(in);
+ fclose(in);
+ if (!m || !checkMsc(m))
+ {
+ fprintf(stderr, "Input format error for '%s'\n", inputFile);
+ return MSCGEN_INPUT_FORMAT_ERROR;
+ }
+
+ /* Check if an ismap file should also be generated */
+ if (format==mscgen_format_pngmap || format==mscgen_format_svgmap)
+ {
+ ismap = fopen(outputFile, "w");
+ if (!ismap)
+ {
+ fprintf(stderr, "Failed to open output file '%s': %s\n", outputFile, strerror(errno));
+ MscFree(m);
+ return MSCGEN_FILE_ERROR;
+ }
+ }
+
+ /* Open the drawing context with dummy dimensions */
+ if (!ADrawOpen(10, 10, nullFile, "", outType, &ctx.drw))
+ {
+ if (ismap)
+ {
+ fclose(ismap);
+ }
+ MscFree(m);
+ fprintf(stderr, "Failed to open temporary drawing context\n");
+ return MSCGEN_OUTPUT_CONTEXT_ERROR;
+ }
+
+ /* Now compute ideal canvas size, which may use text metrics */
+ if (MscGetOptAsFloat(m, MSC_OPT_WIDTH, &f))
+ {
+ ctx.opts.idealCanvasWidth = (unsigned int)f;
+ }
+ else if (MscGetOptAsFloat(m, MSC_OPT_HSCALE, &f))
+ {
+ ctx.opts.idealCanvasWidth *= (unsigned int)f;
+ }
+
+ /* Set the arc gradient if needed */
+ if (MscGetOptAsFloat(m, MSC_OPT_ARCGRADIENT, &f))
+ {
+ ctx.opts.arcGradient = (int)f;
+ ctx.opts.arcSpacing += ctx.opts.arcGradient;
+ }
+
+ /* Check if word wrapping on arcs other than boxes should be used */
+ MscGetOptAsBoolean(m, MSC_OPT_WORDWRAPARCS, &ctx.opts.wordWrapArcLabels);
+
+ /* Work out the entitySpacing */
+ if (ctx.opts.idealCanvasWidth / MscGetNumEntities(m) > ctx.opts.entitySpacing)
+ {
+ ctx.opts.entitySpacing = ctx.opts.idealCanvasWidth / MscGetNumEntities(m);
+ }
+
+ /* Work out the entityHeadGap */
+ MscResetEntityIterator(m);
+ for (col = 0; col < MscGetNumEntities(m); col++)
+ {
+ unsigned int lines = countLines(MscGetCurrentEntAttrib(m, MSC_ATTR_LABEL));
+ unsigned int gap;
+
+ /* Get the required gap */
+ gap = lines * ctx.drw.textHeight(&ctx.drw);
+ if (gap > ctx.opts.entityHeadGap)
+ {
+ ctx.opts.entityHeadGap = gap;
+ }
+
+ MscNextEntity(m);
+ }
+
+ //printf("opts: idealCanvasWidth=%d entitySpacing=%d entityHeadGap=%d "
+ // "arcSpacing=%d arcGradient=%d boxSpacing=%d boxInternalBorder=%d "
+ // "rboxArc=%d noteCorner=%d aboxSlope=%d wordWrapArcLabels=%d arrowWidth=%d "
+ // "arrowHeight=%d loopArcHeight=%d textHGapPre=%d textHGapPost=%d\n",
+ // ctx.opts.idealCanvasWidth,ctx.opts.entitySpacing,ctx.opts.entityHeadGap,
+ // ctx.opts.arcSpacing,ctx.opts.arcGradient,ctx.opts.boxSpacing,ctx.opts.boxInternalBorder,
+ // ctx.opts.rboxArc,ctx.opts.noteCorner,ctx.opts.aboxSlope,ctx.opts.wordWrapArcLabels,ctx.opts.arrowWidth,
+ // ctx.opts.arrowHeight,ctx.opts.loopArcHeight,ctx.opts.textHGapPre,ctx.opts.textHGapPost);
+
+ /* Work out the width and height of the canvas */
+ rowInfo = computeCanvasSize(&ctx, m, &w , &h);
+
+ /* Close the temporary output file */
+ ctx.drw.close(&ctx.drw);
+
+ //printf("opening canvas %d x %d for %s type=%d\n",w,h,outImage,outType);
+
+ /* Open the output */
+ if (!ADrawOpen(w, h, outImage, "", outType, &ctx.drw))
+ {
+ free(rowInfo);
+ if (ismap)
+ {
+ fclose(ismap);
+ }
+ MscFree(m);
+ return MSCGEN_OUTPUT_CONTEXT_ERROR;
+ }
+
+ /* Allocate storage for entity heading colours */
+ entColourRef = malloc_s(MscGetNumEntities(m) * sizeof(ADrawColour));
+
+ /* Draw the entity headings */
+ MscResetEntityIterator(m);
+ for (col = 0; col < MscGetNumEntities(m); col++)
+ {
+ unsigned int x = (ctx.opts.entitySpacing / 2) + (ctx.opts.entitySpacing * col);
+ const char *line;
+
+ /* Titles */
+ entityText(&ctx, ismap,
+ x,
+ ctx.opts.entityHeadGap - (ctx.drw.textHeight(&ctx.drw) / 2),
+ MscGetCurrentEntAttrib(m, MSC_ATTR_LABEL),
+ MscGetCurrentEntAttrib(m, MSC_ATTR_URL),
+ MscGetCurrentEntAttrib(m, MSC_ATTR_ID),
+ MscGetCurrentEntAttrib(m, MSC_ATTR_IDURL),
+ MscGetCurrentEntAttrib(m, MSC_ATTR_TEXT_COLOUR),
+ MscGetCurrentEntAttrib(m, MSC_ATTR_TEXT_BGCOLOUR));
+
+ /* Get the colours */
+ line = MscGetCurrentEntAttrib(m, MSC_ATTR_LINE_COLOUR);
+ if (line != NULL)
+ {
+ entColourRef[col] = ADrawGetColour(line);
+ }
+ else
+ {
+ entColourRef[col] = ADRAW_COL_BLACK;
+ }
+
+ MscNextEntity(m);
+ }
+
+ /* Draw the arcs */
+ addLines = TRUE;
+ row = 0;
+
+ MscResetArcIterator(m);
+ do
+ {
+ const MscArcType arcType = MscGetCurrentArcType(m);
+ const char *arcUrl = MscGetCurrentArcAttrib(m, MSC_ATTR_URL);
+ const char *arcId = MscGetCurrentArcAttrib(m, MSC_ATTR_ID);
+ const char *arcIdUrl = MscGetCurrentArcAttrib(m, MSC_ATTR_IDURL);
+ const char *arcTextColour = MscGetCurrentArcAttrib(m, MSC_ATTR_TEXT_COLOUR);
+ const char *arcTextBgColour = MscGetCurrentArcAttrib(m, MSC_ATTR_TEXT_BGCOLOUR);
+ const char *arcLineColour = MscGetCurrentArcAttrib(m, MSC_ATTR_LINE_COLOUR);
+ const int arcGradient = isBoxArc(arcType) ? 0 : getArcGradient(&ctx, m, rowInfo, row);
+ const int arcHasArrows = MscGetCurrentArcAttrib(m, MSC_ATTR_NO_ARROWS) == NULL;
+ const int arcHasBiArrows = MscGetCurrentArcAttrib(m, MSC_ATTR_BI_ARROWS) != NULL;
+ char **arcLabelLines = NULL;
+ unsigned int arcLabelLineCount = 0;
+ int startCol = -1, endCol = -1;
+
+ if (arcType == MSC_ARC_PARALLEL)
+ {
+ addLines = FALSE;
+
+ /* Rewind the row */
+ assert(row > 0);
+ row--;
+ }
+ else
+ {
+ const unsigned int ymin = rowInfo[row].ymin;
+ const unsigned int ymid = rowInfo[row].arcliney;
+ const unsigned int ymax = rowInfo[row].ymax;
+
+#if 0
+ /* For debug, mark the row spacing */
+ ctx.drw.line(&ctx.drw, 0, ymin, 10, ymin);
+ ctx.drw.line(&ctx.drw, 0, ymid, 5, ymid);
+ ctx.drw.line(&ctx.drw, 0, ymax, 10, ymax);
+#endif
+ /* Get the entity indices */
+ if (arcType != MSC_ARC_DISCO && arcType != MSC_ARC_DIVIDER && arcType != MSC_ARC_SPACE)
+ {
+ startCol = MscGetEntityIndex(m, MscGetCurrentArcSource(m));
+ endCol = MscGetEntityIndex(m, MscGetCurrentArcDest(m));
+
+ /* Check that the start column is known and the end column is
+ * known, or that it's a broadcast arc
+ */
+ assert(startCol != -1);
+ assert(endCol != -1 || isBroadcastArc(MscGetCurrentArcDest(m)));
+
+ /* Check for entity colouring if not set explicitly on the arc */
+ if (arcTextColour == NULL)
+ {
+ arcTextColour = MscGetEntAttrib(m, startCol, MSC_ATTR_ARC_TEXT_COLOUR);
+ }
+
+ if (arcTextBgColour == NULL)
+ {
+ arcTextBgColour = MscGetEntAttrib(m, startCol, MSC_ATTR_ARC_TEXT_BGCOLOUR);
+ }
+
+ if (arcLineColour == NULL)
+ {
+ arcLineColour = MscGetEntAttrib(m, startCol, MSC_ATTR_ARC_LINE_COLOUR);
+ }
+
+ }
+ else
+ {
+ /* Discontinuity or parallel arc spans whole chart */
+ startCol = 0;
+ endCol = MscGetNumEntities(m) - 1;
+ }
+
+ /* Work out how the label fits the gap between entities */
+ arcLabelLineCount = computeLabelLines(&ctx, m, arcType, &arcLabelLines,
+ MscGetCurrentArcAttrib(m, MSC_ATTR_LABEL),
+ startCol, endCol);
+
+ /* Check if this is a broadcast message */
+ if (isBroadcastArc(MscGetCurrentArcDest(m)))
+ {
+ unsigned int t;
+
+ /* Add in the entity lines */
+ if (addLines)
+ {
+ entityLines(&ctx, m, ymin, ymax + ctx.opts.arcSpacing, FALSE, entColourRef);
+ }
+
+ /* Draw arcs to each entity */
+ for (t = 0; t < MscGetNumEntities(m); t++)
+ {
+ if ((signed)t != startCol)
+ {
+ arcLine(&ctx, m, ymid, arcGradient, startCol,
+ t, arcLineColour, arcHasArrows,
+ arcHasBiArrows, arcType);
+ }
+ }
+
+ /* Fix up the start/end columns to span chart */
+ startCol = 0;
+ endCol = MscGetNumEntities(m) - 1;
+ }
+ else
+ {
+ /* Check if it is a box, discontinuity arc etc... */
+ if (isBoxArc(arcType))
+ {
+ if (addLines)
+ {
+ entityLines(&ctx, m, ymin, ymax + ctx.opts.arcSpacing, FALSE, entColourRef);
+ }
+ arcBox(&ctx, ymin, ymax, startCol, endCol, arcType, arcLineColour, arcTextBgColour);
+ }
+ else if (arcType == MSC_ARC_DISCO)
+ {
+ if (addLines)
+ {
+ entityLines(&ctx, m, ymin, ymax + ctx.opts.arcSpacing, TRUE /* dotted */, entColourRef);
+ }
+ }
+ else if (arcType == MSC_ARC_DIVIDER || arcType == MSC_ARC_SPACE)
+ {
+ if (addLines)
+ {
+ entityLines(&ctx, m, ymin, ymax + ctx.opts.arcSpacing, FALSE, entColourRef);
+ }
+
+ /* Dividers also have a horizontal line at the middle */
+ if (arcType == MSC_ARC_DIVIDER)
+ {
+ const unsigned int margin = ctx.opts.entitySpacing / 4;
+
+ if (arcLineColour != NULL)
+ {
+ ctx.drw.setPen(&ctx.drw, ADrawGetColour(arcLineColour));
+ }
+
+ /* Draw line through middle of text */
+ ctx.drw.dottedLine(&ctx.drw,
+ margin, ymid,
+ (MscGetNumEntities(m) * ctx.opts.entitySpacing) - margin, ymid);
+
+ if (arcLineColour != NULL)
+ {
+ ctx.drw.setPen(&ctx.drw, ADRAW_COL_BLACK);
+ }
+ }
+ }
+ else
+ {
+ if (addLines)
+ {
+ entityLines(&ctx, m, ymin, ymax + ctx.opts.arcSpacing, FALSE, entColourRef);
+ }
+ arcLine(&ctx, m, ymid, arcGradient, startCol, endCol, arcLineColour,
+ arcHasArrows, arcHasBiArrows, arcType);
+ }
+ }
+
+ /* All may have text */
+ if (arcLabelLineCount > 0)
+ {
+ arcText(&ctx, m, ismap, w, ymid, arcGradient,
+ startCol, endCol,
+ arcLabelLineCount, arcLabelLines,
+ arcUrl, arcId, arcIdUrl,
+ arcTextColour, arcTextBgColour, arcType);
+ }
+
+ freeLabelLines(arcLabelLineCount, arcLabelLines);
+
+ /* Advance the row */
+ row++;
+ addLines = TRUE;
+ }
+ } while (MscNextArc(m));
+
+ /* Skip arcs may require the entity lines to be extended */
+ entityLines(&ctx, m,
+ rowInfo[(MscGetNumArcs(m) - MscGetNumParallelArcs(m)) - 1].ymax,
+ h, FALSE, entColourRef);
+
+ if (ismap)
+ {
+ fclose(ismap);
+ }
+
+ free(entColourRef);
+ free(rowInfo);
+ MscFree(m);
+ /* Close the context */
+ ctx.drw.close(&ctx.drw);
+
+ return MSCGEN_OK;
+}
+
+
+const char *mscgen_error2str(int code)
+{
+ switch (code)
+ {
+ case MSCGEN_OK: return "OK";
+ case MSCGEN_FILE_ERROR: return "FILE ERROR";
+ case MSCGEN_INPUT_FORMAT_ERROR: return "INPUT FORMAT ERROR";
+ case MSCGEN_OUTPUT_CONTEXT_ERROR: return "OUTPUT CONTEXT ERROR";
+ default: return "UNKNOWN_ERROR";
+ }
+}
+