/* --------------------------------------------------------------------
 * jpeg2ps
 * convert JPEG files to compressed PostScript Level 2 or 3 EPS
 *
 * (C) Thomas Merz 1994-2002
 *
 * ------------------------------------------------------------------*/

#define VERSION		"V1.9"

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

/* try to identify Mac compilers */
#ifdef __MWERKS__
#if __POWERPC__ || __CFM68K__ || __MC68K_
#define MAC
#elif defined(__INTEL__)
#define DOS
#endif
#endif /* __MWERKS__ */

#if defined(__CYGWIN32__)
#include <getopt.h>
#elif defined(DOS)
int getopt(int argc, char * const argv[], const char *optstring);
extern char *optarg;            
extern int optind;              
#elif !defined(DOS) && !defined(MAC)
#include <unistd.h>
#endif

#ifndef MAC
extern char *optarg;
extern int optind;
#endif

#ifdef DOS
#include <io.h>
#include <fcntl.h>
#endif

/* Requires the DropUNIX package */
#if defined(MAC) && defined(DROPUNIX)
#include "Main.h"		/* Required for DropUNIX */
#endif

#include "psimage.h"

#ifdef REQUIRES_GETOPT
int      getopt(int nargc, char **nargv, char *ostr);
#endif

#if (defined(DOS) || defined (MAC))
#define READMODE  "rb"           /* read JPEG files in binary mode */
#define WRITEMODE "wb"           /* write (some) PS files in binary mode */
#else
#define READMODE  "r"
#define WRITEMODE "w"           /* write (some) PS files in binary mode */
#endif

int Margin     	= 20;           /* safety margin */
BOOL quiet	= FALSE;	/* suppress informational messages */
BOOL autorotate = FALSE;	/* disable automatic rotation */

BOOL	AnalyzeJPEG(imagedata * image);
int	ASCII85Encode(FILE *in, FILE *out);
void    ASCIIHexEncode(FILE *in, FILE *out);

#define BUFFERSIZE 1024
static char buffer[BUFFERSIZE];
static char *ColorSpaceNames[] = {"", "Gray", "", "RGB", "CMYK" };

/* Array of known page sizes including name, width, and height */

typedef struct { const char *name; int width; int height; } PageSize_s;

PageSize_s PageSizes[] = {
    /* ISO paper sizes */
    {"a0",	2380, 3368},
    {"a1",	1684, 2380},
    {"a2",	1190, 1684},
    {"a3",	842, 1190},
    {"a4",	595, 842},
    {"a5",	421, 595},
    {"a6",	297, 421},
    {"b5",	501, 709},

    /* US customary sizes */
    {"letter",	612, 792},
    {"legal",	612, 1008},
    {"ledger",	1224, 792},
    {"p11x17",	792, 1224}
};

#define PAGESIZELIST	(sizeof(PageSizes)/sizeof(PageSizes[0]))

#ifdef A4
int PageWidth  = 595;           /* page width A4 */
int PageHeight = 842;           /* page height A4 */
#else
int PageWidth  = 612;           /* page width letter */
int PageHeight = 792;           /* page height letter */
#endif

