/****************************************************************************
 * Webify - 					   3/16/95 Steve Ward	    *
 *     converts Postscript to web-browsable html/gif			    *
 *									    *
 * USAGE:								    *
 *   webify <options> <template-file> <postscript-file-name> [subdir]	    *
 *									    *
 *      <template-file> is a template for the generated HTML page,	    *
 *      <postscript-file-name> is the postscript file from which to	    *
 * 		generate GIFs.						    *
 * 	[subdir] is an optional subdirectory to contain the generated files.*
 * 		It defaults to basname(<postscript-file-name].		    *
 *   <options> may include						    *
 * 	-T #	set Thumbnail resolution (default 10)			    *
 * 	-S #	set Slide resolution (default 50)			    *
 * 	-h	Horizontal (landscape) mode				    *
 * 	-v	Vertical (portrait) mode				    *
 *	-t text	specify title
 ****************************************************************************/

#include <stdio.h>

#define KEYL 40			/* Max length of a keyword		    */

char *TemplateName,		/* Name of html template file		    */
     *PostscriptName,		/* Name of .ps file, sans .ps suffix	    */
     FullPostscriptName[200],	/* Name of .ps file, with .ps suffix	    */
     *Subdir,			/* Directory name prefix, eg "Lect/"	    */
     *HTMLName,			/* Name of HTML file			    */
     *SubDir,			/* Subdirectory for output files.	    */
     *GSName = "gs",		/* Name of Ghostscript command		    */
     *ProgName;			/* Name of this program ("webify", usually) */

/* Command line options:						    */
char Thumbnails = 1;		/* Use thumbnails.			    */
char PageTurn;			/* Use turnable pages			    */
char Landscape = 1,		/* Landscape mode			    */
     LandCmd;			/* Flag: Landscape set on cmd line	    */
char FlipPage;			/* Upside-down page			    */
int  ThumbRes, DefaultThumbRes = 10;	/* Thumbnail resolution		    */
int  SlideRes, DefaultSlideRes = 50;	/* Slide resolution		    */
char Debug;
int  CommentChar = '|';		/* Default comment character.		    */


double CropTop,  CropBot,	/* Optional cropping of page margins	    */
       CropLeft, CropRight;

FILE *InFile, *OutFile;		/* Input template, output HTML files	    */

int  LineNo;			/* Source line #			    */
char *LastKey;			/* Opening escape, for error report.	    */
int   LastKeyLine;		/* Line # of above.			    */

char Title[100];

/* Following are used by {for-each-page} and BuildGifs....		    */
int Pages,			/* Number of pages (GIF files)		    */
    CurPage;			/* Page we're currently on 		    */

struct OutStream
 { FILE *OutFile;		/* if FILE output			    */
   char *Ptr, *EPtr;		/* if character string output		    */
   struct OutStream *old;	/* Previous output stream.		    */
 } *CurrentOutput;		/* The current output stream.		    */

/* Forward refs								    */
char *FileSize(char *name);
char *ComputeGifName(int n, char *prefix);
char *ComputeRelName(int n, char *prefix, char *suffix);
char *NameInSubDir(char *name);

/* Parse the name of a postscript file.
 * Fills PostscriptName, also used as the default for subdirectory name.
 */

ParsePSName(char *name)
 { char *lastdot=0, *lastslash = 0, *aa, *bb, *base=name;
   static char basename[100];
   for (aa=name; *aa; aa++)
    { if (*aa == '.') lastdot = aa;
      if (*aa == '/') lastslash = aa;
    }
   if (lastslash) base = lastslash+1;
   if (lastdot && strncmp(lastdot, ".ps", 3)) lastdot = 0;
   for (aa=basename, bb=base; *bb && (bb != lastdot);) *aa++ = *bb++;
   PostscriptName = basename;
   SubDir = basename;
   for (aa=FullPostscriptName, bb=name; *bb && (bb != lastdot);)
   	*aa++ = *bb++;
   for (bb=".ps"; *aa++ = *bb++; );
   if (!FileSize(FullPostscriptName))
    { fprintf(stderr, "Can't find postscript input %s\n", FullPostscriptName);
      exit(-1);
    }
}

