#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/wait.h>

char *PostscriptName,		/* Name of .ps file, sans .ps suffix	    */
     FullPSName[200],		/* Name of .ps file, with .ps suffix	    */
     SubDir[100],		/* Subdirectory for output files.	    */
     *GSName = "gs",		/* Name of Ghostscript command		    */
     *PostCommandName="ppmtogif",/* Name of post processor command	    */
     *HackPPMName = "hackppm",	/* Name of hackppm command		    */
     *PixSuffix,		/* eg, ".jpeg" or ".gif"		    */
     *ProgName;			/* Name of this program ("ps2html")	    */

/* Command line options:						    */
int  Rotation;			/* Rotation, CW 90-deg increments	    */
char Negative;			/* Negative (reverse video) mode	    */
const int DefaultQualImage = 50;
int  QualityImage;              /* Quality images                           */
int  SlideRes, DefaultSlideRes = 72;	/* Slide resolution		    */
int  Debug;
int  NoGS;			/* Flag: don't do ghostscript		    */
int  NoCheck;			/* Flag: don't check environment	    */
int  NoFifo;			/* Flag: don't use Unix FIFOs		    */

int CropTop=0,		        /* Optional cropping of page margins	    */
    CropBot=0,
    CropLeft=0,
    CropRight=0;

char Title[100];
char BaseName[100];		/* Name of input file, sans suffix.	    */
char PostCommand[200];		/* Post-processor command.		    */

/* Following are used by BuildJPegs....					    */
int PageNum;			/* Number of pages (PPM files)		    */
extern char PaperSize[30];
extern int SleepTime;
extern struct ImageNode *ImageTail;
extern struct ImageNode *ImageHead;

/* Forward refs								    */
#define ComputeBaseName(n, m, prefix) ComputeSuffixName(n,m,prefix,"")
char *ComputePpmName(int n, char *prefix);
char *ComputeSuffixName(int n, int m, char *prefix, char *suffix);
char *ComputeRelName(int n, int m, char *prefix, char *suffix);
char *NameInSubDir(char *name);
char *ExpandString(char *template, char *target);

void SetJPEG()
{ 
	if (!PostCommandName) PostCommandName = "cjpeg";
	if (!strcmp(PostCommandName, "ppmtogif")) PostCommandName = "cjpeg";
   	PixSuffix = ".jpeg";
   	sprintf(PostCommand, "%s -quality %d -optimize ",
	   	PostCommandName, QualityImage);
   	strcat(PostCommand, " > %B.jpeg");
	if (Debug)
		fprintf(stderr, "System(%s)\n", PostCommand);
}

void SetGIF()
{
	if (!PostCommandName) PostCommandName = "ppmtogif";
	PixSuffix = ".gif";
	sprintf(PostCommand, "%s ", PostCommandName);
	strcat(PostCommand, " > %B.gif");
	if (Debug)
		fprintf(stderr, "System(%s)\n", PostCommand);
}

char *MAlloc(int size)		/* Our interface to malloc		   */
{
	char *it = (char *) malloc(size);
	if (!it) { fprintf(stderr, "ps2html: out of storage in MAlloc(%d)\n", size);
 	      exit(-1); }
	return it;
}

char *SCopy(char *s)		/* Copy a string to malloc'ed storage     */
{
	int len = strlen(s)+1;
	char *it;
	it = MAlloc(len);
	strcpy(it,s);
	return it;
}


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

webify(int myargc, char **myargv)
{ int argn,i,j;
  char *garb;
  char *arg;

  ProgName = "ps2html";
  QualityImage = DefaultQualImage;
  SetGIF();
  NoFifo = 1;

  for (argn=1; argn<myargc; argn++)
   { if (*(arg=myargv[argn]) == '-') 
      { if (!strcmp(arg, "-jpeg")) { SetJPEG(); continue; }
	if (!strcmp(arg, "-gif"))  { SetGIF(); continue; }
	switch (*++arg)
	 { case 'd':	Debug=1; continue;
	   case 'n': 	NoGS = atoi(NXARG);
	   		if (!NoGS) { exit(-1); }
	   		continue;
	   case 'r':	Rotation = atoi(NXARG); continue;
	   case 't':	strcpy(Title, NXARG); continue;
	   case 'Q': 	QualityImage = atoi(NXARG);
			if (QualityImage < 0 || QualityImage > 100)
			 { fprintf(stderr,"Ps2html (warning): the quality parameter ");
			   fprintf(stderr, "must have a value between 0 and 100.\n");
			   fprintf(stderr,
				   "  The quality parameter is set to its default");
			   fprintf(stderr, " value, i.e., %d\n",DefaultQualImage);
			   QualityImage = DefaultQualImage;
			 }
	   		SetJPEG();
			continue;

	   case 'S':	SlideRes = atoi(NXARG); continue;

	   case 'G':	GSName = NXARG; continue;
	   case 'p':	PostCommandName = NXARG; continue;
	   case 'H':	HackPPMName = NXARG; continue;

	   case 'F':	NoFifo = 1; continue;
	   case 'v':	Negative = 1; continue;

	   default:	garb = strdup(NXARG); continue;
	 }
      }
      else break;
  }

  BuildJPegs();		/* Do the .jpeg conversions */
  return 0;
}


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

/* 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, int m, char *prefix, char *suffix)
 { static char buf[100];
   sprintf(buf, "%s%03d_%d%s", prefix, n, m, suffix);
   return buf;
 }

/* Compute name, relative to upper (invoking) directory.  */
char *ComputeSuffixName(int n, int m, char *prefix, char *suffix)
 { return NameInSubDir(ComputeRelName(n, m, prefix, suffix));
 }

