/*************************************************
*                      ASPIC                     *
*************************************************/
 
/* Copyright (c) University of Cambridge 1991 - 2008 */
/* Created: February 1991 */
/* Last modified: March 2008 */
 
/* ASPIC is an Amazingly Simple PICture composing program. It reads a
description of a "line-art" picture from a file and outputs commands for
another program to draw it. Typically the other program is a text processor,
and sometimes ASPIC can be called dynamically from within such processors,
enabling its input to be embedded within the text processor's source.

Aspic used only to support output for SGCAL. Now it also supports encapsulated 
PostScript and SVG (Scalable Vector Graphics), and the SGCAL support has become 
a legacy feature. */
 
 
#include "aspic.h"
 
 

/*************************************************
*                 Global variables               *
*************************************************/
 
FILE *main_input;
FILE *out_file;

includestr *included_from = NULL; /* chain for included files */
uschar **file_line_stack;         /* line stack for included files */
int  *file_chptr_stack;           /* chptr stack ditto */
int inc_stack_ptr = 0;            /* stack position */
 
item *main_item_base;
 
double pi;
colour black;
colour unfilled;

bindfont *font_base = NULL;    /* chain of font bindings */
BOOL use_fonts = TRUE;         /* expect to use fonts */
BOOL translate_chars = FALSE;  /* don't translate by default */
 
uschar **in_line_stack;        /* stack of pointers to saved in_lines */
int  *chptr_stack;	       /* stack of saved chptrs */
int  max_level = 0;            /* uppermost level */
int  min_level = 0;            /* lowermost level */
int  *mac_count_stack;
int  mac_stack_ptr = 0;        /* the stack position */
int  macro_count;	       /* for generating id's */
int  macro_id;
unit minimum_thickness = 0;    /* this is ok for SGCAL & PS */
BOOL no_variables = FALSE;     /* variables are available by default */
int  outstyle = OUT_UNSET;     /* output style */
unit resolution = 1;           /* default resolution is now exact */
BOOL strings_exist = FALSE;    /* at least one item has a string */

unit joined_xx;                /* Coordinates of explicit join position */
unit joined_yy; 
 
macro *macroot = NULL;         /* root of all macros */
macro *macactive = NULL;       /* chain of active macros */

BOOL started = FALSE;          /* done init */
BOOL testing = FALSE;          /* suppress version/date in output */

tree_node *varroot = NULL;     /* root of variables tree */


/*************************************************
*                Error messages                  *
*************************************************/
 
static uschar *error_messages[] = {
  US"Unrecognized command line option \"%s\"",              /* 0 */ 
  US"Failed to open %s for %s: %s",                         /* 1 */
  US"Unknown aspic command \"%s\"",                         /* 2 */
  US"Semicolon expected (unexpected text follows command)", /* 3 */
  US"Font %d has not been bound",                           /* 4 */ 
  US"Font number must be > 0 and < 64",                     /* 5 */ 
  US"Unknown%s variable \"%s\"",                            /* 6 */  
  US"Unknown option word \"%s\"",                           /* 7 */
  US"Dimension expected",                                   /* 8 */
  US"Label \"%s\" incorrectly placed (can only precede drawing command)", /* 9 */
  US"Can't find item labelled \"%s\"",                      /* 10 */
  US"%s expected",                                          /* 11 */
  US"No previous item",                                     /* 12 */
  US"Inappropriate position descriptor applied to a %s",    /* 13 */
  US"Inappropriate fraction encountered",                   /* 14 */
  US"Unable to get store - %d bytes requested",             /* 15 */
  US"Aspic command word expected",                          /* 16 */
  US"Empty variable name",                                  /* 17 */
  US"No stacked environment to restore",                    /* 18 */
  US"Too many constraints for arc - %s ignored",            /* 19 */
  US"An edge join cannot be explicitly positioned",         /* 20 */
  US"A positioned join must refer to a corner",             /* 21 */
  US"No previous item to join to",                          /* 22 */
  US"\"depth\" or \"via\" for arc given without end point - ignored",  /* 23 */
  US"An arc cannot be constructed using the given via point", /* 24 */
  US"Line too long while substituting \"%s\"",              /* 25 */ 
  US"Line too long after substitutions",                    /* 26 */ 
  US"Missing } after \"${%s\"",                             /* 27 */ 
  US"Only one of -ps, -sgcal, or -svg is allowed",          /* 28 */ 
  US"File name expected",                                   /* 29 */ 
  US"\"include\" is not allowed in a macro"                 /* 30 */ 
  };
 
 
 
/*************************************************
*            Supply missing div() if needed      *
*************************************************/
 
#ifdef __needs_div__
 