/* Write output character to current output stream.			    */
WrCh(char ch)
 { if (!CurrentOutput) return;
   if (CurrentOutput->OutFile) putc(ch, CurrentOutput->OutFile);
   if (CurrentOutput->Ptr)
    { *(CurrentOutput->Ptr++) = ch;
      if ((CurrentOutput->Ptr++) >= (CurrentOutput->EPtr))
      	SynErr("String buffer output overflow");
      *(CurrentOutput->Ptr++) = 0;
    } 
 }

WrStr(char *str)
 { if (!str) return WrStr("<null>");
   while (*str) WrCh(*str++);
 }

/* Read next character from input.  Counts lines, ignores comments.	    */
int RdCh()
 { int ch = getc(InFile);
agn:
   if (ch == CommentChar)
    { while (((ch = getc(InFile)) != '\n') && (ch != EOF));
      goto agn;
    }
   switch(ch)
    { case '\n': ++LineNo; return ch;
      case '\\': switch (ch = getc(InFile))
       { case 'n': return '\n';
	 case 't': return '\t';
	 case 'b': return '\b';
	 default: return ch;
       }
      default:	return ch;
    }
 }

UnRdCh(int ch)
 { ungetc(ch, InFile);
 }

/* Fetch a "word" from within current { ... }; returns NULL if end.
 */

char *RdWord()
 { int ch, *p, i, quotes=0;
   static char buf[100];
   while (((ch = RdCh()) != EOF) && (ch <= ' '));
   if (ch == '"') { ++quotes; ch = RdCh(); }

   for (i=0; ; ch = RdCh())
    { if (ch == EOF) break;
      if (!quotes)
       { if (ch == '}') { UnRdCh(ch); break; }
         if (ch <= ' ') break;
       }
      else
       { if (ch == '"') break;
	 if (ch == '\n') SynErr("Unclosed quoted string");
       }
      buf[i++] = ch;
    }
   if (!i) return NULL;
   buf[i] = 0;
   return buf;
 }

DummyKey(char *key)
 { char buf[100];
   sprintf(buf, "{%s ... ignored}", key);
   WrStr(buf);
   return IgnoreEscape();
 }

/* Interpolate text, skip to end of escape				    */
TextKey(char *str)
 { WrStr(str);
   IgnoreEscape();
 }

/* Find file size, return as a char string				    */
#include <sys/types.h>
#include <sys/stat.h>

char *FileSize(char *name)
 { struct stat buf;
   long size;
   static char sbuf[20];
   if (stat(name, &buf) < 0) return NULL;
   size = buf.st_size;
   if (size < 10000) sprintf(sbuf, "%d", size);
   else if (size < 1000000) sprintf(sbuf, "%dK", (size+512)>>10);
   else { size += 50*1024;
	  sprintf(sbuf, "%d.%dM", (size>>20),
 		 (size & 0xFFFFF) / (100*1024));
	}
   return sbuf;
 }

/* Output an integer, taking the format from the next argument
 * within the current { ... }.
 * Format defaults to "%d".
 */
FormatIntKey(int n)
 { char *fmt = RdWord(), buf[20];
   if (!fmt) fmt = "%d";
   sprintf(buf, fmt, n);
   WrStr(buf);
   IgnoreEscape();
 }

/* Is ch a break character?						    */
int BreakCh(int ch)
 { switch(ch)
    { case '{': case '}': case '(': case ')':
      case ' ':	case '\n': case '\t': case EOF:
      		return 1;
      default: 	return 0;
    }
 }