/* Compute .ppm name */
char *ComputePpmName(int n, char *prefix)
{ static char buf[100];
  sprintf(buf, "/tmp/%s%03d.ppm", prefix, n);
  return buf;
}

BuildSlides()
{
   if (!SlideRes) SlideRes = DefaultSlideRes;
   RunGS("I", SlideRes);
 }


BuildJPegs()
 {
   if (Debug)
     fprintf(stderr,
	     "Rotation=%d, Crops = %d, %d, %d, %d\n",
	     Rotation, CropTop, CropBot, CropLeft, CropRight);

   if (!PageNum)
    { 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, FullPSName, '"');
      exit(-1);
    }
   fprintf(stderr, "%s: Parsed %d pages.\n", ProgName, PageNum);
   if (ImageHead != (struct ImageNode *)NULL)
      fprintf(stderr, "%s: %d %s will be created.\n",ProgName,ImageHead->ImageNum,PixSuffix);
   else fprintf(stderr, "%s: No %s will be created.\n", ProgName, PixSuffix);
   BuildSlides();
 }

RunGS(char *NamePrefix, int res)
{ char command[400], cjpeg[400], hackppm[100];
  int i, j, err, pid;
  static char *gsoutflag = ">";

  sprintf(hackppm,
	  "%s rotate % d crop %d %d %d %d %s",
	  HackPPMName,
	  Rotation,
	  (int) (CropTop*res), (int) (CropBot*res),
	  (int) (CropLeft*res), (int)(CropRight*res),
	  Negative? "negative" : "");

  /*
   * Strategy:
   * Use UNIX FIFOs, where we can, to reduce scatch disk consumption
   *    by pipelining PPM file creation & consumption.
   */
  /* First, we make the fifos. */
     for (i = 1; i <= PageNum; i++)
      { char *PPMFileName;
	const mode_t PERMS = 0666;
	struct ImageNode *sp;
	int check=0;

	for (sp = ImageTail;
	     sp != (struct ImageNode *)NULL;
	     sp = sp->prev)
		if (sp->Page == i)
			check++;

	PPMFileName = ComputePpmName(i, NamePrefix);
	if (check <= 1)
	{
		unlink(PPMFileName);
		if ((mknod(PPMFileName, S_IFIFO | PERMS, 0)) < 0 )
	 	{ perror("mknod"); exit(-1); }
	}
	check = 0;
      }

    switch (pid=fork())
     { case -1:	perror("fork");
       		exit(-1);
       		break;

       case 0:
		/* Convert PPM files into image files. */
		for (i=1, j=1; i <= PageNum; i++)
		{
			char *PPMFileName;
			PPMFileName = SCopy(ComputePpmName(i, NamePrefix));
			strcpy(BaseName, ComputeBaseName(i, j, NamePrefix));
			{
				char buf[200];
				char duration[10];
				if (ImageTail->Page == i)
				{
					sprintf(hackppm,
						"%s rotate %d crop %d %d %d %d %s",
						HackPPMName,
						Rotation,
						ImageTail->CropTop,
						ImageTail->CropBot,
						ImageTail->CropLeft,
						ImageTail->CropRight,
						Negative? "negative" : "");

					sprintf(cjpeg, "%s < %s | %s | ", hackppm, PPMFileName, "pnmcrop");
					ExpandString(PostCommand,buf);
					strcat(cjpeg, buf);
					sprintf(duration, "sleep %d", SleepTime);
					system(duration);
					err = system(cjpeg);
					if (Debug) fprintf(stderr,
						"system(%s) returns %d\n", cjpeg, err);
					if (ImageTail->prev != (struct ImageNode *)NULL)
					{
						if (ImageTail->prev->Page == i)
						{ i--; j++; }
						else
						{
							unlink(PPMFileName);
							j=1;
						}
						ImageTail = ImageTail->prev;
					}
					else unlink(PPMFileName);
				}
				else
				{
					char garb[100];
					sprintf(garb, "cat < %s > /dev/null", PPMFileName);
					err = system(garb);
					unlink(PPMFileName);
				}
			}
		}
		exit(0);


       default:
       	if (Debug) fprintf(stderr, "RunGS('%s', %d)\n", NamePrefix, res);
       	sprintf(command,
		"%s -dNOPAUSE -r%dx%d %s %s%s%%03d.ppm %s - %s%sgs.out 2>&1 </dev/null",
		GSName, res, res,
		PaperSize,
		"-sDEVICE=ppmraw -sOutputFile=/tmp/",
		NamePrefix,
		FullPSName,
		gsoutflag, SubDir);
       if (Debug) fprintf(stderr, "system(%s) ...\n", command);
       err = system(command);
       if (Debug) fprintf(stderr, "  ... system(...) returns %d\n", err);
       while (wait(NULL) != pid);
     }
 }

/* Interpolate a string, expanding %x excapes:
 *   %Q		Quality (-Q # option)
 *   %B		Base filename
 */

char *ExpandString(char *template, char *target)
 { char buf[100], *p=target, *aa;
   while (*template)
    { switch (*template)
       { case '%': switch (*++template)
	  { case 'Q': { char buf[20];
			sprintf(buf, "%d", QualityImage);
			for (aa=buf; *target=*aa++; ++target);
		      }
	 	      ++template; continue;
	    case 'B': for (aa=BaseName; *target=*aa++; ++target);
	 	      ++template; continue;
	    default:  *target++ = *template++; continue;
	  }
	 case 0: goto done;
	 default:  *target++ = *template++;
	 	   continue;
       }
    }
done:
   *target = 0;
 }
