/***************************************************************************
 *
 * $Id: ps_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 <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include "mscgen_adraw_int.h"
#include "mscgen_utf8.h"
#include "mscgen_safe.h"

/***************************************************************************
 * Manifest Constants
 ***************************************************************************/

/** Overall scaling of the Postscript output.
 */
#define PS_OUT_SCALE  0.7f

/***************************************************************************
 * Local types
 ***************************************************************************/

typedef struct PsContextTag
{
    /** Output file. */
    FILE        *of;

    /** Point size of the current font. */
    int          fontPoints;

    /** Current pen colour. */
    ADrawColour  penColour;

    /** Background colour for the pen. */
    ADrawColour  penBgColour;
}
PsContext;

typedef struct
{
    int capheight, xheight, ascender, descender;
    int widths[256];
}
PsCharMetric;

/** 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 PsCharMetric PsHelvetica =
{
    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 PsContext *getPsCtx(struct ADrawTag *ctx)
{
    return (PsContext *)ctx->internal;
}

/** Get the context pointer from an ADraw structure.
 */
static FILE *getPsFile(struct ADrawTag *ctx)
{
    return getPsCtx(ctx)->of;
}

/** 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 * getPsCtx(ctx)->fontPoints) + 500) / 1000;
}

/** Write out a line of text, escaping special characters.
 */
static void writeEscaped(struct ADrawTag *ctx, const char *string)
{
    FILE *f = getPsFile(ctx);

    while(*string != '\0')
    {
        unsigned int code, bytes;

        switch(*string)
        {
            case '(': fprintf(f, "\\("); break;
            case ')': fprintf(f, "\\)"); break;
            default:
                if(Utf8Decode(string, &code, &bytes))
                {
                    fprintf(f, "\\%o", code);
                    string += bytes - 1;
                }
                else
                {
                    fputc(*string, f);
                }
                break;
        }

        string++;
    }
}

static void setColour(struct ADrawTag *ctx,
                      ADrawColour      col)
{
    float r, g, b;

    /* Extract RGB values */
    r = (float)((col & 0xff0000) >> 16);
    g = (float)((col & 0x00ff00) >>  8);
    b = (float)((col & 0x0000ff) >>  0);

    /* Normalise */
    r /= 255.0f;
    g /= 255.0f;
    b /= 255.0f;

    /* Generate output command */
    fprintf(getPsFile(ctx), "%f %f %f setrgbcolor\n", r ,g ,b);
}

/***************************************************************************
 * API Functions
 ***************************************************************************/

unsigned int PsTextWidth(struct ADrawTag *ctx,
                          const char *string)
{
    unsigned long width = 0;

    while(*string != '\0')
    {
        int           i = *string & 0xff;
        unsigned long w = PsHelvetica.widths[i];

        /* Ignore undefined characters */
        width += w > 0 ? w : 0;

        string++;
    }

    return getSpace(ctx, width);
}


int PsTextHeight(struct ADrawTag *ctx)
{
    return getSpace(ctx, PsHelvetica.ascender - PsHelvetica.descender);
}


void PsLine(struct ADrawTag *ctx,
             unsigned int     x1,
             unsigned int     y1,
             unsigned int     x2,
             unsigned int     y2)
{
    fprintf(getPsFile(ctx),
            "newpath %d %d moveto %d %d lineto stroke\n",
            x1, 0-y1, x2, 0-y2);

}


void PsDottedLine(struct ADrawTag *ctx,
                   unsigned int     x1,
                   unsigned int     y1,
                   unsigned int     x2,
                   unsigned int     y2)
{
    fprintf(getPsFile(ctx), "[2] 0 setdash\n");
    PsLine(ctx, x1, y1, x2, y2);
    fprintf(getPsFile(ctx), "[] 0 setdash\n");
}


void PsFilledRectangle(struct ADrawTag *ctx,
                       unsigned int     x1,
                       unsigned int     y1,
                       unsigned int     x2,
                       unsigned int     y2)
{
    fprintf(getPsFile(ctx),
            "newpath "
            "%d %d moveto "
            "%d %d lineto "
            "%d %d lineto "
            "%d %d lineto "
            "closepath "
            "fill\n",
            x1, 0-y1,
            x2, 0-y1,
            x2, 0-y2,
            x1, 0-y2);
}