/* Grab a "word" from the template input stream:			    */
char *GrabWord()
 { static char buf[100];
   int i=0, ch;
   while (((ch = RdCh()) <= ' ') && (ch != EOF));
   buf[i++] = ch; buf[i] = 0;
   if (BreakCh(ch)) return buf;
   while (((ch = RdCh()) > ' ') && (ch != EOF))
    { if (BreakCh(ch))
       { if (i == 0) buf[i++] = ch;
	 else ungetc(ch, InFile);
	 buf[i] = 0;
	 goto rtn;
       }
      buf[i++] = ch;
      buf[i] = 0;
      if (i >= 99) goto rtn;
    }
rtn:
   return buf;
 }

/* Convert a float from input template, return as value
 */

double FloatKey()
 { double x;
   char *cc = GrabWord();
   if (sscanf(cc, "%lf", &x) != 1) SynErr("Bad real number");
   IgnoreEscape();
   return x;
 }

/* Process an escape.
 */
DoKey(char *key)
 { if (!strcmp(key, "comment")) return IgnoreEscape();
   if (!strcmp(key, "quietly")) return Quietly();

   if (!strcmp(key, "title")) return TextKey(Title);
   if (!strcmp(key, "for-each-page")) return ForEachPage();
   if (!strcmp(key, "thumbnail"))
   	return TextKey(ComputeRelName(CurPage, "T", ".gif"));
   if (!strcmp(key, "slide"))
   	return TextKey(ComputeRelName(CurPage, "S", ".gif"));

   if (!strcmp(key, "this-page"))
   	return TextKey(ComputeRelName(CurPage, "P", ".html"));
   if (!strcmp(key, "first-page"))
   	return TextKey(ComputeRelName(1, "P", ".html"));
   if (!strcmp(key, "last-page"))
   	return TextKey(ComputeRelName(Pages, "P", ".html"));
   if (!strcmp(key, "prev-page"))
   	return TextKey(ComputeRelName(CurPage>1? CurPage-1:Pages, "P", ".html"));
   if (!strcmp(key, "next-page"))
   	return TextKey(ComputeRelName(CurPage>=Pages? 1 :CurPage+1, "P",".html"));

   if (!strcmp(key, "page-number")) return FormatIntKey(CurPage);
   if (!strcmp(key, "pages")) return FormatIntKey(Pages);

   if (!strcmp(key, "horizontal")) { if (!LandCmd) Landscape = 1;
				     return IgnoreEscape(); }
   if (!strcmp(key, "vertical")) { if (!LandCmd) Landscape = 0;
				   return IgnoreEscape(); }
   if (!strcmp(key, "FlipPage")) { FlipPage = 1; return IgnoreEscape(); }

   if (!strcmp(key, "write-page-file")) return WritePageFile();
   if (!strcmp(key, "write-file")) return WriteFile();

   if (!strcmp(key, "postscript-size"))
   	return TextKey(FileSize(FullPostscriptName));
   if (!strcmp(key, "postscript")) return TextKey(PostscriptName);

   if (!strcmp(key, "crop-top"))
    { CropTop = FloatKey(); return; }
   if (!strcmp(key, "crop-bot"))
    { CropBot = FloatKey(); return; }
   if (!strcmp(key, "crop-left"))
    { CropLeft = FloatKey(); return; }
   if (!strcmp(key, "crop-right"))
    { CropRight = FloatKey(); return; }

   if (!strcmp(key, "slide-resolution"))
    { int temp = (int) FloatKey();
      if (!SlideRes) SlideRes = temp; return; }
   if (!strcmp(key, "thumbnail-resolution"))
    { int temp = (int) FloatKey();
      if (!ThumbRes) ThumbRes = temp; return; }

   SynErr("Unknown escape keyword");
 }

ForEachPage()
 { long seekpt = ftell(InFile);

   if (!Pages) return IgnoreEscape();
   for (CurPage=1; CurPage<=Pages; CurPage++)
    { if (ParseTemplate() != '}') SynErr("Unterminated {for-each-page...}");
      fseek(InFile, seekpt, 0);
    }
   return IgnoreEscape();
 }


