/*************************************************
*                      ASPIC                     *
*************************************************/

/* Copyright (c) University of Cambridge 1991 - 2008 */
/* Created: February 1991 */
/* Last modified: February 2008 */

/* This module generates output in SGCAL input format. */


#include "aspic.h"



/*************************************************
*               Local variables                  *
*************************************************/

static unit bbox[4];

static colour line_filling;
static BOOL first_filling;

static char fixed_buffer[100];
static char fixed_ptr = 0;

static unit halfdepth;
static unit setthickness;
static colour setcolour;
static unit setdash1;
static unit setdash2;

static unit last_xstart;
static unit last_xend;
static unit last_ystart;
static unit last_yend;
static unit last_line_thickness;



/*************************************************
*          Local initialization                  *
*************************************************/

void
init_sg(void)
{
use_fonts = FALSE;      /* don't use Aspic fonts (pro tem?) */
resolution = 240;       /* 0.24 == 300 dpi for default resolution */
}



/*************************************************
*             Drawing routines                   *
*************************************************/

static void move(unit x, unit y)
{
x = rnd(x - bbox[0]);
y = rnd(y - bbox[1] - halfdepth);

if (centrepic) x += (centrepic - bbox[2] + bbox[0])/2;

if (x > 0) fprintf(out_file, "$s%s", fixed(x));
  else if (x < 0) fprintf(out_file, "$s-%s", fixed(-x));

if (y > 0) fprintf(out_file, "$U%s", fixed(y));
  else if (y < 0) fprintf(out_file, "$D%s", fixed(-y));
}


static void rline(unit x, unit y)
{
if (x == 0)
  fprintf(out_file, "$vr%s", fixed(rnd(y)));
else if (y == 0)
  fprintf(out_file, "$hr%s", fixed(rnd(x)));
else fprintf(out_file, "$sr%s,%s", fixed(rnd(x)), fixed(rnd(y)));
}


static void rbezier(unit x1, unit y1, unit x2, unit y2, unit x3, unit y3)
{
fprintf(out_file, "$bc%s,%s,%s,%s,%s,%s", fixed(x1), fixed(y1),
  fixed(x2), fixed(y2), fixed(rnd(x3)), fixed(rnd(y3)));
}


static void home(void)
{
fprintf(out_file, "\n");
}


static void linesplit(void)
{
fprintf(out_file, "+++\n");
}


/*************************************************
*          Set line thickness & colour           *
*************************************************/

static void set_thickness(unit t)
{
if (t != setthickness)
  {
  fprintf(out_file, ".rulewidth %s\n", fixed(t));
  setthickness = t;
  }
}

static void set_colour(colour c)
{
if (c.red   != setcolour.red ||
    c.green != setcolour.green ||
    c.blue  != setcolour.blue)
  {
  if (c.red == c.green && c.green == c.blue)
    {
    fprintf(out_file, ".graphgrey %s\n", fixed(c.red));
    }
  else
    {
    fprintf(out_file, ".graphcolour %s %s %s\n", fixed(c.red), fixed(c.green),
      fixed(c.blue));
    }
  setcolour = c;
  }
}


/*************************************************
*                  Set dashedness                *
*************************************************/

static void set_dash(unit dash1, unit dash2)
{
if (dash1 != setdash1 || (dash1 != 0 && dash2 != setdash2))
  {
  if (dash1 == 0) fprintf(out_file, ".ruledash 0\n");
    else fprintf(out_file, ".ruledash %s %s\n", fixed(dash1), fixed(dash2));
  setdash1 = dash1;
  setdash2 = dash2;
  }
}



/*************************************************
*             Draw an arrow head                 *
*************************************************/

static void
arrowhead(unit x, unit y, unit xx, unit yy, double angle, colour filled)
{
double s = sin(angle);
double c = cos(angle);

unit x1 = (unit)((double)yy*s*0.5);
unit y1 = (unit)((double)yy*c*0.5);
unit x2 = (unit)((double)xx*c);
unit y2 = (unit)((double)xx*s);

if (filled.red != unfilled.red)
  {
  colour savecolour = setcolour;
  set_colour(filled);
  fprintf(out_file, "$sf");
  move(x, y);
  rline(x1, -y1);
  rline(x2 - x1, y2 + y1);
  rline(-x2 -x1, y1 - y2);
  rline(x1, -y1);
  fprintf(out_file, "$sf");
  home();
  set_colour(savecolour);
  }

set_thickness(400);
move(x, y);
rline(x1, -y1);
rline(x2 - x1, y2 + y1);
rline(-x2 -x1, y1 - y2);
rline(x1, -y1);
home();
}


/*************************************************
*            Draw a circular arc                 *
*************************************************/

/* Called from below to draw an arc < 2.0*pi from current position. For
anticlockwise arcs, angle2 > angle2; and for clockwise arcs, angle2 < angle1. */