udiv_t udiv(unit numer, unit denom)
{
udiv_t div_yield;
div_yield.quot = numer / denom;
div_yield.rem  = numer % denom;
return div_yield;
}
 
#endif
 
 
 
/*************************************************
*                Error routine                   *
*************************************************/

/* Error messages go to stderr, with information about the line at fault
if there is one. Give up if not yet fully initialized.

Arguments:
  n            the error number
  ...          substitutions into the message
  
Returns:       nothing
*/  
 
void 
error_moan(int n, ...)
{
unit ptr, i;
va_list ap;
va_start(ap, n);
 
fprintf(stderr, "Aspic: ");
vfprintf(stderr, CS error_messages[n], ap);
fprintf(stderr, "\n");

if (!started) return;

if (chptr > 0)
  {
  ptr = chptr;
  fprintf(stderr, "%s", CS in_line);
  if (in_line[Ustrlen(in_line)-1] != '\n') fprintf(stderr, "\n");
  }
else
  {
  ptr = Ustrlen(in_last);
  fprintf(stderr, "%s", CS in_last);
  if (in_last[ptr-1] != '\n') fprintf(stderr, "\n");
  }

for (i = 0; i < ptr; i++) fprintf(stderr, " ");
fprintf(stderr, "^\n");
 
va_end(ap);
}
 
 
/*************************************************
*           Checked store allocator              *
*************************************************/

/* Calls malloc() and checks the result. Disaster on failure.

Arguments:
  size         store wanted
  
Returns:       pointer to the store
*/ 
 
void 
*getstore(size_t size)
{
void *yield = malloc(size);
if (yield == NULL)
  {
  error_moan(15, size);
  freechain();
  exit(EXIT_FAILURE);
  }
return yield;
}
 
 
 
/*************************************************
*         Set up the timestamp string            *
*************************************************/

/* This is a local function, used to initialize the $date variable.

Arguments:
  timebuf       where to put the output
  size          size of the buffer
  
Returns:        timebuf
*/

static uschar *
time_stamp(uschar *timebuf, int size)
{
int diff_hour, diff_min, len;
time_t now = time(NULL);
struct tm *gmt;
struct tm local;

memcpy(&local, localtime(&now), sizeof(struct tm));
gmt = gmtime(&now);

diff_min = 60*(local.tm_hour - gmt->tm_hour) + local.tm_min - gmt->tm_min;
if (local.tm_year != gmt->tm_year)
  diff_min += (local.tm_year > gmt->tm_year)? 1440 : -1440;
else if (local.tm_yday != gmt->tm_yday)
  diff_min += (local.tm_yday > gmt->tm_yday)? 1440 : -1440;
diff_hour = diff_min/60;
diff_min  = abs(diff_min - diff_hour*60);

len = Ustrftime(timebuf, size, "%a, ", &local);
(void) sprintf(CS timebuf + len, "%02d ", local.tm_mday);
len += Ustrlen(timebuf + len);
len += Ustrftime(timebuf + len, size - len, "%b %Y %H:%M:%S", &local);
(void) sprintf(CS timebuf + len, " %+03d%02d", diff_hour, diff_min);

return timebuf;
}


/*************************************************
*                 Usage                          *
*************************************************/

/* f is either stdout or stderr, depending on whether this is called by
-help or as a result of an error

Arguments:
  f            file on which to write
  
Returns:       nothing
*/ 

static 
void usage(FILE *f)
{
fprintf(f, "Usage:\n");
fprintf(f, "aspic [<options>] [<infile>] [<outfile>]\n\n");
fprintf(f, "Options:\n");
fprintf(f, "  -help          show usage information and exit\n");
fprintf(f, "  -nv            disable variable substitutions\n");
fprintf(f, "  -ps            generate Encapsulated PostScript\n");
fprintf(f, "  -sgcal         generate SGCAL input\n");
fprintf(f, "  -svg           generate SVG\n");
fprintf(f, "  -testing       used by 'make test'\n");
fprintf(f, "  -tr            translate quotes and double-hyphens\n");
fprintf(f, "  -v             show version and exit\n\n");

fprintf(f, "Omit file names or use \"-\" for stdin and stdout.\n");
fprintf(f, "The default output format is PostScript.\n");
fprintf(f, "Only one of -ps, -sgcal, and -sv is permitted.\n");
}



/*************************************************
*		    Entry point			 *
*************************************************/
 