/* Ignore input thru closing curly bracket				    */
IgnoreEscape()
 { int ch, count=1;
   for (;;) switch (ch = RdCh())
    { case '{': ++count; continue;
      case '}': if (!--count) return; continue;
      case EOF: SynErr("Missing '}'");
      default:	continue;
    }
 }

/* Flush output, interpret escapes:
 * {quietly ...}
 */
Quietly()
 { struct OutStream out;
   char ch;

   out.old = CurrentOutput;
   out.OutFile = 0;
   out.Ptr = 0;
   CurrentOutput = &out;

   ch = ParseTemplate();
   if (ch != '}') SynErr("Missing '}'");
   CurrentOutput = out.old;
 }

/* Write out a page file:
 * form {write-page-file  ... } writes ... into file named "name".
 */
WritePageFile()
{
    writefile(ComputeRelName(CurPage, "P", ".html"));
    return;
}


/* Write out a file:
 * form {write-file name... } writes ... into file named "name".
 */
WriteFile()
{
    int c;
    char buf[100];
    int i;

    for (;;) {
	switch (c = RdCh()) {
	case ' ': case '\t': case '\n':
	    continue;
	case '}':  SynErr("Premature '}':  missing filename");
	case EOF:  SynErr("Premature EOF:  missing filename");
	}
	break;
    }
    /* Start of filename. */
    i = 0;
    do {
	buf[i++] = c;
	switch (c = RdCh()) {
	case ' ': case '\t': case '\n': case '}':
	    goto out;
	case EOF:  SynErr("Premature EOF in write-file: missing filename");
	}
    } while (i < sizeof(buf));
    SynErr("filename too long");

out:;
    buf[i] = '\0';
    writefile(buf);
}

/* Start writing to file "fn", saving current output. */
writefile(fn)
char *fn;
 { struct OutStream out;
   char ch;
   out.old = CurrentOutput;
   if (!(out.OutFile = fopen(NameInSubDir(fn), "w")))
    { fprintf(stderr, "Can't write file %s\n", fn); exit(-1); }
   out.Ptr = 0;
   CurrentOutput = &out;
   ch = ParseTemplate();
   if (ch != '}') SynErr("Missing '}'");
   fclose(out.OutFile);
   CurrentOutput = out.old;
 }


/* Evaluate text into a string, until next '}'
 * Ptr is start, size is max length.
 */
ParseTo(char *Ptr, int size)
 { struct OutStream out;
   int ch;
   out.OutFile = 0;
   out.old = CurrentOutput;
   CurrentOutput = &out;

   out.Ptr = Ptr;  out.EPtr = Ptr+size;
   ch = ParseTemplate();
   if (ch != '}') SynErr("Missing '}'");
   CurrentOutput = out.old;
   return ch;
 }

/* Read the template file.
 * If pass is zero, just scan and set parameters.
 * If pass is one, write the HTML output file.
 */
DoFile(pass)
 { char *fn;
   int ch;
   struct OutStream out;

   out.OutFile = 0; out.Ptr = 0;
   out.old = CurrentOutput;
   CurrentOutput = &out;

   if (!(InFile = fopen(TemplateName, "r")))
    { fprintf(stderr, "%s: can't read file %s\n", TemplateName); exit(-1); }
   if (pass)
    { if (!(out.OutFile = fopen(fn=NameInSubDir(HTMLName), "w")))
       { fprintf(stderr,
		 "%s: can't write file %s\n", ProgName, fn);
	 exit(-1);
       }
    }

   LineNo = 1;
   if (ParseTemplate() != EOF) SynErr("Premature EOF -- missing }?");
   CurrentOutput = out.old;
   if (out.OutFile) fclose(out.OutFile);
 }

/* Parse the template file.
 * Reads & processes until EOF or '}' read.
 */
