/*  
 *  This is a set of routines for dvips that are used to process color 
 *  commands in the TeX file (passed by \special commands).  This was
 *  orignally written by J. Hafner, E. Blanz and M. Flickner of IBM
 *  Research, Almaden Research Center.  Contact hafner@almaden.ibm.com.
 *  And then it was completely rewritten by Tomas Rokicki to:
 *
 *      - Be easier on the memory allocator (malloc/free)
 *      - Use less memory overall (by a great deal) and be faster
 *      - Work with the -C, -a, and other options
 *      - Be more adaptable to other drivers and previewers.
 *
 *  The motivating idea:  we want to be able to process 5,000 page
 *  documents using lots of colors on each page on a 640K IBM PC
 *  relatively quickly.
 */
#include "dvips.h" /* The copyright notice in that file is included too! */
#include <stdio.h>
/*
 *   Externals we use.
 */
#include "protos.h"
extern integer pagenum ;
extern FILE *dvifile ;
/*
 *   Here we set some limits on some color stuff.
 */
#define COLORHASH (89)
#define MAXCOLORLEN (120)     /* maximum color length for background */
#define INITCOLORLEN (3000)   /* initial stack size in chars */
/*
 *   This is where we store the color information for a particular page.
 *   If we free all of these, we free all of the allocated color
 *   stuff; we do no allocations (but one) other than when we allocate
 *   these.
 */
static struct colorpage {
   struct colorpage *next ;
   integer boploc ; /* we use the bop loc as a page indicator */
   char *bg ;
   char colordat[2] ;
} *colorhash[COLORHASH] ;
static char *cstack, *csp, *cend, *bg ;
/*
 *   This routine sends a color command out.  If the command is a
 *   single `word' or starts with a double quote, we send it out
 *   exactly as is (except for any double quote.)  If the command
 *   is a word followed by arguments, we send out the arguments and
 *   then the word prefixed by "TeXcolor".
 */
void colorcmdout P1C(char *, s)
{
   char *p ;
   char tempword[100] ;

   while (*s && *s <= ' ')
      s++ ;
   if (*s == '"') {
      cmdout(s+1) ;
      return ;
   }
   for (p=s; *p && *p > ' '; p++) ;
   for (; *p && *p <= ' '; p++) ;
   if (*p == 0) {
      cmdout(s) ;
      return ;
   }
   cmdout(p) ;
   strcpy(tempword, "TeXcolor") ;
   for (p=tempword + strlen(tempword); *s && *s > ' '; p++, s++)
      *p = *s ;
   *p = 0 ;
   cmdout(tempword) ;
   return ;
}
/*
 *   For a new dvi file, call this.  Frees all allocated memory.
 */
#define DEFAULTCOLOR "Black"
void initcolor() {
   int i ;
   struct colorpage *p, *q ;

   for (i=0; i<COLORHASH; i++) {
      for (p=colorhash[i]; p; p = q) {
         q = p->next ;
         free(p) ;
      }
      colorhash[i] = 0 ;
   }
   cstack = (char *)mymalloc(INITCOLORLEN) ;
   strcpy(cstack, "\n") ;
   strcat(cstack, DEFAULTCOLOR) ;
   csp = cstack + strlen(cstack) ;
   cend = cstack + INITCOLORLEN - 3 ; /* for conservativeness */
   bg = 0 ;
}
/*
 * This takes a call from predospecial to set the background color for
 * the current page.  It is saved in stackdepth and backed down the
 * stack during popcolors.
 */
void
background P1C(char *, bkgrnd)
{
   if (bkgrnd && *bkgrnd) {
      if (strlen(bkgrnd) > MAXCOLORLEN)
         error(" color name too long; ignored") ;
      else
         strcpy(bg, bkgrnd) ;
   }
}
/*
 * This routine puts a call from \special for color on the colorstack
 * and sets the color in the PostScript.
 */