static void arc(unit clockwise, unit x, unit y, unit radius1, unit radius2,
  double angle1, double angle2, BOOL domove)
{
if (!clockwise)
  {
  while (angle1 > angle2) angle2 += 2.0*pi;
  if (domove) move(x + (unit)((double)radius1 * cos(angle1)),
                   y + (unit)((double)radius2 * sin(angle1)));
  while (angle2 - angle1 > 0.5*pi)
    {
    smallarc(radius1, radius2, angle1, angle1 + 0.49*pi, rbezier);
    angle1 += 0.49*pi;
    linesplit();
    }
  }

else
  {
  while (angle1 < angle2) angle2 -= 2.0*pi;
  if (domove) move(x + (unit)((double)radius1 * cos(angle1)),
                   y + (unit)((double)radius2 * sin(angle1)));
  while (angle1 - angle2 > 0.5*pi)
    {
    smallarc(radius1, radius2, angle1, angle1 - 0.49*pi, rbezier);
    angle1 -= 0.49*pi;
    linesplit();
    }
  }

smallarc(radius1, radius2, angle1, angle2, rbezier);
}


/*************************************************
*             Write strings                      *
*************************************************/

static void write_strings(item *p)
{
stringchain *s = p->strings;
unit x;
unit y;

if (s == NULL) return;  /* There are no strings */
stringpos(p, &x, &y);

for (;;)
  {
  uschar *ss = (s->justify == just_left)? US" " :
    (s->justify == just_right)? US"$E" : US"$C";
  if (s->text[0] != 0)
    {
    uschar *t = s->text;
    move(x + s->xadjust, y + s->yadjust);
    fprintf(out_file, "%s", ss);
    while (*t != 0)
      {
      int c;
      GETCHARINC(c, t);
      fprintf(out_file, "%c", (c < 256)? c : '?');
      }
    fprintf(out_file, "\n");   
    }
     
  s = s->next;
  if (s == NULL) break; 
  y -= find_linedepth(p, s);
  }
}


/*************************************************
*               Write an arc                     *
*************************************************/

static void write_arc(item_arc *p, BOOL first_filling)
{
double radius = (double)p->radius;
double angle1 = p->angle1;
double angle2 = p->angle2;

if (line_filling.red != unfilled.red)
  {
  arc(p->cw, p->x, p->y, p->radius, p->radius, angle1, angle2, first_filling);
  linesplit();
  }

else
  {
  /* Must set colour in case arrowheads */

  set_colour(p->colour);

  /* Draw the arrow heads as necessary */

  if (p->arrow_start || p->arrow_end)
    {
    double tilt = asin((double)(p->arrow_x) / (2.0*radius));

    set_dash(0, 0);

    if (p->arrow_start)
      {
      double angle = (p->cw)? (angle1 + pi/2.0 + tilt) : (angle1 - pi/2.0 - tilt);
      arrowhead(p->x + (unit)(radius * cos(angle1)), p->y + (unit)(radius * sin(angle1)),
        p->arrow_x, p->arrow_y, angle, p->arrow_filled);
      }

    if (p->arrow_end)
      {
      double angle = (p->cw)? (angle2 - pi/2.0 - tilt) : (angle2 + pi/2.0 + tilt);
      arrowhead(p->x + (unit)(radius * cos(angle2)), p->y + (unit)(radius * sin(angle2)),
        p->arrow_x, p->arrow_y, angle, p->arrow_filled);
      }
    }

  /* Draw the arc unless invisible */

  if (p->style != is_invi)
    {
    set_thickness(p->thickness);
    set_dash(p->dash1, p->dash2);
    arc(p->cw, p->x, p->y, p->radius, p->radius, angle1, angle2, TRUE);
    home();
    }
  }

write_strings((item *)p);
}



/*************************************************
*              Write a box                       *
*************************************************/