int ParseTemplate()
 { int ch;
   while ((ch = RdCh()) != EOF) switch (ch)
    { case '}':	return ch;
      case '{': ParseKey(); continue;
      default:	WrCh(ch);
    }
   return EOF;
 }

/* Parse an escape sequence {key...}.
 * Called after initial { read; returns after closing } read.
 */
ParseKey()
 { char key[KEYL+1], *OldLastKey=LastKey;
   int OldLastKeyLine=LastKeyLine, ch, i;
   for (i=0;;)
    { ch = RdCh();
      if (ch <= ' ') break;
      if (ch == '}') { ungetc(ch, InFile); break; }
      if (i >= KEYL) SynErr("Keyword too long");
      key[i++] = ch;
    }
   LastKey = key;
   LastKeyLine = LineNo;
   key[i] = 0;
   DoKey(key);
   LastKeyLine = OldLastKeyLine;
   LastKey = OldLastKey;
 }


#define NXARG (*++arg? arg : argv[++argn])

main(int argc, char **argv)
 { int argn,i,j;
   char *arg;

   ProgName = argv[0];
   HTMLName = "index.html";

   if (argc<3) { Usage(); return 0; }

   for (argn=1; argn<argc; argn++)
    { if (*(arg=argv[argn]) == '-') switch (*++arg)
       { case 'd':	Debug=1; continue;
	 case 'h':	Landscape = 1; LandCmd = 1; continue;
	 case 'v':	Landscape = 0; LandCmd = 1; continue;
	 case 'f':	FlipPage = 1; continue;
	 case 't':	strcpy(Title, NXARG); continue;
	 case 'p':	PageTurn = 1; Thumbnails = 0; continue;
	 case 'T':	ThumbRes = atoi(NXARG); continue;
	 case 'S':	SlideRes = atoi(NXARG); continue;
	 case 'g':	GSName = NXARG; continue;
	 default:	Usage();
       }
      else { if (!TemplateName) TemplateName = arg;
	     else if (!PostscriptName) ParsePSName(arg);
	     else SubDir = arg;
	   }
    }
   DoFile(0);			/* Scanning pass, for parameters	    */

   mkdir(SubDir, 0775);
   link(FullPostscriptName, NameInSubDir(FullPostscriptName));

   for (i=10,j=1; ; j++)	/* Delete existing thumbnails.		    */
    { if (unlink(NameInSubDir(ComputeRelName(j,"T",".gif"))))
       { if (--i <= 0) break; }
      unlink(NameInSubDir(ComputeRelName(j,"S",".gif")));
    }

   DoFile(0);			/* 1st pass thru template file...	    */
   BuildGifs();			/* Do the gif conversions		    */
   DoFile( 1);			/* Do it for real this time.		    */
   return 0;
 }

SynErr(char *msg)
 { fprintf(stderr, "%s:%d: %s\n", TemplateName, LineNo, msg);
   if (LastKey)
    { fprintf(stderr, "   {%s ... begins on line %d\n",
	      LastKey, LastKeyLine);
    }
   fprintf(stderr, "%s aborted.\n", ProgName);
   exit(-1);
 }