void
pushcolor P2C(char *, p, Boolean, outtops)
{
   while (strlen(p) + csp > cend) {
      int newlen = 3 * (cend - cstack) ;
      char *newcs = (char *)mymalloc(newlen) ;
      strcpy(newcs, cstack) ;
      csp = newcs + (csp - cstack) ;
      cend = newcs + newlen - 3 ;
      cstack = newcs ;
   }
   *csp++ = '\n' ;
   strcpy(csp, p) ;
   csp += strlen(p) ;
   if (outtops) {
      colorcmdout(p) ;
   }
}
/*
 * This routine removes a color from the colorstack and resets the color
 * in the PostScript to the previous color.
 */
void
popcolor P1C(Boolean, outtops)
{
   char *p = csp - 1 ;

   while (p >= cstack && *p != '\n')
      p-- ;
   if (p == cstack)
      return ;  /* We don't pop the last color as that is global */
   *p = 0 ;
   csp = p ;
   for (p--; p >= cstack && *p != '\n'; p--) ;
   p++ ;
   if ( outtops ) {
      colorcmdout(p) ;
   }
}
/*
 * This routine clears the colorstack, pushes a new color onto the stack
 * (this is now the root or global color).
 */
void
resetcolorstack P2C(char *, p, int, outtops)
{
   char *q = csp - 1 ;

   while (q > cstack && *q != '\n')
      q-- ;
   if (q > cstack && outtops == 0) {
#ifdef SHORTINT
     (void)fprintf(stderr, "You've mistakenly made a global color change ") ;
     (void)fprintf(stderr, "to %s within nested colors\n", p) ;
     (void)fprintf(stderr, "on page %ld. Will try to recover.\n", pagenum) ;
#else   /* ~SHORTINT */
     (void)fprintf(stderr, "You've mistakenly made a global color change ") ;
     (void)fprintf(stderr, "to %s within nested colors\n", p) ;
     (void)fprintf(stderr, "on page %d. Will try to recover.\n", pagenum) ;
#endif  /* ~SHORTINT */
   }
   csp = cstack ;
   *csp = 0 ;
   pushcolor(p, outtops) ;
}
/*
 *   This routine is a bit magic.  It looks up the page in the current
 *   hash table.  If the page is already entered in the hash table, then
 *   it restores the color to what that page had, and sets the last
 *   color.  This will occur if this is not the first time that this
 *   page has been encountered.
 *
 *   If, on the other hand, this is the first time that the page has
 *   been encountered, then it will create a new hash entry and copy the
 *   current color information into it.  Since we can only encounter a
 *   new page after having just finished scanning the previous page,
 *   this is safe.
 */
void
bopcolor P1C(int, outtops)
{
   integer pageloc = ftell(dvifile) ;
   int h = pageloc % COLORHASH ;
   struct colorpage *p = colorhash[h] ;

   while (p) {
      if (p->boploc == pageloc)
         break ;
      else
         p = p->next ;
   }
   if (p) {
      strcpy(cstack, p->colordat) ;
      csp = cstack + strlen(cstack) ;
      bg = p->bg ;
      if (outtops && strcmp(bg, "White")!=0 && bg[0]) {
         cmdout("gsave") ;
         colorcmdout(bg) ;
         cmdout("clippath fill grestore") ;
      }
   } else {
      p = (struct colorpage *)mymalloc((integer)
                  (strlen(cstack) + sizeof(struct colorpage) + MAXCOLORLEN)) ;
      p->next = colorhash[h] ;
      p->boploc = pageloc ;
      strcpy(p->colordat, cstack) ;
      p->bg = p->colordat + strlen(cstack) + 1 ;
      if (bg)
         strcpy(p->bg, bg) ;
      else
         *(p->bg) = 0 ;
      bg = p->bg ;
      colorhash[h] = p ;
   }
   if (outtops) {
      char *pp = csp - 1 ;
      while (pp >= cstack && *pp != '\n')
         pp-- ;
      pp++ ;
      if (strcmp(pp, DEFAULTCOLOR)!=0) {
         colorcmdout(pp) ;
      }
   }
}
