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

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


#include "aspic.h"




/*************************************************
*           Compare two colours                  *
*************************************************/

BOOL
samecolour(colour a, colour b)
{
return a.red == b.red && a.green == b.green && a.blue == b.blue;
}



/*************************************************
*         Find a line depth for a string         *
*************************************************/

/* The line depth is the greater of the value set in the item from the 
environment when it was read, and the size of the font for the string.

Arguments:
  p         the item to which the string is attached
  s         the string  
   
Returns:    the line depth
*/

unit
find_linedepth(item *p, stringchain *s)
{
int fontsize = 12000;    /* default */
bindfont *b;
for (b = font_base; b != NULL; b = b->next)
  {
  if (b->number == s->font)
    {
    fontsize = b->size;
    break;
    }
  }
return (fontsize > p->linedepth)? fontsize: p->linedepth;
}



/*************************************************
*         Find a font depth for a string         *
*************************************************/

/* The font depth is the greater of the value set in the item from the 
environment when it was read, and half the size of the font for the string.

Arguments:
  p         the item to which the string is attached
  s         the string  
   
Returns:    the font depth
*/

unit
find_fontdepth(item *p, stringchain *s)
{
int fontdepth = 6000;    /* default */
bindfont *b;
for (b = font_base; b != NULL; b = b->next)
  {
  if (b->number == s->font)
    {
    fontdepth = b->size/2;
    break;
    }
  }
 
return (fontdepth > p->fontdepth)? fontdepth : p->fontdepth;
}



/*************************************************
*          Determine the bounding box            *
*************************************************/

static void 
setbbox(unit *box, unit x, unit y, unit w, unit d)
{
unit xx = x + w;
unit yy = y + d;

if (x > xx) { unit temp = x; x = xx; xx = temp; }
if (y > yy) { unit temp = y; y = yy; yy = temp; }

if (x  < box[0]) box[0] = x;
if (y  < box[1]) box[1] = y;
if (xx > box[2]) box[2] = xx;
if (yy > box[3]) box[3] = yy;
}


/* This is the function that is called from outside.

Argument:   pointer to a vector of 4 units
Returns:    nothing
*/