void PsTextR(struct ADrawTag *ctx,
              unsigned int     x,
              unsigned int     y,
              const char      *string)
{
    PsContext *context = getPsCtx(ctx);

    /* Push the string and get its width */
    fprintf(getPsFile(ctx), "(");
    writeEscaped(ctx, string);
    fprintf(getPsFile(ctx), ") dup stringwidth\n");

    /* Draw the background box */
    setColour(ctx, context->penBgColour);
    fprintf(getPsFile(ctx), "pop "                /* Ignore y-value */
                            "dup "                /* Duplicate string width */
                            "newpath "
                            "%d %d moveto "       /* Bottom left of the box */
                            "0 rlineto "          /* Move to bottom right of the box */
                            "0 %d rlineto "       /* To top right */
                            "neg 0 rlineto "      /* Back to bottom left */
                            "closepath fill\n",   /* Done */
                            x, 0-y - getSpace(ctx, PsHelvetica.descender),
                            getSpace(ctx, PsHelvetica.ascender));

    /* Restore pen and show the string */
    setColour(ctx, context->penColour);
    fprintf(getPsFile(ctx), "%d %d moveto show\n",
                             x, 0-y - getSpace(ctx, PsHelvetica.descender));
}


void PsTextL(struct ADrawTag *ctx,
              unsigned int     x,
              unsigned int     y,
              const char      *string)
{
    PsContext *context = getPsCtx(ctx);

    /* Draw the background box */
    setColour(ctx, context->penBgColour);
    PsFilledRectangle(ctx, x, 0-y, x + 10, 0-y + 10);
    setColour(ctx, context->penColour);

    fprintf(getPsFile(ctx),
            "%d %d moveto "
            "(",
            x, 0-y - getSpace(ctx, PsHelvetica.descender));
    writeEscaped(ctx, string);
    fprintf(getPsFile(ctx),
            ") dup stringwidth "
            "pop "  /* Ignore y value */
            "neg "  /* Invert x value */
            "0 "
            "rmoveto "
            "show\n");
}


void PsTextC(struct ADrawTag *ctx,
              unsigned int     x,
              unsigned int     y,
              const char      *string)
{
    PsContext *context = getPsCtx(ctx);

    /* Push the string and get its width */
    fprintf(getPsFile(ctx), "(");
    writeEscaped(ctx, string);
    fprintf(getPsFile(ctx), ") dup stringwidth\n");

    /* Draw the background box */
    setColour(ctx, context->penBgColour);
    fprintf(getPsFile(ctx), "pop "                     /* Ignore y-value */
                            "dup dup "                 /* Duplicate string width twice */
                            "newpath "
                            "%d %d moveto "            /* Starting point, centre bottom of box */
                            "2 div neg 0 rmoveto "     /* Move to bottom left */
                            "0 rlineto "               /* Move to bottom right of the box */
                            "0 %d rlineto "            /* To top right */
                            "neg 0 rlineto "           /* Back to bottom left */
                            "closepath fill\n",        /* Done */
                            x, 0-y,
                            getSpace(ctx, PsHelvetica.ascender));

    /* Restore pen and show the string */
    setColour(ctx, context->penColour);
    fprintf(getPsFile(ctx), "%d %d moveto dup stringwidth pop 2 div neg 0 rmoveto show\n",
                             x, 0-y - getSpace(ctx, PsHelvetica.descender));
}


void PsFilledTriangle(struct ADrawTag *ctx,
                       unsigned int x1,
                       unsigned int y1,
                       unsigned int x2,
                       unsigned int y2,
                       unsigned int x3,
                       unsigned int y3)
{
    fprintf(getPsFile(ctx),
            "newpath "
            "%d %d moveto "
            "%d %d lineto "
            "%d %d lineto "
            "closepath "
            "fill\n",
            x1, 0-y1,
            x2, 0-y2,
            x3, 0-y3);
}


void PsFilledCircle(struct ADrawTag *ctx,
                     unsigned int x,
                     unsigned int y,
                     unsigned int r)
{
    fprintf(getPsFile(ctx),
            "newpath "
            "%d %d %d 0 360 arc "
            "closepath "
            "fill\n",
            x, 0-y, r);
}


void PsArc(struct ADrawTag *ctx,
            unsigned int cx,
            unsigned int cy,
            unsigned int w,
            unsigned int h,
            unsigned int s,
            unsigned int e)
{
    fprintf(getPsFile(ctx),
            "newpath "
            "%d %d %d %d %d %d ellipse "
            "stroke\n",
            cx, 0-cy, w, h, s, e);
}


void PsDottedArc(struct ADrawTag *ctx,
                  unsigned int cx,
                  unsigned int cy,
                  unsigned int w,
                  unsigned int h,
                  unsigned int s,
                  unsigned int e)
{
    fprintf(getPsFile(ctx), "[2] 0 setdash\n");
    PsArc(ctx, cx, cy, w, h, s, e);
    fprintf(getPsFile(ctx), "[] 0 setdash\n");
}


void PsSetPen(struct ADrawTag *ctx,
              ADrawColour      col)
{
    PsContext *context = getPsCtx(ctx);

    assert(col != ADRAW_COL_INVALID);

    /* Check if the pen colour has changed */
    if(context->penColour != col)
    {
        setColour(ctx, col);

        /* Store the pen colour */
        context->penColour = col;
    }
}