static void 
JPEGtoPS(imagedata *JPEG, FILE *PSfile) {
  int llx, lly, urx, ury;        /* Bounding box coordinates */
  size_t n;
  float scale, sx, sy;           /* scale factors            */
  time_t t;
  int i;

  /* read image parameters and fill JPEG struct*/
  if (!AnalyzeJPEG(JPEG)) {
    fprintf(stderr, "Error: '%s' is not a proper JPEG file!\n", JPEG->filename);
    return;
  }

  if (!quiet)
      fprintf(stderr, "Note on file '%s': %dx%d pixel, %d color component%s\n",
	JPEG->filename, JPEG->width, JPEG->height, JPEG->components,
	(JPEG->components == 1 ? "" : "s"));

  /* "Use resolution from file" was requested, but we couldn't find any */
  if (JPEG->dpi == DPI_USE_FILE && !quiet) { 
    fprintf(stderr,
    	"Note: no resolution values found in JPEG file - using standard scaling.\n");
    JPEG->dpi = DPI_IGNORE;
  }

  if (JPEG->dpi == DPI_IGNORE) {
    if (JPEG->width > JPEG->height && autorotate) {	/* switch to landscape if needed */
      JPEG->landscape = TRUE;
      if (!quiet)
	  fprintf(stderr, 
	    "Note: image width exceeds height - producing landscape output!\n");
    }
    if (!JPEG->landscape) {       /* calculate scaling factors */
      sx = (float) (PageWidth - 2*Margin) / JPEG->width;
      sy = (float) (PageHeight - 2*Margin) / JPEG->height;
    }else {
      sx = (float) (PageHeight - 2*Margin) / JPEG->width;
      sy = (float) (PageWidth - 2*Margin) / JPEG->height;
    }
    scale = min(sx, sy);	/* We use at least one edge of the page */
  } else {
    if (!quiet)
	fprintf(stderr, "Note: Using resolution %d dpi.\n", (int) JPEG->dpi);
    scale = (float) (72.0 / JPEG->dpi);     /* use given image resolution */
  }

  if (JPEG->landscape) {
    /* landscape: move to (urx, lly) */
    urx = PageWidth - Margin;
    lly = Margin;
    ury = (int) (Margin + scale*JPEG->width + 0.9);    /* ceiling */
    llx = (int) (urx - scale * JPEG->height);          /* floor  */
  }else {
    /* portrait: move to (llx, lly) */
    llx = lly = Margin;
    urx = (int) (llx + scale * JPEG->width + 0.9);     /* ceiling */
    ury = (int) (lly + scale * JPEG->height + 0.9);    /* ceiling */
  }

  time(&t);

  /* produce EPS header comments */
  fprintf(PSfile, "%%!PS-Adobe-3.0 EPSF-3.0\n");
  fprintf(PSfile, "%%%%Creator: jpeg2ps %s by Thomas Merz\n", VERSION);
  fprintf(PSfile, "%%%%Title: %s\n", JPEG->filename);
  fprintf(PSfile, "%%%%CreationDate: %s", ctime(&t));
  fprintf(PSfile, "%%%%BoundingBox: %d %d %d %d\n", 
                   llx, lly, urx, ury);
  fprintf(PSfile, "%%%%DocumentData: %s\n", 
                  JPEG->mode == BINARY ? "Binary" : "Clean7Bit");
  fprintf(PSfile, "%%%%LanguageLevel: 2\n");
  fprintf(PSfile, "%%%%EndComments\n");
  fprintf(PSfile, "%%%%BeginProlog\n");
  fprintf(PSfile, "%%%%EndProlog\n");
  fprintf(PSfile, "%%%%Page: 1 1\n");

  fprintf(PSfile, "/languagelevel where {pop languagelevel 2 lt}");
  fprintf(PSfile, "{true} ifelse {\n");
  fprintf(PSfile, "  (JPEG file '%s' needs PostScript Level 2!",
                  JPEG->filename);
  fprintf(PSfile, "\\n) dup print flush\n");
  fprintf(PSfile, "  /Helvetica findfont 20 scalefont setfont ");
  fprintf(PSfile, "100 100 moveto show showpage stop\n");
  fprintf(PSfile, "} if\n");

  fprintf(PSfile, "save\n");
  fprintf(PSfile, "/RawData currentfile ");

  if (JPEG->mode == ASCIIHEX)            /* hex representation... */
    fprintf(PSfile, "/ASCIIHexDecode filter ");
  else if (JPEG->mode == ASCII85)        /* ...or ASCII85         */
    fprintf(PSfile, "/ASCII85Decode filter ");
  /* else binary mode: don't use any additional filter! */

  fprintf(PSfile, "def\n");

  fprintf(PSfile, "/Data RawData << ");
  fprintf(PSfile, ">> /DCTDecode filter def\n");

  /* translate to lower left corner of image */
  fprintf(PSfile, "%d %d translate\n", (JPEG->landscape ? 
                   PageWidth - Margin : Margin), Margin);

  if (JPEG->landscape)                 /* rotation for landscape */
    fprintf(PSfile, "90 rotate\n");
      
  fprintf(PSfile, "%.2f %.2f scale\n", /* scaling */
                   JPEG->width * scale, JPEG->height * scale);
  fprintf(PSfile, "/Device%s setcolorspace\n", 
                  ColorSpaceNames[JPEG->components]);
  fprintf(PSfile, "{ << /ImageType 1\n");
  fprintf(PSfile, "     /Width %d\n", JPEG->width);
  fprintf(PSfile, "     /Height %d\n", JPEG->height);
  fprintf(PSfile, "     /ImageMatrix [ %d 0 0 %d 0 %d ]\n",
                  JPEG->width, -JPEG->height, JPEG->height);
  fprintf(PSfile, "     /DataSource Data\n");
  fprintf(PSfile, "     /BitsPerComponent %d\n", 
                  JPEG->bits_per_component);

  /* workaround for color-inverted CMYK files produced by Adobe Photoshop:
   * compensate for the color inversion in the PostScript code
   */
  if (JPEG->adobe && JPEG->components == 4) {
    if (!quiet)
	fprintf(stderr, "Note: Adobe-conforming CMYK file - applying workaround for color inversion.\n");
    fprintf(PSfile, "     /Decode [1 0 1 0 1 0 1 0]\n");
  }else {
    fprintf(PSfile, "     /Decode [0 1");
    for (i = 1; i < JPEG->components; i++) 
      fprintf(PSfile," 0 1");
    fprintf(PSfile, "]\n");
  }

  fprintf(PSfile, "  >> image\n");
  fprintf(PSfile, "  Data closefile\n");
  fprintf(PSfile, "  RawData flushfile\n");
  fprintf(PSfile, "  showpage\n");
  fprintf(PSfile, "  restore\n");
  fprintf(PSfile, "} exec");

  /* seek to start position of JPEG data */
  fseek(JPEG->fp, JPEG->startpos, SEEK_SET);

  switch (JPEG->mode) {
  case BINARY:
    /* important: ONE blank and NO newline */
    fprintf(PSfile, " ");
#if !defined(__MWERKS__) && defined(DOS)
    fflush(PSfile);         	  /* up to now we have CR/NL mapping */
    setmode(fileno(PSfile), O_BINARY);    /* continue in binary mode */
#endif
    /* copy data without change */
    while ((n = fread(buffer, 1, sizeof(buffer), JPEG->fp)) != 0)
      fwrite(buffer, 1, n, PSfile);
#if !defined(__MWERKS__) && defined(DOS)
    fflush(PSfile);                  	/* binary yet */
    setmode(fileno(PSfile), O_TEXT);    /* text mode */
#endif
    break;

  case ASCII85:
    fprintf(PSfile, "\n");

    /* ASCII85 representation of image data */
    if (ASCII85Encode(JPEG->fp, PSfile)) {
      fprintf(stderr, "Error: internal problems with ASCII85Encode!\n");
      exit(1);
    }
    break;

  case ASCIIHEX:
    /* hex representation of image data (useful for buggy dvips) */
    ASCIIHexEncode(JPEG->fp, PSfile);
    break;
  }
  fprintf(PSfile, "\n%%%%EOF\n");
}

