/*************************************************************************** * * $Id: svg_out.c 186 2011-03-01 21:18:19Z Michael.McTernan $ * * This file is part of mscgen, a message sequence chart renderer. * Copyright (C) 2005 Michael C McTernan, Michael.McTernan.2001@cs.bris.ac.uk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA **************************************************************************/ #include "mscgen_config.h" #include #include #include #include #include #include #include "mscgen_adraw_int.h" #include "mscgen_safe.h" #include "mscgen_utf8.h" /*************************************************************************** * Local types ***************************************************************************/ typedef struct SvgContextTag { /** Output file. */ FILE *of; /** Current pen colour name. */ const char *penColName; /** Current background pen colour name. */ const char *penBgColName; int fontPoints; } SvgContext; typedef struct { int capheight, xheight, ascender, descender; int widths[256]; } SvgCharMetric; /** Helvetica character widths. * This gives the width of each character is 1/1000ths of a point. * The values are taken from the Adobe Font Metric file for Hevletica. */ static const SvgCharMetric SvgHelvetica = { 718, 523, 718, -207, { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 278, 278, 355, 556, 556, 889, 667, 222, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556, 1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500, 667, 556, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 278, 278, 278, 469, 556, 222, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500, 222, 833, 556, 556, 556, 556, 333, 500, 278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 333, 556, 556, 167, 556, 556, 556, 556, 191, 333, 556, 333, 333, 500, 500, -1, 556, 556, 556, 278, -1, 537, 350, 222, 333, 333, 556, 1000, 1000, -1, 611, -1, 333, 333, 333, 333, 333, 333, 333, 333, -1, 333, 333, -1, 333, 333, 333, 1000, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1000, -1, 370, -1, -1, -1, -1, 556, 778, 1000, 365, -1, -1, -1, -1, -1, 889, -1, -1, -1, 278, -1, -1, 222, 611, 944, 611, -1, -1, -1, -1 } }; /*************************************************************************** * Helper functions ***************************************************************************/ /** Get the context pointer from an ADraw structure. */ static SvgContext *getSvgCtx(struct ADrawTag *ctx) { return (SvgContext *)ctx->internal; } /** Get the context pointer from an ADraw structure. */ static FILE *getSvgFile(struct ADrawTag *ctx) { return getSvgCtx(ctx)->of; } static const char *getSvgPen(struct ADrawTag *ctx) { return getSvgCtx(ctx)->penColName; } static const char *getSvgBgPen(struct ADrawTag *ctx) { return getSvgCtx(ctx)->penBgColName; } /** Given a font metric measurement, return device dependent units. * Font metric data is stored as 1/1000th of a point, and therefore * needs to be multiplied by the font point size and divided by * 1000 to give a value in device dependent units. */ static int getSpace(struct ADrawTag *ctx, long thousanths) { return ((thousanths * getSvgCtx(ctx)->fontPoints) + 500) / 1000; } /** Compute a point on an ellipse. * This computes the point on an ellipse. * * \param[in] cx,cy Center of the ellipse. * \param[in] w,h Ellipse width and height. * \param[in] a Angle in degrees. * \param[in,out] x,y Pointer to be populated with result co-ordinates. */ static void arcPoint(float cx, float cy, float w, float h, float a, unsigned int *x, unsigned int *y) { float rad = (float)((a * M_PI) / 180.0f); /* Compute point, noting this is for SVG co-ordinate system */ *x = (unsigned int)round(cx + ((w / 2.0f) * cos(rad))); *y = (unsigned int)round(cy + ((h / 2.0f) * sin(rad))); } /** Write out a line of text, escaping special characters. */ static void writeEscaped(struct ADrawTag *ctx, const char *string) { FILE *f = getSvgFile(ctx); while(*string != '\0') { unsigned int code, bytes; switch(*string) { case '<': fprintf(f, "<"); break; case '>': fprintf(f, ">"); break; case '"': fprintf(f, """); break; case '&': fprintf(f, "&"); break; default: if(Utf8Decode(string, &code, &bytes)) { fprintf(f, "&#x%x;", code); string += bytes - 1; } else { fputc(*string, f); } break; } string++; } } static void svgRect(struct ADrawTag *ctx, const char *colour, unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2) { fprintf(getSvgFile(ctx), "\n", colour, x1, y1, x2, y1, x2, y2, x1, y2); } static const char *svgColour(ADrawColour col) { switch(col) { case ADRAW_COL_WHITE: return "white"; case ADRAW_COL_BLACK: return "black"; case ADRAW_COL_BLUE: return "blue"; case ADRAW_COL_RED: return "red"; case ADRAW_COL_GREEN: return "green"; default: return NULL; } } /*************************************************************************** * API Functions ***************************************************************************/ unsigned int SvgTextWidth(struct ADrawTag *ctx, const char *string) { unsigned long width = 0; while(*string != '\0') { int i = *string & 0xff; unsigned long w = SvgHelvetica.widths[i]; /* Ignore undefined characters */ width += w > 0 ? w : 0; string++; } return getSpace(ctx, width); } int SvgTextHeight(struct ADrawTag *ctx) { return getSpace(ctx, SvgHelvetica.ascender - SvgHelvetica.descender); } void SvgLine(struct ADrawTag *ctx, unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2) { fprintf(getSvgFile(ctx), "\n", x1, y1, x2, y2, getSvgPen(ctx)); } void SvgDottedLine(struct ADrawTag *ctx, unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2) { fprintf(getSvgFile(ctx), "\n", x1, y1, x2, y2, getSvgPen(ctx)); } void SvgTextR(struct ADrawTag *ctx, unsigned int x, unsigned int y, const char *string) { SvgContext *context = getSvgCtx(ctx); svgRect(ctx, getSvgBgPen(ctx), x - 2, y - SvgTextHeight(ctx) + 1, x + SvgTextWidth(ctx, string), y - 1); y += getSpace(ctx, SvgHelvetica.descender); fprintf(getSvgFile(ctx), "\n", x - 1, y, SvgTextWidth(ctx, string), context->fontPoints, context->penColName); writeEscaped(ctx, string); fprintf(getSvgFile(ctx), "\n\n"); } void SvgTextL(struct ADrawTag *ctx, unsigned int x, unsigned int y, const char *string) { SvgContext *context = getSvgCtx(ctx); svgRect(ctx, getSvgBgPen(ctx), x - (SvgTextWidth(ctx, string) + 2), y - SvgTextHeight(ctx) + 1, x, y - 1); y += getSpace(ctx, SvgHelvetica.descender); fprintf(getSvgFile(ctx), "\n", x, y, SvgTextWidth(ctx, string), context->fontPoints, context->penColName); writeEscaped(ctx, string); fprintf(getSvgFile(ctx), "\n\n"); } void SvgTextC(struct ADrawTag *ctx, unsigned int x, unsigned int y, const char *string) { SvgContext *context = getSvgCtx(ctx); unsigned int hw = SvgTextWidth(ctx, string) / 2; svgRect(ctx, getSvgBgPen(ctx), x - (hw + 2), y - SvgTextHeight(ctx) + 1, x + hw, y - 1); y += getSpace(ctx, SvgHelvetica.descender); fprintf(getSvgFile(ctx), "\n\n", x, y, SvgTextWidth(ctx, string), context->fontPoints, context->penColName); writeEscaped(ctx, string); fprintf(getSvgFile(ctx), "\n\n"); } void SvgFilledTriangle(struct ADrawTag *ctx, unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2, unsigned int x3, unsigned int y3) { fprintf(getSvgFile(ctx), "\n", getSvgPen(ctx), x1, y1, x2, y2, x3, y3); } void SvgFilledCircle(struct ADrawTag *ctx, unsigned int x, unsigned int y, unsigned int r) { fprintf(getSvgFile(ctx), "\n", getSvgPen(ctx), x, y, r); } void SvgFilledRectangle(struct ADrawTag *ctx, unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2) { svgRect(ctx, getSvgPen(ctx), x1, y1, x2, y2); } void SvgArc(struct ADrawTag *ctx, unsigned int cx, unsigned int cy, unsigned int w, unsigned int h, unsigned int s, unsigned int e) { unsigned int sx, sy, ex, ey; /* Get start and end x,y */ arcPoint((float)cx, (float)cy, (float)w, (float)h, (float)s, &sx, &sy); arcPoint((float)cx, (float)cy, (float)w, (float)h, (float)e, &ex, &ey); fprintf(getSvgFile(ctx), "", sx, sy, w / 2, h / 2, ex, ey, getSvgPen(ctx)); } void SvgDottedArc(struct ADrawTag *ctx, unsigned int cx, unsigned int cy, unsigned int w, unsigned int h, unsigned int s, unsigned int e) { unsigned int sx, sy, ex, ey; /* Get start and end x,y */ arcPoint((float)cx, (float)cy, (float)w, (float)h, (float)s, &sx, &sy); arcPoint((float)cx, (float)cy, (float)w, (float)h, (float)e, &ex, &ey); fprintf(getSvgFile(ctx), "", sx, sy, w / 2, h / 2, ex, ey, getSvgPen(ctx)); } void SvgSetPen(struct ADrawTag *ctx, ADrawColour col) { static char colCmd[10]; getSvgCtx(ctx)->penColName = svgColour(col); if(getSvgCtx(ctx)->penColName == NULL) { /* Print the RGB value into the local storage */ sprintf(colCmd, "#%06X", col); /* Now set the colour name to the local store */ getSvgCtx(ctx)->penColName = colCmd; } } void SvgSetBgPen(struct ADrawTag *ctx, ADrawColour col) { static char colCmd[10]; getSvgCtx(ctx)->penBgColName = svgColour(col); if(getSvgCtx(ctx)->penBgColName == NULL) { /* Print the RGB value into the local storage */ sprintf(colCmd, "#%06X", col); /* Now set the colour name to the local store */ getSvgCtx(ctx)->penBgColName = colCmd; } } void SvgSetFontSize(struct ADrawTag *ctx, ADrawFontSize size) { SvgContext *context = getSvgCtx(ctx); switch(size) { case ADRAW_FONT_TINY: context->fontPoints = 8; break; case ADRAW_FONT_SMALL: context->fontPoints = 12; break; default: assert(0); } } Boolean SvgClose(struct ADrawTag *ctx) { SvgContext *context = getSvgCtx(ctx); /* Close the SVG */ fprintf(context->of, "\n"); /* Close the output file */ if(context->of != stdout) { fclose(context->of); } /* Free and destroy context */ free(context); ctx->internal = NULL; return TRUE; } Boolean SvgInit(unsigned int w, unsigned int h, const char *file, struct ADrawTag *outContext) { SvgContext *context; /* Create context */ context = outContext->internal = malloc_s(sizeof(SvgContext)); if(context == NULL) { return FALSE; } /* Open the output file */ if(strcmp(file, "-") == 0) { context->of = stdout; } else { context->of = fopen(file, "wb"); if(!context->of) { fprintf(stderr, "SvgInit: Failed to open output file '%s': %s\n", file, strerror(errno)); return FALSE; } } /* Set the initial pen state */ SvgSetPen(outContext, ADRAW_COL_BLACK); SvgSetBgPen(outContext, ADRAW_COL_WHITE); /* Default to small font */ SvgSetFontSize(outContext, ADRAW_FONT_SMALL); fprintf(context->of, "\n"); fprintf(context->of, "\n", w, h, w, h); /* Now fill in the function pointers */ outContext->line = SvgLine; outContext->dottedLine = SvgDottedLine; outContext->textL = SvgTextL; outContext->textC = SvgTextC; outContext->textR = SvgTextR; outContext->textWidth = SvgTextWidth; outContext->textHeight = SvgTextHeight; outContext->filledRectangle = SvgFilledRectangle; outContext->filledTriangle = SvgFilledTriangle; outContext->filledCircle = SvgFilledCircle; outContext->arc = SvgArc; outContext->dottedArc = SvgDottedArc; outContext->setPen = SvgSetPen; outContext->setBgPen = SvgSetBgPen; outContext->setFontSize = SvgSetFontSize; outContext->close = SvgClose; return TRUE; } /* END OF FILE */