void 
find_bbox(unit *box)
{
item *p;
unit x, y;

box[0] = box[1] = UNIT_MAX;
box[2] = box[3] = UNIT_MIN;

for (p = main_item_base; p != NULL; p = p->next)
  {
  if (p->style == is_invi &&
      p->strings == NULL &&
      samecolour(p->shapefilled, unfilled))
    continue;      
     
  switch (p->type)
    {
    case i_arc:
      {
      item_arc *pp = (item_arc *)p;
      unit bx, by, cx, cy;
      double a1 = pp->angle1;
      double a2 = pp->angle2;

      if (pp->cw) { double temp = a1; a1 = a2; a2 = temp; }

      /* Arrange that a1 <= a2 and 0 <= a2 <= 2*pi and -2*pi <= a1 <= 2*pi */

      while (a1 > a2) a2 += 2.0*pi;
      while (a2 > 2.0*pi) { a1 -= 2.0*pi; a2 -= 2.0*pi; }
      while (a2 < 0) { a1 += 2.0*pi; a2 += 2.0*pi; }

      bx = (unit)((double)(pp->radius) * cos(a1));
      by = (unit)((double)(pp->radius) * sin(a1));
      cx = (unit)((double)(pp->radius) * cos(a2));
      cy = (unit)((double)(pp->radius) * sin(a2));

      if (a1 < 0)   /* We know a2 must be > 0 */
	{
	if (bx < cx) cx = bx;
	bx = pp->radius;
	}

      if ((a1 < -pi) || (a1 < pi && a2 > pi))
	{
	if (cx > bx) bx = cx;
	cx = -(pp->radius);
	}

      if ((a1 < -1.5*pi) || (a1 < 0.5*pi && a2 > 0.5*pi))
	{
	if (by < cy) cy = by;
	by = pp->radius;
	}

      if ((a1 < -0.5*pi) || (a1 < 1.5*pi && a2 > 1.5*pi))
	{
	if (cy > by) by = cy;
	cy = -(pp->radius);
	}

      setbbox(box, pp->x + bx, pp->y + by, cx - bx, cy - by);
      }
    break;

    case i_box:
      {
      item_box *pp = (item_box *)p;
      unit width = pp->width + pp->thickness;
      unit depth = pp->depth + pp->thickness;
      unit bx = pp->x - width/2 - pp->thickness/2;
      unit by = pp->y - depth/2 - pp->thickness/2;
      setbbox(box, bx, by, width, depth);
      }
    break;

    case i_line:
      {
      item_line *pp = (item_line *)p;
      unit x = pp->x;
      unit y = pp->y;
      unit width = pp->width;
      unit depth = pp->depth;
      unit t = pp->thickness; 
      
      if (width == 0) { x -= t/2; width = t; }             /* Vertical line */
      if (depth == 0) { y -= t/2; depth = t; }             /* Horizontal line */
 
      if (pp->arrow_start || pp->arrow_end)                /* For horizontal or vertical    */
	{						   /* arrows, we include the head   */
	unit ww = pp->arrow_y;				   /* because that is simple to do. */
	if (width == 0) { x -= ww/2; width = ww; }	   /* In fact, the arrow head will  */
	  else if (depth == 0) { y -= ww/2; depth = ww; }  /* rarely affect the bounding    */
	}						   /* box in any case.		    */
      setbbox(box, x, y, width, depth);
      }
    break;

    case i_text:
    case i_wait:
    case i_redraw:
    break;  
    }
    
  /* Make an attempt to allow for strings that might stick out of the boundary 
  of the existing bbox. We don't know the actual width of strings. A reasonable 
  guess is to use half the font size for each character width. */
  
  if (p->strings != NULL)
    { 
    stringchain *s = p->strings;
    stringpos(p, &x, &y);      /* Find the position for the strings */
    
    for (;;)
      {
      bindfont *f; 
      int len = s->chcount;
      int depth = p->fontdepth;
       
      for (f = font_base; f != NULL; f = f->next)
        { if (f->number == s->font) break; }  
        
      if (f != NULL)
        { 
        len *= f->size/2;
        if (depth < f->size/2) depth = f->size/2;
        }
      else len *= 6000;   /* Unbound font (should not occur) */
      
      setbbox(box, x - ((s->justify == just_centre)? len/2 :
                        (s->justify == just_right)? len : 0),
                   y, len, depth);       
           
      s = s->next;
      if (s == NULL) break;     
      y -= find_linedepth(p, s);
      }
    }   
  }
  
/* Addjust the values if a frame is required */ 
  
if (drawbboxthickness > 0)
  {
  box[0] -= drawbbox;
  box[1] -= drawbbox;
  box[2] += drawbbox;
  box[3] += drawbbox;
  }
}



/*************************************************
*           Round dimension to resolution        *
*************************************************/

/* The resolution can be changed per file, and defaults differently for 
different output formats.

Arguments:
  value       the dimension to be rounder
  
Returns:      the rounded dimension
*/ 
 
unit 
rnd(unit value)
{
int sign = (value < 0)? (-1) : (+1);
udiv_t split = udiv(uabs(value), resolution);
if (split.rem > resolution/2) split.quot++;
return split.quot * resolution * sign;
}
 
 
 
/*************************************************
*           Coordinate to fixed point string     *
*************************************************/

/* This function converts a dimension into a fixed-point string. A large 
circular buffer is used so that several results can be in existence at once 
(e.g. several values in a single fprintf() call). 

Arguments:
 x             the dimension
 
Returns:       pointer to a string
*/ 

static uschar fixed_buffer[200];
static int    fixed_ptr = 0;

uschar *
fixed(unit x)
{
uschar *p = fixed_buffer + fixed_ptr;
int n = 0;
if (x < 0) { *p = '-'; x = -x; n++; }
n = sprintf(CS(p + n), "%" U_FORMAT, x/1000);
n = (int)(x%1000);
if (n) sprintf(CS(p + Ustrlen(p)), ".%03d", n);
fixed_ptr += 20;
if (fixed_ptr >= 200) fixed_ptr = 0;
return p;
}




/*************************************************
*            Draw an elliptical arc                 *
*************************************************/

/* Called to draw an arc < 2.0*pi from current position. For anticlockwise
arcs, angle2 > angle2; and for clockwise arcs, angle2 < angle1. The function to 
do the actual output is an argument.

Arguments:
  radius1       x "radius"
  radius2       y "radius"
  angle1        start angle
  angle2        end angle
  rbezier       callback function do do the output    
  
Returns:        nothing 
*/