static
void usage(void) {
  fprintf(stderr, "jpeg2ps %s: convert JPEG files to PostScript Level 2 or 3.\n",
		   VERSION);
  fprintf(stderr, "(C) Thomas Merz 1994-2002\n\n");
  fprintf(stderr, "usage: jpeg2ps [options] jpegfile > epsfile\n");
  fprintf(stderr, "-a        auto rotate: produce landscape output if width > height\n");
  fprintf(stderr, "-b        binary mode: output 8 bit data (default: 7 bit with ASCII85)\n");
  fprintf(stderr, "-h        hex mode: output 7 bit data in ASCIIHex encoding\n");
  fprintf(stderr, "-o <name> output file name\n");
  fprintf(stderr, "-p <size> page size name. Known names are:\n");
  fprintf(stderr, "          a0, a1, a2, a3, a4, a5, a6, b5, letter, legal, ledger, p11x17\n");
  fprintf(stderr, "-q        quiet mode: suppress all informational messages\n");
  fprintf(stderr, "-r <dpi>  resolution value (dots per inch)\n");
  fprintf(stderr, "          0 means use value given in file, if any (disables autorotate)\n");
  exit(1);
}

int
main(int argc, char ** argv) {
  imagedata image;
  FILE *outfile;

#ifdef MAC
  int i, bufLength;
  char *cp, outfilename[512];
#else
  int opt, pagesizeindex = -1;
#endif

  image.filename = NULL;
  image.mode     = ASCII85;
  image.startpos = 0L;
  image.landscape= FALSE;
  image.dpi      = DPI_IGNORE;
  image.adobe    = FALSE;

  outfile = stdout;

 if (argc == 1)
    usage();

#ifndef MAC
  while ((opt = getopt(argc, argv, "abho:p:qr:")) != -1)
    switch (opt) {
      case 'a':
          autorotate = TRUE;
          break;
      case 'b':
	  image.mode = BINARY;
	  break;
      case 'h':
	  image.mode = ASCIIHEX;
	  break;
      case 'o':
	  outfile = fopen(optarg, "w");
	  if (outfile == NULL) {
	    fprintf(stderr, "Error: cannot open output file '%s'.\n", optarg);
	    exit(-2);
	  } 
	  break;
      case 'p':
	  for(pagesizeindex=0; pagesizeindex < PAGESIZELIST; pagesizeindex++)
	    if (!strcmp((const char *) optarg, PageSizes[pagesizeindex].name)) {
		PageHeight = PageSizes[pagesizeindex].height;
		PageWidth = PageSizes[pagesizeindex].width;
		break;
	    }
	  if (pagesizeindex == PAGESIZELIST) {	/* page size name not found */
		fprintf(stderr, "Error: Unknown page size %s.\n", optarg);
		exit(-3);
	  }
	  break;
      case 'q':
          quiet = TRUE;
	  break;
      case 'r':
	  image.dpi = (float) atof(optarg);
	  if (image.dpi < 0) {
	    fprintf(stderr, "Error: bad resolution value %f !\n", image.dpi);
	    exit(1);
	  }
	  break;
      case '?':
	  usage();
    }

  if (pagesizeindex != -1 && ! quiet)	/* page size user option given */
      fprintf(stderr, "Note: Using %s page size.\n",
		    PageSizes[pagesizeindex].name);

  if (optind == argc)	/* filename missing */
    usage();
  else
    image.filename = argv[optind];

  if (!image.filename)
    usage();

  if ((image.fp = fopen(image.filename, READMODE)) == NULL) {
    fprintf(stderr, "Error: couldn't read JPEG file '%s'!\n", 
      image.filename),
    exit(1);
  }

  JPEGtoPS(&image, outfile);      /* convert JPEG data */
  fclose(image.fp);
  fclose(outfile);

#else /* MAC */

 for (i = 1; i < argc; i++) {
    image.filename = argv[i];

    strcpy(outfilename, image.filename);
    bufLength = (int) strlen(outfilename);
    cp = outfilename;
    if (bufLength > 3)
    {
	cp += (bufLength - 4);
	/* strip .jpg from terminating string */
	if (strcmp(cp, ".jpg") == 0 || strcmp(cp, ".JPG") == 0) 
	    outfilename[bufLength - 4] = '\0'; 		
    }
    
    strcat(outfilename, ".eps");

     if ((image.fp = fopen(image.filename, READMODE)) == NULL) {
	fprintf(stderr, "Error: couldn't read JPEG file '%s'!\n", 
	image.filename),
	exit(1);
     }

    outfile = fopen(outfilename, WRITEMODE);

    JPEGtoPS(&image, outfile);      /* convert JPEG data */

    fclose(image.fp);
    fclose(outfile);
  }

#endif	/* not MAC */

  return 0;
}