void PsSetBgPen(struct ADrawTag *ctx,
                ADrawColour      col)
{
    PsContext *context = getPsCtx(ctx);

    context->penBgColour = col;
}


void PsSetFontSize(struct ADrawTag *ctx,
                   ADrawFontSize    size)
{
    PsContext *context = getPsCtx(ctx);

    switch(size)
    {
        case ADRAW_FONT_TINY:
            getPsCtx(ctx)->fontPoints = 8;
            break;

        case ADRAW_FONT_SMALL:
            getPsCtx(ctx)->fontPoints = 12;
            break;

        default:
            assert(0);
    }

    fprintf(context->of, "/Helvetica findfont\n");
    fprintf(context->of, "%d scalefont\n", getPsCtx(ctx)->fontPoints);
    fprintf(context->of, "setfont\n");
}


Boolean PsClose(struct ADrawTag *ctx)
{
    PsContext *context = getPsCtx(ctx);

    /* Close the output file */
    if(context->of != stdout)
    {
        fclose(context->of);
    }

    /* Free and destroy context */
    free(context);
    ctx->internal = NULL;

    return TRUE;
}



Boolean PsInit(unsigned int     w,
               unsigned int     h,
               const char      *file,
               struct ADrawTag *outContext)
{
    PsContext *context;

    /* Create context */
    context = outContext->internal = malloc_s(sizeof(PsContext));
    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, "PsInit: Failed to open output file '%s': %s\n", file, strerror(errno));
            return FALSE;
        }
    }

    /* Write the header */
    fprintf(context->of, "%%!PS-Adobe-3.0 EPSF-2.0\n"
                         "%%%%BoundingBox: 0 0 %.0f %.0f\n", w * PS_OUT_SCALE, h * PS_OUT_SCALE);
    fprintf(context->of, "%%%%Creator: mscgen %s\n", PACKAGE_VERSION);
    fprintf(context->of, "%%%%EndComments\n");

    /* Shrink everything by 70% */
    fprintf(context->of, "%f %f scale\n", PS_OUT_SCALE, PS_OUT_SCALE);

    /* Create clipping rectangle to constrain dimensions */
    fprintf(context->of, "0 0 moveto\n");
    fprintf(context->of, "0 %u lineto\n", h);
    fprintf(context->of, "%u %u lineto\n", w, h);
    fprintf(context->of, "%u 0 lineto\n", w);
    fprintf(context->of, "closepath\n");
    fprintf(context->of, "clip\n");
    fprintf(context->of, "%%PageTrailer\n");
    fprintf(context->of, "%%Page: 1 1\n");

    /* Set default font */
    fprintf(context->of, "/Helvetica findfont\n");
    fprintf(context->of, "10 scalefont\n");
    fprintf(context->of, "setfont\n");

    /* Get the default font size */
    PsSetFontSize(outContext, ADRAW_FONT_SMALL);

    /* Translate up by the height, y-axis will be inverted */
    fprintf(context->of, "0 %d translate\n", h);

    /* Arc drawing function */
    fprintf(context->of, "/mtrx matrix def\n"
                         "/ellipse\n"
                         "  { /endangle exch def\n"
                         "    /startangle exch def\n"
                         "    /ydia exch def\n"
                         "    /xdia exch def\n"
                         "    /y exch def\n"
                         "    /x exch def\n"
                         "    /savematrix mtrx currentmatrix def\n"
                         "    x y translate\n"
                         "    xdia 2 div ydia 2 div scale\n"
                         "    1 -1 scale\n"
                         "    0 0 1 startangle endangle arc\n"
                         "    savematrix setmatrix\n"
                         "} def\n");

    /* Set the current pen colours */
    context->penColour = ADRAW_COL_BLACK;
    context->penBgColour = ADRAW_COL_WHITE;

    /* Now fill in the function pointers */
    outContext->line            = PsLine;
    outContext->dottedLine      = PsDottedLine;
    outContext->textL           = PsTextL;
    outContext->textC           = PsTextC;
    outContext->textR           = PsTextR;
    outContext->textWidth       = PsTextWidth;
    outContext->textHeight      = PsTextHeight;
    outContext->filledRectangle = PsFilledRectangle;
    outContext->filledTriangle  = PsFilledTriangle;
    outContext->filledCircle    = PsFilledCircle;
    outContext->arc             = PsArc;
    outContext->dottedArc       = PsDottedArc;
    outContext->setPen          = PsSetPen;
    outContext->setBgPen        = PsSetBgPen;
    outContext->setFontSize     = PsSetFontSize;
    outContext->close           = PsClose;

    return TRUE;
}

/* END OF FILE */