void 
smallarc(unit radius1, unit radius2, double angle1, double angle2,
  void (*rbezier)(unit, unit, unit, unit, unit, unit))
{
double r1 = (double) radius1;
double r2 = (double) radius2;
double x0, x1, x2, x3, xp, xq;
double y0, y1, y2, y3, yp, yq;
double arp, arq;
double ax, bx, cx, ay, by, cy;

arp = 0.6667*angle1 + 0.3333*angle2;
arq = 0.3333*angle1 + 0.6667*angle2;

x0 = r1 * cos(angle1);
y0 = r2 * sin(angle1);

x3 = r1 * cos(angle2);
y3 = r2 * sin(angle2);

xp = r1 * cos(arp);
yp = r2 * sin(arp);

xq = r1 * cos(arq);
yq = r2 * sin(arq);

cx = (18.0*xp - 9.0*xq + 2.0*x3 - 11.0*x0)/2.0;
bx = (-45.0*xp + 36.0*xq - 9.0*x3 + 18.0*x0)/2.0;
ax = x3 - x0 - cx - bx;

x1 = x0 + cx/3.0;
x2 = x1 + (cx+bx)/3.0;

cy = (18.0*yp - 9.0*yq + 2.0*y3 - 11.0*y0)/2.0;
by = (-45.0*yp + 36.0*yq - 9.0*y3 + 18.0*y0)/2.0;
ay = y3 - y0 - cy - by;

y1 = y0 + cy/3.0;
y2 = y1 + (cy+by)/3.0;

rbezier((unit)(x1-x0), (unit)(y1-y0), (unit)(x2-x0), (unit)(y2-y0), (unit)(x3-x0), (unit)(y3-y0));
}



/*************************************************
*        Find position for strings               *
*************************************************/

/* The position depends on the item type to which the strings are attached.

Arguments:
  p             the item
  xx            where to return the x coordinate
  yy            where to return the y coordinate  
*/

void 
stringpos(item *p, unit *xx, unit *yy)
{
stringchain *s, *ss;
unit fontdepth = p->fontdepth;
unit x = p->x;
unit y = p->y;
int n = 0;
int i;

/* Find the number of strings */

for (s = p->strings; s != NULL; s = s->next) n++;

/* Find the "middle" string */

for (s = p->strings, i = 1; i < (n + 1)/2; s = s->next, i++);

/* Sort out the position for strings on a line */

if (p->type == i_line)
  {
  item_line *pp = (item_line *)p; 
  x += pp->width/2;
  
  /* Horizontal line */
 
  if (pp->depth == 0)
    y += (n == 1)? 2000 : (find_linedepth(p, s)/2 - find_fontdepth(p, s)/2);

  /* Non-horizontal line */
   
  else 
    {
    x += 3000;         /* Move a little bit away */ 
    y += pp->depth/2 - find_fontdepth(p, s)/2;
    if ((n & 1) == 0) y += find_linedepth(p, s)/2;
    } 
  }

/* Sort out the position for strings on an arc */

else if (p->type == i_arc)
  {
  item_arc *arc = (item_arc *)p;
  double radius = (double)(arc->radius);
  double angle = (arc->angle1 + arc->angle2)/2.0;

  if (arc->angle1 > arc->angle2) angle += pi;
  if (arc->cw) angle += pi;

  x += (unit)(radius * cos(angle)) + 6000;  /* Move a little bit away */
  y += (unit)(radius * sin(angle));
  angle = fabs(angle);
  if (angle > 3.0*pi/8.0 && angle < 5.0*pi/8.0) 
    y += find_fontdepth(p, s)/2 + 2000;
  if ((n & 1) != 0) y -= find_fontdepth(p, s)/2;
  }
  
/* Sort out the position for strings in a closed shape */

else
  {
  y -= find_fontdepth(p, s)/2;
  if ((n & 1) == 0) y += find_linedepth(p, s)/2;
  } 
  
/* The y coordinate is now set for the "middle" string. If there are other 
strings above it, we must move the y coordinate upwards. */ 

for (ss = p->strings; ss != s; ss = ss->next)
  y += find_linedepth(p, ss->next); 

/* Return the coordinates */

*xx = x;
*yy = y;
}

/* End of aswrite.c */
