/*
# epstopdf: written by Kong Hoon Lee konghoon@hyowon.cc.pusan.ac.kr<4/1/1999>
#
# It converts an EPS file to an encapsulated PDF File and
# coded with the perl script 'epstopdf' by Sebastian Rahtz on
# http://tug.org/applications/pdftex/epstopdf .
# It works like as the perl script without 'perl' for Windows 95
# but requires 'Ghostscript' for Windows.
#

# Modified 2004-02-27 by Rasmus Friis Kjeldsen rasmus (at) reblag (dot) dk to 
# include the option "--(no)lossy" resulting in the gs option 
# "-dEncodeColorImages=[false|true]" so color images being compressed beyond
# recognition can be avoided.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>


#define EPSTOPDF_VERSION "2.2.20"
#define EPSTOPDF_DATE    "2001-12-01"


#ifndef GSEXEC
 #ifdef __WIN32__
  #define GSEXEC "gswin32c"
 #else
  #define GSEXEC "gs"
 #endif
#endif


#define BEGINDOC "%%BeginDocument"
#define ENDDOC   "%%EndDocument"
#define PSSTART  "%!"
#define PSEOF    "%%EOF"
#define CR       0x0A
#define LF       0x0D

#define TRUE     1
#define FALSE    0


typedef  struct tline {
	int cap,len;
	char *data;
 } tline;

char newline[3] = "\n";



static int getline(tline* line, FILE *in)

{
	if(!line || !in) return 0;
	if(line->cap<1)  return 0;

	line->len=0;
	while(line->len < line->cap-1) {
	 line->data[line->len++]=fgetc(in);  
	 if(feof(in)) { line->len--; break; }

	 if(line->data[line->len-1]==CR){  /* stop on carriage return */
	  sprintf(newline,"%c",CR);
          break;
	 }

	 if(line->data[line->len-1]==LF){  /* stop on line feed */
	  sprintf(newline,"%c",LF);
          break;
	 }
	}

	/* some systems (Win/DOS) use LF + CR. If we find a line
           with CR only and the last line was terminated with LF,
           we have propably a Win/DOS file */
	if(line->len==1) {
	 if(line->data[0]==CR && newline[0]==LF)
	   sprintf(newline,"%c%c",LF,CR);    /* yes, LF + CR combination */
	}

	/* terminate sting */ 
	line->data[line->len]='\0';

	return line->len;
}


static void putline(tline* line, FILE *out)

{
	fwrite(line->data, 1, line->len, out);
}


static void usage(void)

{
	printf("epstopdf %s:\n",EPSTOPDF_VERSION);
	printf("written by Kong Hoon Lee, konghoon@dreamwiz.com <1999-4-1>\n");
	printf("changes by Juergen Bausa, Juergen.Bausa@Online.de <%s>\n",EPSTOPDF_DATE);
	printf("bugfix by Pascal Perichon, Pascal.Perichon@u-bourgogne.fr <2000-5-25>\n\n");
	printf("It converts an EPS file to an encapsulated PDF File and is written\n");
	printf("based on the perl script 'epstopdf' by Sebastian Rahtz on\n");
	printf("http://tug.org/applications/pdftex/epstopdf .\n");
	printf("It works like the perl script without 'perl' but requires 'Ghostscript'.\n");
	printf("The accompanied Makefile can be used to automate the update of all eps/pdf\n");
        printf("files in a directory. Just put it in the directory where your eps files\n");
        printf("are and type 'make' from the command line (you will need a version of\n");
        printf("GNU-make)\n\n");

	printf("This program invokes '%s' and the path including '%s'\n",GSEXEC,GSEXEC);
	printf("should be included to the environment variable 'PATH'.\n");
	printf("'%s' should know, where to find its initialization files\n",GSEXEC);
        printf("and fonts, using an registry entry (Windows) or an environment variable.\n\n");

	printf("Using a different output device, it is also possible to convert eps\n");
        printf("files to bitmaps (e.g. -sDEVICE=bmpmono).\n\n");

	printf("Usage: epstopdf [options] filename-of-an-eps-file\n");
	printf("Options:\n");
	printf("  --help:             print usage\n");
	printf("  --outfile=<file>:   write result to <file>\n");
	printf("  --tmpfile=<file>:   use <file> as temporary file\n");
	printf("  --(no)filter:       read/writ standard input/output   (default: false)\n");
	printf("  --(no)gs:           run ghostscript                   (default: true)\n");
	printf("  --(no)compress:     use compression                   (default: true)\n");
	printf("  --(no)lossy:        lossy compression on color images (default: false)\n");
	printf("  --(no)hires:        scan HiresBoundingBox             (default: false)\n");
	printf("  --(no)exact:        scan ExactBoundingBox             (default: false)\n");
	printf("  --(no)debug:        debug informations                (default: false)\n");
	printf("  --(no)wait:         wait for keystroke                (default: false)\n");
	printf("  --(no)antialias:    antialiasing (only Bitmap dev.)   (default: false)\n");
	printf("  --gsexec=<gs>:      use <gs> to invoke ghostscript    (default: %s)\n",GSEXEC);
	printf("  --enlarge=<a>:      enlarge BB by <a>/72 ''           (default: 0.)\n");
	printf("  -sDEVICE=<dev>:     use <dev> as output device        (default: pdfwrite)\n");
	printf("  -r<a>:              output resolution for gs          (default: 600)\n");
	printf("  --width=<a>:        output width in pixels            (default: none)\n");
	printf("  --height=<a>:       output height in pixels           (default: none)\n");
	printf("  --gsopt=<a>:        add <a> to the gs command line    (default: none)\n");
}