Usage()
 {
   printf("%s: convert Postscript to web-browsable html/gif subtree\n",
	  ProgName);
   printf("\t\t   - Steve Ward 3/16/95 ward@mit.edu\n");
   printf("USAGE:\n");
   printf("  webify <options> template psfile\n");
   printf("    template is a text file containing prototype HTML and\n");
   printf("      escapes which specify the configuration of generated\n");
   printf("      GIFs and HTML documents (brief summary below).\n");
   printf("    psfile.ps is the postscript file whose pages are to\n");
   printf("      become web-browsable.\n");
   printf(" <options> may include:\n");
   printf("    -h    Horizontal (Landscape) mode.  The default.\n");
   printf("    -v    Vertical (Portrait) mode.\n");
   printf("    -f    Flip pages upside down (eg, like HP printers)\n");
   printf("    -t \"title\" A title for the presentation.\n");
   printf("    -T #  Thumbnail resolution (hence size). Default is 10.\n");
   printf("    -S #  Slide/Page resolution (hence size).  Default is 50.\n");
   printf("    -g <name> Use <name> as command to invoke Ghostscript.  Default 'gs'.\n");
   printf("    -d    Debug mode.\n");

   printf("\nEscapes in template include:\n");
   printf("    {title} - insert title.\n");
   printf("    {quietly ...} - evaluate for escapes, no output.\n");
   printf("    {crop-top #} Crop # (a float) from top edge.  Similarly for\n");
   printf("        crop-bot, crop-left, crop-right.\n");
   printf("    {slide-resolution #} set slide resolution/size (default 50)\n");
   printf("    {thumbnail-resolution #} set thumbnail resolution/size (default 10)\n");

   printf("    {vertical}, {horizontal} - like -h, -v command options.\n");
   printf("    {FlipPage} - like -f command option.\n");

   printf("\n    {for-each-page ...} - repeat ... for each page. May include:\n");
   printf("      {thumbnail} - reference to thumbnail GIF for current page.\n");
   printf("      {slide} - reference to slide GIF for current page.\n");
   printf("      {this-page}, {next-page}, {prev-page}, {first-page}, {last-page}\n");
   printf("          - references to per-page HTML files.\n");
   printf("      {write-page-file ...} Put ... in current per-page HTML file.\n");
   printf("      {write-file fn ...} Put ... in file fn.\n");
   printf("      {page-number [fmt]} - numeric page number, formatted per\n");
   printf("          printf format [fmt].  Default is %c%s%c\n", '"', "%d", '"');
   printf("      {pages [fmt]} - total number of pages, formatted per [fmt].\n");

   printf("\nNB: Webify invokes Ghostscript (via gs command).\n");
   printf("    Gs 2.6.1 must be installed and have gif8 conversion enabled.\n");
 }


/****************************************************************************
 * Interface to Ghostscript: Build the GIF Images			    *
 ****************************************************************************/

int	GSRes = 50;		/* Device resolution, in pixels/inch	    */
double  XSize=11, YSize=8.5;	/* Page dimensions, in inches.		    */
double	XOrg=0, YOrg=0;		/* Origin of page			    */
int	Rotate = 90;		/* Rotation, in degrees (90 for landscape)  */
char	*FileNPrefix="S";	/* Template for file name.	            */

char tmpfilename[200];		/* Name of temp file 			    */
FILE *tmpout;			/* Temporary file for Ghostscript input     */

CropIt()
 { XOrg = CropLeft;
   YOrg = CropRight;
 }

Rotation()
 { if (Landscape)
    { fprintf(tmpout, "%d rotate 0 -72 XSize mul translate\n",
	   Rotate);
      fprintf(tmpout,
	   "CropBot -72 mul CropLeft 72 mul translate\n");
    }
   else
      fprintf(tmpout,
	   "CropLeft -72 mul CropBot -72 mul translate\n");
   if (FlipPage)
    { fprintf(tmpout,
	      "180 rotate -72 YSize mul -72 XSize mul translate\n");
    }
 }

/* Prefix SubDir/ to the given file name:				    */
char *NameInSubDir(char *name)
 { static char buf[100];
   sprintf(buf, "%s/%s", SubDir, name);
   return buf;
 }

/* Compute name, relative to subdirectory				    */
char *ComputeRelName(int n, char *prefix, char *suffix)
 { static char buf[100];
   sprintf(buf, "%s%03d%s", prefix, n, suffix);
   return buf;
 }

/* Compute name, relative to upper (invoking) directory.		    */
char *ComputeGifName(int n, char *prefix)
 { return NameInSubDir(ComputeRelName(n, prefix, ".gif"));
 }

