diff options
Diffstat (limited to 'libmscgen/mscgen_gd_out.c')
-rw-r--r-- | libmscgen/mscgen_gd_out.c | 606 |
1 files changed, 606 insertions, 0 deletions
diff --git a/libmscgen/mscgen_gd_out.c b/libmscgen/mscgen_gd_out.c new file mode 100644 index 0000000..263431e --- /dev/null +++ b/libmscgen/mscgen_gd_out.c @@ -0,0 +1,606 @@ +/*************************************************************************** + * + * $Id: gd_out.c 186 2011-03-01 21:18:19Z Michael.McTernan $ + * + * This file is part of mscgen, a message sequence chart renderer. + * Copyright (C) 2010 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" +#ifndef REMOVE_PNG_OUTPUT +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif +#include "gd.h" +#ifndef USE_FREETYPE +#include "gdfontt.h" /* Tiny font */ +#include "gdfonts.h" /* Small font */ +#endif +#include "mscgen_adraw_int.h" +#include "mscgen_safe.h" + +/*************************************************************************** + * Manifest Constants + ***************************************************************************/ + +#define MAX_COLOURS 128 + +/*************************************************************************** + * Local types + ***************************************************************************/ + +typedef struct GdoContextTag +{ + gdImagePtr img; +#ifdef USE_FREETYPE + double fontPoints; + const char *fontName; +#else + gdFontPtr font; +#endif + + /** Array of colours and GD references. */ + struct + { + int ref; + ADrawColour col; + } + colour[MAX_COLOURS]; + + /** Number of valid references in \a colourRef[]. */ + int colourCount; + + /** The current pen for GD. */ + int pen; + + /** Background colour for rendering text. */ + int bgpen; + + FILE *outFile; +} +GdoContext; + +/*************************************************************************** + * Helper functions + ***************************************************************************/ + +/** Swap a pair of values inplace. + */ +static void swap(unsigned int *a, unsigned int *b) +{ + unsigned int x; + + x = *a; + *a = *b; + *b = x; +} + + +/** Get the context pointer from an ADraw structure. + */ +static GdoContext *getGdoCtx(struct ADrawTag *ctx) +{ + return (GdoContext *)ctx->internal; +} + + +/** Get the GD image pointer from an ADraw structure. + */ +static gdImagePtr getGdoImg(struct ADrawTag *ctx) +{ + return getGdoCtx(ctx)->img; +} + +/** Get the current GD pen index from an ADraw structure. + */ +static int getGdoPen(struct ADrawTag *ctx) +{ + return getGdoCtx(ctx)->pen; +} + + +/** Given a colour value, convert to a gd colour reference. + * This searches the current pallette of colours for the passed colour and + * returns an existing reference if possible. Otherwise a new colour reference + * is allocated and returned. + */ +static int getColourRef(GdoContext *context, ADrawColour col) +{ + int t; + + /* Check if the colour is already allocated */ + for(t = 0; t < context->colourCount; t++) + { + if(context->colour[t].col == col) + { + return context->colour[t].ref; + } + } + + /* Allocate a new colour if there is space */ + if(t < MAX_COLOURS) + { + /* Store the colour and allocate a reference */ + context->colour[t].col = col; + context->colour[t].ref = gdImageColorAllocate(context->img, + (col & 0xff0000) >> 16, + (col & 0x00ff00) >> 8, + (col & 0x0000ff) >> 0); + context->colourCount++; + + /* Return the new colour reference */ + return context->colour[t].ref; + } + else + { + /* Cannot allocate more colours, so return black by default */ + return getColourRef(context, ADRAW_COL_BLACK); + } +} + + +/** Set the dashed style. + */ +static void setStyle(struct ADrawTag *ctx) +{ + GdoContext *context = getGdoCtx(ctx); + int style[4]; + + /* Create dash pattern */ + style[0] = style[1] = context->pen; + style[2] = style[3] = getColourRef(context, ADRAW_COL_WHITE); + + gdImageSetStyle(context->img, style, 4); +} + +/*************************************************************************** + * API Functions + ***************************************************************************/ + +unsigned int gdoTextWidth(struct ADrawTag *ctx, + const char *string) +{ +#ifndef USE_FREETYPE + const unsigned int l = strlen(string); + + /* Remove 1 pixel since there is usually an uneven gap at + * the right of the last character for the fixed width + * font. + */ + return l == 0 ? 0 : (getGdoCtx(ctx)->font->w * l) - 1; +#else + GdoContext *context = getGdoCtx(ctx); + int rect[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + const char *r; + + r = gdImageStringFT(NULL, + rect, + context->pen, + (char *)context->fontName, + context->fontPoints, + 0, + 0, 0, + (char *)string); + if(r) + { + fprintf(stderr, "Error: gdoTextWidth: %s (GDFONTPATH=%s)\n", r, mscgen_getenv_s("GDFONTPATH")); + exit(EXIT_FAILURE); + } + + /* Remove 1 pixel since there is usually an uneven gap at + * the right of the last character for the fixed width + * font. + */ + return rect[2] - 1; +#endif +} + + +int gdoTextHeight(struct ADrawTag *ctx) +{ +#ifndef USE_FREETYPE + return getGdoCtx(ctx)->font->h; +#else + GdoContext *context = getGdoCtx(ctx); + int rect[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + const char *r; + + r = gdImageStringFT(NULL, + rect, + context->pen, + (char *)context->fontName, + context->fontPoints, + 0, + 0, 0, + "gHELLOWt"); + if(r) + { + fprintf(stderr, "Error: gdoTextHeight: %s (GDFONTPATH=%s)\n", r, mscgen_getenv_s("GDFONTPATH")); + exit(EXIT_FAILURE); + } + + return (-rect[5]) + 1; +#endif +} + + +void gdoLine(struct ADrawTag *ctx, + unsigned int x1, + unsigned int y1, + unsigned int x2, + unsigned int y2) +{ + /* Range check since gdImageLine() takes signed values */ + if(x1 <= INT_MAX && y1 <= INT_MAX && x2 <= INT_MAX && y2 <= INT_MAX) + { + /* Anti-aliasing fails if drawing 'backwards' for some octants */ + if(x1 > x2 && abs((int)x1 - (int)x2) > abs((int)y1 - (int)y2)) + { + swap(&x1, &x2); + swap(&y1, &y2); + } + + gdImageSetAntiAliased(getGdoImg(ctx), getGdoPen(ctx)); + gdImageLine(getGdoImg(ctx), + x1, y1, x2, y2, gdAntiAliased); + } +} + + +void gdoDottedLine(struct ADrawTag *ctx, + unsigned int x1, + unsigned int y1, + unsigned int x2, + unsigned int y2) +{ + setStyle(ctx); + + /* Range check since gdImageLine() takes signed values */ + if(x1 <= INT_MAX && y1 <= INT_MAX && x2 <= INT_MAX && y2 <= INT_MAX) + { + gdImageLine(getGdoImg(ctx), x1, y1, x2, y2, gdStyled); + } +} + + +void gdoTextR(struct ADrawTag *ctx, + unsigned int x, + unsigned int y, + const char *string) +{ + GdoContext *context = getGdoCtx(ctx); +#ifdef USE_FREETYPE + int rect[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + const char *r; +#endif + int textWidth; + + textWidth = gdoTextWidth(ctx, string); + + /* Range check since gdImageFilledRectangle() takes signed values */ + if(x + textWidth <= INT_MAX && y <= INT_MAX) + { + gdImageFilledRectangle(getGdoImg(ctx), + x, + y - (gdoTextHeight(ctx) - 2), + x + textWidth, + y - 2, + context->bgpen); + +#ifdef USE_FREETYPE + r = gdImageStringFT(getGdoImg(ctx), + rect, + context->pen, + (char *)context->fontName, + context->fontPoints, + 0, + x, y - 2, + (char *)string); + + if(r) + { + fprintf(stderr, "Error: gdoTextR: %s (GDFONTPATH=%s)\n", r, mscgen_getenv_s("GDFONTPATH")); + exit(EXIT_FAILURE); + } +#else + gdImageString(getGdoImg(ctx), + getGdoCtx(ctx)->font, + x, + y - gdoTextHeight(ctx), + (unsigned char *)string, + getGdoPen(ctx)); +#endif + } + +} + + +void gdoTextL(struct ADrawTag *ctx, + unsigned int x, + unsigned int y, + const char *string) +{ + x -= gdoTextWidth(ctx, string); + + /* Range check since gdImageFilledRectangle() takes signed values */ + if(x <= INT_MAX && y <= INT_MAX) + { + gdoTextR(ctx, x, y, string); + } +} + +void gdoTextC(struct ADrawTag *ctx, + unsigned int x, + unsigned int y, + const char *string) +{ + gdoTextR(ctx, x - (gdoTextWidth(ctx, string) / 2), y, string); +} + +void gdoFilledRectangle(struct ADrawTag *ctx, + unsigned int x1, + unsigned int y1, + unsigned int x2, + unsigned int y2) +{ + gdPoint p[4]; + + /* Range check since gdPoint contains signed values */ + if(x1 <= INT_MAX && y1 <= INT_MAX && x2 <= INT_MAX && y2 <= INT_MAX) + { + p[0].x = x1; p[0].y = y1; + p[1].x = x2; p[1].y = y1; + p[2].x = x2; p[2].y = y2; + p[3].x = x1; p[3].y = y2; + + + gdImageFilledPolygon(getGdoImg(ctx), p, 4, getGdoPen(ctx)); + } +} + +void gdoFilledTriangle(struct ADrawTag *ctx, + unsigned int x1, + unsigned int y1, + unsigned int x2, + unsigned int y2, + unsigned int x3, + unsigned int y3) +{ + gdPoint p[3]; + + /* Range check since gdPoint contains signed values */ + if(x1 <= INT_MAX && y1 <= INT_MAX && + x2 <= INT_MAX && y2 <= INT_MAX && + x3 <= INT_MAX && y3 <= INT_MAX) + { + p[0].x = x1; p[0].y = y1; + p[1].x = x2; p[1].y = y2; + p[2].x = x3; p[2].y = y3; + + gdImageSetAntiAliased(getGdoImg(ctx), getGdoPen(ctx)); + gdImageFilledPolygon(getGdoImg(ctx), p, 3, gdAntiAliased); + } +} + + +void gdoFilledCircle(struct ADrawTag *ctx, + unsigned int x, + unsigned int y, + unsigned int r) +{ + gdImageSetAntiAliased(getGdoImg(ctx), getGdoPen(ctx)); + gdImageFilledEllipse(getGdoImg(ctx), x, y, r * 2, r * 2, gdAntiAliased); +} + + +void gdoArc(struct ADrawTag *ctx, + unsigned int cx, + unsigned int cy, + unsigned int w, + unsigned int h, + unsigned int s, + unsigned int e) +{ + + /* Range check since gdImageArc takes signed values */ + if(cx <= INT_MAX && cy <= INT_MAX) + { + gdImageArc(getGdoImg(ctx), cx, cy, w, h, s, e, getGdoPen(ctx)); + } +} + + +void gdoDottedArc(struct ADrawTag *ctx, + unsigned int cx, + unsigned int cy, + unsigned int w, + unsigned int h, + unsigned int s, + unsigned int e) +{ + /* Range check since gdImageArc takes signed values */ + if(cx <= INT_MAX && cy <= INT_MAX) + { + setStyle(ctx); + gdImageArc(getGdoImg(ctx), cx, cy, w, h, s, e, gdStyled); + } +} + + +void gdoSetPen(struct ADrawTag *ctx, + ADrawColour col) +{ + GdoContext *context = getGdoCtx(ctx);; + + context->pen = getColourRef(context, col); +} + + +void gdoSetBgPen(struct ADrawTag *ctx, + ADrawColour col) +{ + GdoContext *context = getGdoCtx(ctx);; + + context->bgpen = getColourRef(context, col); +} + + +void gdoSetFontSize(struct ADrawTag *ctx, + ADrawFontSize size) +{ + switch(size) + { +#ifdef USE_FREETYPE + case ADRAW_FONT_TINY: + getGdoCtx(ctx)->fontPoints = 9.0; + break; + + case ADRAW_FONT_SMALL: + getGdoCtx(ctx)->fontPoints = 11.0; + break; +#else + case ADRAW_FONT_TINY: + getGdoCtx(ctx)->font = gdFontGetTiny(); + break; + + case ADRAW_FONT_SMALL: + getGdoCtx(ctx)->font = gdFontGetSmall(); + break; +#endif + default: + assert(0); + } +} + + +Boolean gdoClose(struct ADrawTag *ctx) +{ + GdoContext *context = getGdoCtx(ctx); + + /* Output the image to the disk file in PNG format. */ + gdImagePng(getGdoImg(ctx), context->outFile); + if(context->outFile != stdout) + { + fclose(context->outFile); + } + + /* Destroy the image in memory */ + gdImageDestroy(context->img); + + /* Free and destroy context */ + free(context); + ctx->internal = NULL; + + return TRUE; +} + + + +Boolean GdoInit(unsigned int w, + unsigned int h, + const char *file, + const char *fontName UNUSED, + struct ADrawTag *outContext) +{ + GdoContext *context; + + /* Range check the size */ + if(w > INT_MAX || h > INT_MAX) + { + fprintf(stderr, "Warning: The output image size larger than can be supported for png; output\n" + " will be clipped.\n"); + } + + /* Clip image size to limits */ + if(w > INT_MAX) w = INT_MAX; + if(h > INT_MAX) h = INT_MAX; + + /* Create context */ + context = outContext->internal = zalloc_s(sizeof(GdoContext)); + if(context == NULL) + { + return FALSE; + } + + /* Open the output file */ + if(strcmp(file, "-") == 0) + { + context->outFile = stdout; + } + else + { + context->outFile = fopen(file, "wb"); + if(!context->outFile) + { + fprintf(stderr, "GdoInit: Failed to open output file '%s': %s\n", file, strerror(errno)); + return FALSE; + } + } + +#ifdef USE_FREETYPE + /* Request that we use font config strings and store font name */ + gdFTUseFontConfig(1); + context->fontName = fontName; + + assert(fontName != NULL); +#endif + + /* Allocate the image */ + context->img = gdImageCreateTrueColor(w, h); + + /* Allocate first colour and clear background */ + gdImageFilledRectangle(context->img, + 0, 0, + w, h, + getColourRef(context, ADRAW_COL_WHITE)); + + /* Set pen colour to black and background to white */ + context->pen = getColourRef(context, ADRAW_COL_BLACK); + context->bgpen = getColourRef(context, ADRAW_COL_WHITE); + + /* Get the default font size */ + gdoSetFontSize(outContext, ADRAW_FONT_SMALL); + + /* Now fill in the function pointers */ + outContext->line = gdoLine; + outContext->dottedLine = gdoDottedLine; + outContext->textL = gdoTextL; + outContext->textC = gdoTextC; + outContext->textR = gdoTextR; + outContext->textWidth = gdoTextWidth; + outContext->textHeight = gdoTextHeight; + outContext->filledRectangle = gdoFilledRectangle; + outContext->filledTriangle = gdoFilledTriangle; + outContext->filledCircle = gdoFilledCircle; + outContext->arc = gdoArc; + outContext->dottedArc = gdoDottedArc; + outContext->setPen = gdoSetPen; + outContext->setBgPen = gdoSetBgPen; + outContext->setFontSize = gdoSetFontSize; + outContext->close = gdoClose; + + return TRUE; +} + +#endif /* REMOVE_PNG_OUTPUT */ + +/* END OF FILE */ |