static void write_box(item_box *p)
{
int i;
unit x = p->x;
unit y = p->y;
unit width = p->width;
unit depth = p->depth;

/* Loop for (0) filling the box and (1) outlining the box */

for (i = 0; i < 2; i++)
  {
  int thickness = p->thickness;
  if (i == 0)
    {
    if (p->shapefilled.red == unfilled.red) continue;
    set_colour(p->shapefilled);
    fprintf(out_file, "$sf");
    }
  else
    {
    if (p->style == is_invi) continue;
    set_thickness(thickness);
    set_dash(p->dash1, p->dash2);
    set_colour(p->colour);
    }

  switch(p->boxtype)
    {
    case box_box:
    move(x - width/2, y - depth/2);

    /* This was the way we used to do it. This works with PostScript,
    but wasn't easy to handle with sgpoint.

    rline(width, 0);
    rline(0, -thickness/2);
    rline(0, depth + thickness);
    rline(0, -thickness/2);
    rline(-width, 0);
    rline(0, thickness/2);
    rline(0, -(depth + thickness));
    */

    /* This approach draws each corner adjustment as a pair of separate
    short lines. These can be detected by sgpoint and skipped (it does its own
    pixel-based corner adjustments). */

    rline(width, 0);
    rline(0, -thickness/2);
    rline(0, +thickness/2);
    rline(0, depth);
    rline(+thickness/2, 0);
    rline(-thickness/2, 0);
    rline(-width, 0);
    rline(0, +thickness/2);
    rline(0, -thickness/2);
    rline(0, -depth);
    rline(-thickness/2, 0);
    rline(+thickness/2, 0);
    rline(width, 0);

    break;

    case box_circle:
    case box_ellipse:
    arc(FALSE, p->x, p->y, p->width/2, p->depth/2, 0.0, 2.0*pi, TRUE);
    break;
    }

  if (i == 0)
    {
    fprintf(out_file, "$sf");
    }

  home();
  }

write_strings((item *)p);
}



/*************************************************
*               Write a line                     *
*************************************************/

static void write_line(item_line *p, BOOL first_filling)
{
double angle = 0.0;
unit x1 = p->x, y1 = p->y;
unit xw = p->width, yd = p->depth;
unit xx = 0, yy = 0;

/* Filling: just do the line */

if (line_filling.red != unfilled.red)
  {
  if (first_filling) move(x1, y1);
  rline(xw, yd);
  linesplit();
  }

/* Not filling; arrange to draw the line before any arrow heads so that
a forward arrow joined onto a previous line gets the benefit of appropriate
corner processing in sgpoint. */

else
  {
  /* Set colour early, for arrowheads */

  set_colour(p->colour);

  /* If this is an arrow, compute data for arrow heads */

  if (p->arrow_start || p->arrow_end)
    {
    set_dash(0, 0);
    angle = atan2((double)p->depth, (double)p->width);
    xx = (unit)(((double)p->arrow_x) * cos(angle));
    yy = (unit)(((double)p->arrow_x) * sin(angle));
    }

  /* Now adjust the line according to the arrow heads */

  if (p->arrow_start)
    {
    x1 += xx;
    y1 += yy;
    xw -= xx;
    yd -= yy;
    }

  if (p->arrow_end)
    {
    xw -= xx;
    yd -= yy;
    }

  /* Now draw the line unless it is invisible. If it is horizontal or vertical,
  and joins at right angles to the previous line, output some little lines to
  sort out the corner. These are ignored by sgpoint, which does its own
  corners, but they are needed for PostScript. Note that the box drawer does
  its own thing about this. */

  if (p->style != is_invi)
    {
    set_thickness(p->thickness);
    set_dash(p->dash1, p->dash2);
    move(x1, y1);

    if (setthickness != 0 && setthickness == last_line_thickness &&
        x1 == last_xend && y1 == last_yend)
      {
      /* This is vertical; last was horizontal */
      if (xw == 0 && last_ystart == last_yend)
        {
        if (yd > 0)      /* going up */
          {
          rline(0, -setthickness/2);
          rline(0, +setthickness/2);
          }
        else            /* going down */
          {
          rline(0, +setthickness/2);
          rline(0, -setthickness/2);
          }
        }

      /* This is horizontal; last was vertical */
      else if (yd == 0 && last_xstart == last_xend)
        {
        if (xw > 0)      /* going right */
          {
          rline(-setthickness/2, 0);
          rline(+setthickness/2, 0);
          }
        else
          {
          rline(+setthickness/2, 0);
          rline(-setthickness/2, 0);
          }
        }
      }

    /* Now draw the real line */

    rline(xw, yd);
    home();

    /* Remember what we did */

    last_line_thickness = setthickness;
    last_xstart = x1;
    last_ystart = y1;
    last_xend = x1 + xw;
    last_yend = y1 + yd;

    /* Now draw the arrow heads if required */

    if (p->arrow_start)
      arrowhead(x1, y1, p->arrow_x, p->arrow_y, angle + pi, p->arrow_filled);

    if (p->arrow_end)
      arrowhead(x1 + xw, y1 + yd, p->arrow_x, p->arrow_y, angle, p->arrow_filled);
    }

  /* For an invisible line, ensure that there is no "last line" set */

  else last_line_thickness = -999999;
  }

/* Output associated strings */

write_strings((item *)p);
}



/*************************************************
*               End line filling                 *
*************************************************/

/* End the filling, and draw those outlines that are required by re-processing
the line items, forcibly not filled. */