int CountGifs()
 { int i;
   for (i=1; ; i++)
    { if (!FileSize(ComputeGifName(i, FileNPrefix))) return i-1;
    } 
 }

BuildThumbnails()
 { FileNPrefix = "T";
   if (!ThumbRes) ThumbRes = DefaultThumbRes;
   GSRes = ThumbRes;
   RunGS();
 }

BuildSlides()
{ FileNPrefix = "S";
   if (!SlideRes) SlideRes = DefaultSlideRes;
   GSRes = SlideRes;
   RunGS();
 }


BuildGifs()
 {
   if (Debug)
     fprintf(stderr,
	     "Landscape=%d, FlipPage=%d, Crops = %f, %f, %f, %f\n",
	     Landscape, FlipPage, CropTop, CropBot, CropLeft, CropRight);

   if (!Landscape)
    { double tt = XSize;
      XSize = YSize; YSize = tt;
    }
   BuildThumbnails();
   Pages = CountGifs();
   if (!Pages)
    { fprintf(stderr,
	      "%s: Ghostscript (%s) command generated no output.\n",
	      ProgName, GSName);
      fprintf(stderr,
	      "  To debug the problem, try viewing your postscript file directly\n");
      fprintf(stderr,
	      "  using the command %c%s %s%c\n", '"', GSName, FullPostscriptName, '"');
      exit(-1);
    }
   fprintf(stderr, "%s: %d pages of GIFs being created.\n", ProgName, Pages);
   BuildSlides();
 }

RunGS()
 { char command[400];
   int err;
   static char *gsoutflag = ">";

   sprintf(tmpfilename, "T%d", getpid());
   if (!(tmpout=fopen(tmpfilename, "w")))
    { fprintf(stderr, "Can't write temp file %s\n", tmpfilename);
      exit(-1);
    }

   fprintf(tmpout, "/CropLeft %f def /CropRight %f def\n",
	   CropLeft, CropRight);
   fprintf(tmpout, "/CropTop %f def /CropBot %f def\n",
	   CropTop, CropBot);
   fprintf(tmpout, "/XSize %f def /YSize %f def\n",
	   XSize, YSize);
   fprintf(tmpout, "/Res %d def\n", GSRes);

   fprintf(tmpout,
	   "/WebSize { mark Res Res 2 array astore /HWResolution exch\n");
   fprintf(tmpout, "   currentdevice putdeviceprops setdevice\n");
   fprintf(tmpout,
	   "  mark XSize CropLeft CropRight add sub Res mul cvi\n");
   fprintf(tmpout,
	   "  YSize CropTop CropBot add sub Res mul cvi\n");
   fprintf(tmpout,
	   "  2 array astore\n");
   fprintf(tmpout,
	   "  /HWSize exch currentdevice putdeviceprops setdevice } def\n");


   fprintf(tmpout, "(gif8) selectdevice WebSize\n");


   fprintf(tmpout, "mark (%s/%s%s.gif) /OutputFile exch\n",
	   SubDir, FileNPrefix, "%03d");
   fprintf(tmpout, "   currentdevice putdeviceprops setdevice\n");

   Rotation();

   fprintf(tmpout, "(\\nRe-defining showpage\\n) print\n");
   fprintf(tmpout,
	   "/showpage { #copies true .outputpage erasepage initgraphics\n");
   Rotation();
   fprintf(tmpout, " } def\n");

   fprintf(tmpout, "(\\nProcessing the file\\n) print\n");
   fprintf(tmpout, "\n\n(%s) run\n\nquit\n", FullPostscriptName);
   fclose(tmpout);

   sprintf(command,
	   "%s -dNODISPLAY %s %sgs.out 2>&1", GSName, tmpfilename, gsoutflag);
   gsoutflag = ">>";		/* Next time, append.			*/

   err = system(command);
   if (Debug) fprintf(stderr, "system(%s) returns %d\n", command, err);

   if (!Debug) unlink(tmpfilename);
 }