static int round(double a)

{
	return floor(a+0.5);
}

static int isint(double a)

{
	if(fabs(a-round(a))<1e-6) return 1;
	else                      return 0;
}


int main(int argc,char *argv[])

{
	FILE *in,*out;
	char *infile=NULL,*outfile=NULL,*tmpfile=NULL,*copt,*lopt,*ptr,*gsexec,buf[21];
	char command[2000],*BBname,*device,ropt[20],*gsopt,*aliasopt,c1,c2;
	double bbllx,bblly,bburx,bbury,xoffset,yoffset,enlarge;
	int    width,height,res,xpix,ypix;
	int i,count,status,BBCorrected,did_CR,debug,compress,lossy_compress,usegs,wait,
            BBhires,BBexact,found,depth,filter,ngsopt,in_ps,antialias;
	fpos_t  fpos;
	tline *line;


	#if defined (__EMX__)
	_wildcard (&argc, &argv);
	#endif

	/* default parameter */
	status=0;
	BBCorrected=0;
	BBhires=0;
	BBexact=0;
	debug=0;
	compress=1;
	lossy_compress=0;
	usegs=1;
	gsexec=GSEXEC;
	wait=0;
	filter=0;
	enlarge=0.;
	res=600;
	device="pdfwrite";
	xpix=0;
	ypix=0;
	ngsopt=100;
	gsopt=malloc(ngsopt);
	gsopt[0]='\0';
	antialias=0;


	line=malloc(sizeof(tline));
	line->cap=10000;
	line->len=0;
	line->data=malloc(line->cap);


	/******************************************************************

		Process command line options

	******************************************************************/

	for(i=1;i<argc;i++){

	  /* printf("i=%d  arg=>>%s<<\n",i,argv[i]); */

	  if(!strcmp(argv[i],"--help") || !strcmp(argv[i],"-h")){
	    usage();
	    status=0;
	    goto EXIT;
	  }

	  if(!strcmp(argv[i],"--debug") || !strcmp(argv[i],"-d")){
	    debug=1;
	    continue;
	  }

	  if(!strcmp(argv[i],"--nodebug")){
	    debug=0;
	    continue;
	  }

	  if(!strcmp(argv[i],"--antialias")){
	    antialias=1;
	    continue;
	  }

	  if(!strcmp(argv[i],"--noantialias")){
	    antialias=0;
	    continue;
	  }
	  if(!strcmp(argv[i],"--compress") || !strcmp(argv[i],"-c")){
	    compress=1;
	    continue;
	  }

	  if(!strcmp(argv[i],"--nocompress")){
	    compress=0;
	    continue;
	  }

	  if(!strcmp(argv[i],"--lossy")){
	    lossy_compress=1;
	    continue;
	  }
	  
	  if(!strcmp(argv[i],"--nolossy")){
	    lossy_compress=0;
	    continue;
	  }

	  if(!strcmp(argv[i],"--nogs")){
	    usegs=0;
	    continue;
	  }

	  if(!strcmp(argv[i],"--gs")){
	    usegs=1;
	    continue;
	  }

	  if(!strcmp(argv[i],"--wait")  || !strcmp(argv[i],"-w")){
	    wait=1;
	    continue;
	  }

	  if(!strcmp(argv[i],"--nowait")){
	    wait=0;
	    continue;
	  }

	  if(!strcmp(argv[i],"--filter")){
	    filter=1;
	    continue;
	  }

	  if(!strcmp(argv[i],"--nofilter")){
	    filter=0;
	    continue;
	  }

	  if(!strcmp(argv[i],"--hires")){
	    BBhires=1;
	    continue;
	  }

	  if(!strcmp(argv[i],"--nohires")){
	    BBhires=0;
	    continue;
	  }

	  if(!strcmp(argv[i],"--exact")){
	    BBexact=1;
	    continue;
	  }

	  if(!strcmp(argv[i],"--noexact")){
	    BBexact=0;
	    continue;
	  }

	  if(!strncmp(argv[i],"--outfile=",strlen("--outfile="))){
	    outfile=malloc(strlen(argv[i])-strlen("--outfile=")+1);
	    strcpy(outfile, argv[i]+strlen("--outfile="));
	    continue;
	  }

	  if(!strncmp(argv[i],"--tmpfile=",strlen("--tmpfile="))){
	    tmpfile=malloc(strlen(argv[i])-strlen("--tmpfile=")+1);
	    strcpy(tmpfile, argv[i]+strlen("--tmpfile="));
	    continue;
	  }

	  if(!strncmp(argv[i],"-r",strlen("-r"))){
	    if(sscanf(argv[i]+strlen("-r"),"%d",&res)) continue;
	  }

	  if(!strncmp(argv[i],"--width=",strlen("--width="))){
	    if(sscanf(argv[i]+strlen("--width="),"%d",&xpix)) continue;
	  }

	  if(!strncmp(argv[i],"--height=",strlen("--height="))){
	    if(sscanf(argv[i]+strlen("--height="),"%d",&ypix)) continue;
	  }

	  if(!strncmp(argv[i],"--gsopt=",strlen("--gsopt="))){
	    char *opt=argv[i]+strlen("--gsopt=");
	    if(strlen(gsopt)+strlen(opt)+2 < ngsopt){
		ngsopt+=100;
		gsopt=realloc(gsopt,ngsopt);
	    }
	    strcat(gsopt," ");
	    strcat(gsopt,opt);
	    continue;
	  }

	  if(!strncmp(argv[i],"-sDEVICE=",strlen("-sDEVICE="))){
	    device=argv[i]+strlen("-sDEVICE=");
	    continue;
	  }

	  if(!strcmp(argv[i],"-o") && i+1<argc){
	    outfile=malloc(strlen(argv[i+1])+1);
	    strcpy(outfile, argv[i+1]);
	    i++;
	    continue;
	  }

	  if(!strncmp(argv[i],"--gsexec=",strlen("--gsexec="))){
	    gsexec=argv[i]+strlen("--gsexec=");
	    continue;
	  }


	  if(argv[i][0]!='-'){
	    if(infile) printf("\nCan process only one input file\n");
	    else{
	     infile=malloc(strlen(argv[i])+1);
             strcpy(infile,argv[i]);
	    }
	    continue;
	  }

	  if(!strncmp(argv[i],"--enlarge=",strlen("--enlarge="))){
	    if(sscanf(argv[i]+strlen("--enlarge="),"%lf",&enlarge)) continue;
	  }

	  usage();
	  fprintf(stderr,"\nunknown option >>%s<<\n",argv[i]);
	  status=1;
	  goto EXIT;
	}



	/******************************************************************

		check arguments and files

	******************************************************************/


	if(filter) debug=0;
	if(filter) wait =0;

	if(BBexact && BBhires){
	 fprintf(stderr,"\nOptions --hires and --exact cannot be used together\n");
	 status=1;
	 goto EXIT;
	}

	if     (BBexact) BBname="%%ExactBoundingBox:";
	else if(BBhires) BBname="%%HiresBoundingBox:";
	else             BBname="%%BoundingBox:";  

	if(!filter) {

	 if(!infile) {
	  usage();
	  fprintf(stderr,"no input file specified!\n");
	  status=1;
	  goto EXIT;
	 }

	 if((in=fopen(infile,"r")) == NULL) {
          char *infile2=malloc(strlen(infile)+4+1);
	  sprintf(infile2,"%s.eps",infile);
	  if((in=fopen(infile2,"r")) == NULL) { 
	   usage();
	   fprintf(stderr,"\nerror: cannot find either %s or %s.\n",infile,infile2);
	   free(infile2); 
	   status=1;
	   goto EXIT;
	  }
	  free(infile);
	  infile=infile2;
	 }
	 fclose(in);

	}else{

	 if(infile) {
	  fprintf(stderr,"Input file cannot be used with filter option!\n");
	  status=1;
	  goto EXIT;
	 }
	 infile=malloc(strlen("epstopdf")+1);
	 strcpy(infile,"epstopdf");   /* dummy input filename to generate tmp-filename */
	}


	if(debug) printf("This is epstopdf in C %s\n",EPSTOPDF_VERSION);

	/* find a temporary filename that does not exist yet */
	if(usegs && !tmpfile){
	 tmpfile=malloc(strlen(infile)+9);
	 count=0;
	 do{
	  if(count>99) {
           fprintf(stderr,"No temporary file available! Try deleting *.tmp.\n\n");
	   status=1;
           goto EXIT;
	  }
          sprintf(tmpfile,"%s.%d.tmp",infile,count);
	  if(debug) printf("checking temporary filename >>%s<<\n",tmpfile);
	  out=fopen(tmpfile,"r");
	  if(out) fclose(out);
	  count++;
	 }while(out);
	}

	if(!filter){
	 if(!outfile){
	  outfile=malloc(strlen(infile)+6);
	  strcpy(outfile,infile);
	  ptr=outfile;
	  while(strpbrk(ptr,"\\/")) ptr=strpbrk(ptr,"\\/")+1;
	  ptr=strrchr(ptr,'.');
	  if(ptr) *ptr='\0';
	  if(usegs) strcat(outfile,".pdf");
	  else      strcat(outfile,"2.eps");
	 }
	}else{
	 if(outfile) {
	  fprintf(stderr,"Output file cannot be used with filter option!\n");
	  status=1;
	  goto EXIT;
	 }
	 outfile=malloc(2);
	 strcpy(outfile,"-");
	}



	if(!filter) printf("Converting %s to %s ..... ",infile,outfile);


	/******************************************************************

	    put the pagesize from the bounding box into the eps file

	******************************************************************/

	
	if     (!filter) in  = fopen(infile, "rb");
	else             in  = stdin; 
	if     (usegs)   out = fopen(tmpfile,"wb");
	else if(!filter) out = fopen(outfile,"wb");
	else             out = stdout;

	if(!in || !out){
	  fprintf(stderr,"cannot open files\n");
	  status=1;
	  goto EXIT;
	}

	if(debug) printf("\nAdding correct pagesize to EPS ... searching for %s ...\n",BBname);

	depth=0;
	in_ps=FALSE;

	/* throw away binary junk before %! (PSSTART) */
	c1=fgetc(in);
	while(!feof(in)){
	 c2=fgetc(in);
	 if(feof(in)) break;

	 if(c1==PSSTART[0] && c2==PSSTART[1]){
          in_ps=TRUE;
	  break;
	 }
	 c1=c2;
	}
	if(!in_ps) {
	 printf("\nerror: could not find start of PS code!\n");
	 exit(1);
	}
	fputs(PSSTART,out);

	while(getline(line, in)){
	  if(!strncmp(line->data,BEGINDOC,strlen(BEGINDOC))) depth++;   /* count included documents */
	  if(!strncmp(line->data,ENDDOC,  strlen(ENDDOC)  )) depth--;
	  if(!strncmp(line->data,BBname,  strlen(BBname)) && depth==0) { /* look for BB comment in main doc only */
	    sscanf(line->data,"%*s %20s",buf);
	    if(!strcmp(buf,"(atend)")){                       /* BB is atended */  
	      if(filter){
	       fprintf(stderr,"Cannot look for BoundingBox in the trailer "
                              "with option --filter\n");
               if(usegs) remove(tmpfile);
	       status=1;
	       goto EXIT;
	      }
	      if(debug) printf("\n (atend)! ...\n");
	      fgetpos(in, &fpos); /* store file position */
	      found=FALSE;  
	      while (getline(line, in)){
	       if(!strncmp(line->data,BEGINDOC,strlen(BEGINDOC))) depth++;   /* count included documents */
	       if(!strncmp(line->data,ENDDOC,  strlen(ENDDOC)  )) depth--;
	       if(!strncmp(line->data,BBname,strlen(BBname)) && depth==0) { /* look for bounding box in main doc only */
		 found=TRUE;
		 fsetpos(in, &fpos);  /* rewind to (atend) comment */
		 break;
	       }	
              } 
	      if(!found){
	       fprintf(stderr,"atended %s not found\n",BBname);
               if(usegs  && !debug) remove(tmpfile);
               if(!usegs && !debug) remove(outfile);
	       status=1;
	       goto EXIT;
	      } 
	    }


	    /* No Idea what ExactBoundingBox means. Hope it also works with this code */

	    /* I thought Postscript says that the bounding box should be integer.
               However, some applications (like Corel Draw) use floats and gs has no
               problem with it. So I use floats for translate that will result in a
               more exact box. Since gs seems not to be able to use floats in 
               setpagedevice, these values are converted to integer */   

	    if(!BBCorrected){ /* write Bounding box one time only! */
	     if(sscanf(line->data,"%*s %lf %lf %lf %lf",&bbllx,&bblly,&bburx,&bbury)!=4){
	      fprintf(stderr,"incorrect %s \n",BBname);
              if(usegs && !debug) remove(tmpfile);
	      status=1;
	      goto EXIT;
	     }
	     if(debug) printf("BoundingBox: %f %f %f %f%s",bbllx,bblly,bburx,bbury,newline);
             bblly  -= enlarge;
             bbllx  -= enlarge;
             bbury  += enlarge;
             bburx  += enlarge;
	     width   = ceil(bburx-bbllx);  /* make papersize integer and enlarge it a little bit */
	     height  = ceil(bbury-bblly);
	     xoffset =-bbllx;
	     yoffset =-bblly;
	     fprintf(out,"%s %d %d %d %d%s",BBname,0,0,width,height,newline);
	     fprintf(out,"<< /PageSize [%d %d] >> setpagedevice%s",width,height,newline);
	     if(isint(xoffset) && isint(yoffset)) fprintf(out,"gsave %d %d translate%s",round(xoffset),round(yoffset),newline);
	     else                                 fprintf(out,"gsave %f %f translate%s",xoffset,yoffset,newline);
	     if(!filter) printf(" (%dx%d mm) ... ",(int)(25.4/72.*width),(int)(25.4/72.*height));
	     did_CR=1;
	     BBCorrected=1;
	    }
	  }else{
	    putline(line, out);
	    if(line->data[line->len-1]==CR ||
               line->data[line->len-1]==LF    ) did_CR=1;
	    else                                did_CR=0;
	    if(!strncmp(line->data,PSEOF,strlen(PSEOF)) && depth==0) break; /* end of file */
	  }
	}
	if(BBCorrected){
	  if(!did_CR) fprintf(out,newline);
	  fprintf(out,"grestore%s",newline);
	}
	if(in !=stdin ) fclose(in);
	if(out!=stdout) fclose(out);

	if(width && height){
	 if      (xpix) res=(72*xpix)/width;
	 else if (ypix) res=(72*ypix)/height;
	}

	if(debug) {
	 if     (newline[0]==LF && newline[1]==CR) printf(" newline is LF + CR ... ");
	 else if(newline[0]==LF)                   printf(" newline is LF ... ");
	 else if(newline[0]==CR)                   printf(" newline is LF ... ");
	}

	/******************************************************************

		do the conversion eps->pdf using gs

	******************************************************************/


	if(usegs){

	 if(compress)  copt="-dUseFlateCompression=true";
	 else          copt="-dUseFlateCompression=false";

	 if(lossy_compress)  lopt="-dEncodeColorImages=true";
	 else                lopt="-dEncodeColorImages=false";
		 
	 if(res)       sprintf(ropt,"-r%d",res);
	 else          ropt[0]='\0';

	 if(antialias) aliasopt="-dTextAlphaBits=4 -dGraphicsAlphaBits=4";
	 else          aliasopt="";

	 if(res && debug) printf(" (%d dpi) ... ",res);

	 /* The shell 4nt has Problems with double quotes in the command line.
	    Thus use them only if it is really necessary (if there are space
            characters in a filename)
	 */
	 if(strchr(outfile,' ') || strchr(tmpfile,' '))
  	  sprintf(command,"%s -q -dNOPAUSE -dBATCH %s %s %s -sDEVICE=%s %s %s"
                          " \"-sOutputFile=%s\" -f \"%s\"",
		 	  gsexec,copt,lopt,aliasopt,device,ropt,gsopt,outfile,tmpfile);
	 else
	  sprintf(command,"%s -q -dNOPAUSE -dBATCH %s %s %s -sDEVICE=%s %s %s"
                          " -sOutputFile=%s -f %s",
		 	  gsexec,copt,lopt,aliasopt,device,ropt,gsopt,outfile,tmpfile);


	 if(debug) printf("running ghostscript ...\n");
	 if(debug) puts(command);
         status=system(command);
	 if(!debug) remove(tmpfile);
	 else       printf("keeping temporary file >>%s<<\n",tmpfile);
	}
	
	if(!filter) printf("Done\n");



	EXIT:

	free(outfile);
	free(tmpfile);
	free(infile);
	free(gsopt);
	free(line->data);
	free(line);

	if(wait){
	 printf("\n<Press a key> ");
	 getchar();
	 printf("\n");
	}

	return status;
}