static void end_line_filling(item *fillstart, item *fillend)
{
item *p;
fprintf(out_file, "$sf");
line_filling = unfilled;
home();

for (p = fillstart; p != fillend; p = p->next)
  {
  p->shapefilled = unfilled;
  if (p->type == i_arc)
    write_arc((item_arc *)p, FALSE);
  else if (p->type == i_line)
    write_line((item_line *)p, FALSE);
  }
}



/*************************************************
*                Write one item                  *
*************************************************/

static void
write_item(item *p, item **fillstartp)
{
switch (p->type)
  {
  case i_arc:
  case i_line:
  if (line_filling.red != unfilled.red &&
        (
        line_filling.red != p->shapefilled.red ||
        line_filling.green != p->shapefilled.green ||
        line_filling.blue != p->shapefilled.blue
        )
      )
    end_line_filling(*fillstartp, p);     /* End or change of filling */
  if (line_filling.red == unfilled.red &&
      p->shapefilled.red != unfilled.red)
    {
    set_colour(p->shapefilled);
    fprintf(out_file, "$sf");
    line_filling = p->shapefilled;
    first_filling = TRUE;
    *fillstartp = p;
    }
  else
    {
    first_filling = FALSE;
    }
  if (p->type == i_arc)
    {
    write_arc((item_arc *)p, first_filling);
    last_line_thickness = -999999;
    }
  else
    write_line((item_line *)p, first_filling);
  break;

  case i_box:
  if (line_filling.red != unfilled.red) end_line_filling(*fillstartp, p);
  write_box((item_box *)p);
  last_line_thickness = -999999;
  break;

  case i_text:
  if (line_filling.red != unfilled.red) end_line_filling(*fillstartp, p);
  write_strings(p);
  last_line_thickness = -999999;
  break;

  case i_wait:
  fprintf(out_file, ".wait\n");
  break;
  }
}


/*************************************************
*                 Write output file              *
*************************************************/

void write_sg(void)
{
int level;

setthickness = 0;
setcolour = black;
line_filling = unfilled;
setdash1 = 0;

last_xstart = 999999;
last_ystart = 999999;
last_xend   = 999999;
last_yend   = 999999;
last_line_thickness = -999999;

/* Find the bounding box */

find_bbox(bbox);

/* Output header material */

fprintf(out_file, ".newline\n.push\n.linedepth 0\n.parindent 0\n.newpar\n");

/* Output a downward space. Because SGCAL can stretch vertical space downwards
to fill pages, we output half the space now, and operate relative to the
halfway vertical coordinate. Then we output the final space at the end. */

halfdepth = (bbox[3] - bbox[1])/2;
fprintf(out_file, ".space %s\n", fixed(rnd(halfdepth)));

/* Draw a frame if wanted */

if (drawbboxthickness > 0)
  {
  unit adjust = 0;
  unit width = bbox[2] - bbox[0];
  unit depth = bbox[3] - bbox[1];

  if (drawbboxfullwidth)
    {
    adjust = (centrepic - width)/2;
    width = centrepic;
    }

  set_thickness(drawbboxthickness);
  set_colour(drawbboxcolour);
  move(bbox[0] - adjust, bbox[1]);
  rline(width, 0);
  rline(0, depth);
  rline(-width, 0);
  rline(0, -depth);
  home();
  }

/* Now process the items, repeating for each level */

for (level = min_level; level <= max_level; level++)
  {
  item *p = main_item_base;
  item *fillstart = NULL;

  while (p != NULL)
    {
    if (p->level == level)
      {
      if (p->type == i_redraw)
         {
         item_redraw *r = (item_redraw *)p;
         item *rd = r->ref;

         if (!samecolour(r->colour, unfilled)) rd->colour = r->colour;
         if (!samecolour(r->shapefilled, unfilled))
           rd->shapefilled = r->shapefilled;

         switch(rd->type)
           {
           case i_arc:
           if (!samecolour(r->filled, unfilled))
             ((item_arc *)rd)->arrow_filled = r->filled;
           break;

           case i_line:
           if (!samecolour(r->filled, unfilled))
             ((item_line *)rd)->arrow_filled = r->filled;
           break;

           case i_box:
           if (!samecolour(r->filled, unfilled))
             ((item_box *)rd)->shapefilled = r->filled;
           break;

           case i_text:
           break;
           }

         write_item(rd, &fillstart);
         }
      else write_item(p, &fillstart);
      }

    /* If hit an item at another level, end line filling */

    else if (line_filling.red != unfilled.red) end_line_filling(fillstart, p);

    p = p->next;
    }

  /* End final line filling item */

  if (line_filling.red != unfilled.red) end_line_filling(fillstart, NULL);
  }

/* Output the final half space. We also add just a little bit to
leave some white space at the end. */

fprintf(out_file, ".space %s\n", fixed(rnd(halfdepth + 10000)));

/* Output the tail */

fprintf(out_file, ".newline\n.pop\n.space 1ld\n");
}

/* End of aswrsg.c */
