diff options
Diffstat (limited to 'libmscgen/mscgen_api.c')
-rw-r--r-- | libmscgen/mscgen_api.c | 1874 |
1 files changed, 1874 insertions, 0 deletions
diff --git a/libmscgen/mscgen_api.c b/libmscgen/mscgen_api.c new file mode 100644 index 0000000..0f6052a --- /dev/null +++ b/libmscgen/mscgen_api.c @@ -0,0 +1,1874 @@ +#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; + + /** Anguluar 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 = (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 = 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; + + 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 = lineEnd - lineStart; + } + else + { + lineLen = 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 ouput 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 explict 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 backgroun, 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(sx, y - 1, ctx->opts.entitySpacing - 8, + ctx->opts.loopArcHeight, 180 - 45, + &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(sx, y - 1, ctx->opts.entitySpacing - 8, + ctx->opts.loopArcHeight, 45, + &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 entites 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 explicity 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"; + } +} + |