int 
main(int argc, char **argv)
{
tree_node *tn;
uschar timebuf[sizeof("www, dd-mmm-yyyy hh:mm:ss +zzzz")];
int firstarg = 1;       /* points after options */
int yield = EXIT_SUCCESS;

in_raw  = getstore(INPUT_LINESIZE);
in_line = getstore(INPUT_LINESIZE);
in_last = getstore(INPUT_LINESIZE);
in_last[0] = 0;		/* to avoid junk in error messages */

in_line_stack = getstore(MAC_STACKSIZE * sizeof(uschar *));
chptr_stack = getstore(MAC_STACKSIZE * sizeof(int));
mac_count_stack = getstore(MAC_STACKSIZE * sizeof(int));

file_line_stack = getstore(MAC_STACKSIZE * sizeof(uschar *));
file_chptr_stack = getstore(MAC_STACKSIZE * sizeof(int));
 
pi = 4 * atan2(1.0, 1.0);

black.red = black.green = black.blue = 0;
unfilled.red = unfilled.green = unfilled.blue = -1000;

/* Handle options */

while (firstarg < argc && argv[firstarg][0] == '-' && argv[firstarg][1] != 0)
  {
  uschar *arg = US argv[firstarg++];
  if (Ustrcmp(arg, "-nv") == 0)
    no_variables = TRUE;  
  else if (Ustrcmp(arg, "-testing") == 0)
    testing = TRUE;    
  else if (Ustrcmp(arg, "-ps") == 0)    
    { if (outstyle == OUT_UNSET) outstyle = OUT_PS; else error_moan(28); }
  else if (Ustrcmp(arg, "-sgcal") == 0)
    { if (outstyle == OUT_UNSET) outstyle = OUT_SG; else error_moan(28); }
  else if (Ustrcmp(arg, "-svg") == 0)  
    { if (outstyle == OUT_UNSET) outstyle = OUT_SV; else error_moan(28); }
  else if (Ustrcmp(arg, "-tr") == 0)
    translate_chars = TRUE;     
  else if (Ustrcmp(arg, "-v") == 0) 
    {
    printf("\rAspic %s\n", Version_String);
    goto TIDYUP;
    } 
  else if (Ustrcmp(arg, "-help") == 0 || Ustrcmp(arg, "--help") == 0)
    {
    usage(stdout);
    goto TIDYUP;
    } 
  else 
    { 
    error_moan(0, arg); 
    usage(stderr);
    yield = EXIT_FAILURE;
    goto TIDYUP;  
    }  
  } 
  
if (outstyle == OUT_UNSET) outstyle = OUT_PS; 
  
/* The file names are given in the command line. If both are missing, use stdin 
and stdout; if one is given, use it to stdout. The name "-" can also be used to 
mean stdin or stdout. */
 
if (firstarg >= argc || Ustrcmp(argv[firstarg], "-") == 0) 
  {
  main_input = stdin; 
  } 
else
  {
  if ((main_input = fopen(argv[firstarg], "r")) == NULL)
    { 
    error_moan(1, argv[firstarg], "input", strerror(errno)); 
    yield = EXIT_FAILURE;
    goto TIDYUP;  
    }
  }   
  
/* Set up some default value for certain conventional variables */

tn = getstore(sizeof(tree_node) + 7);
Ustrcpy(tn->name, "creator");
tn->value = US"Unknown";
(void)tree_insertnode(&varroot, tn);

tn = getstore(sizeof(tree_node) + 4);
Ustrcpy(tn->name, "date");
tn->value = time_stamp(timebuf, sizeof(timebuf));
(void)tree_insertnode(&varroot, tn);

tn = getstore(sizeof(tree_node) + 5);
Ustrcpy(tn->name, "title");
tn->value = US"Unknown";
(void)tree_insertnode(&varroot, tn);

/* Initialization that depends on the output style */

switch (outstyle)
  {
  case OUT_PS: init_ps(); break;
  case OUT_SG: init_sg(); break;  
  case OUT_SV: init_sv(); break;
  }  
 
/* Process the input and then write the output if successful. */

started = TRUE; 
if (read_inputfile())
  {
  fclose(main_input);
   
  if (firstarg + 1 >= argc || Ustrcmp(argv[firstarg + 1], "-") == 0)
    { 
    out_file = stdout;
    } 
  else
    {
    if ((out_file = fopen(argv[firstarg + 1], "w")) == NULL)
      { 
      error_moan(1, argv[firstarg+1], "output", strerror(errno)); 
      yield = EXIT_FAILURE;
      goto TIDYUP;  
      }
    } 
     
  switch(outstyle)
    {
    case OUT_PS: write_ps(); break;
    case OUT_SG: write_sg(); break;
    case OUT_SV: write_sv(); break;      
    }
    
  fclose(out_file);
  }
 
/* Free up store used, and return */

TIDYUP: 
freechain();
while (env != NULL) { environment *p = env; env = env->previous; free(p); }
return yield;
}
 
/* End of asmain.c */
