/*  $Header$

    This is dvipdfm, a DVI to PDF translator.
    Copyright (C) 1998, 1999 by Mark A. Wicks

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    
    The author may be contacted via the e-mail address

	mwicks@kettering.edu
*/

	
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <ctype.h>
#include "config.h"
#include "system.h"
#include "mem.h"
#include "mfileio.h"
#include "dvi.h"
#include "pdfdoc.h"
#include "pdfdev.h"
#include "encodings.h"
#include "type1.h"
#include "ttf.h"
#include "colorsp.h"
#include "pdfspecial.h"
#include "pdfparse.h"
#include "vf.h"
#include "pkfont.h"
#include "thumbnail.h"
#include "psimage.h"
#include "tfm.h"

struct rect 
{
  double width;
  double height;
};
typedef struct rect rect;

struct 
{
  char *s;
  struct rect data;
} paper_sizes[] = {
  {"letter" , { 612.0, 792.0}},
  {"legal" , { 612.0, 1008.0}},
  {"ledger" , { 1224.0, 792.0}},
  {"tabloid" , { 792.0, 1224.0}},
  {"a5" , { 420.91, 595.27}},
  {"a4" , { 595.27, 841.82}},
  {"a3" , { 841.82, 1190.16}}};

static rect get_paper_size (char *string)
{
  int i;
  for (i=0; i<sizeof(paper_sizes)/sizeof(paper_sizes[0]); i++) {
    if (!strcmp (string, paper_sizes[i].s))
      break;
  }
  if (i == sizeof(paper_sizes)/sizeof(paper_sizes[0]))
    ERROR ("Paper size is invalid");
  return paper_sizes[i].data;
}


char *dvi_filename = NULL, *pdf_filename = NULL;
static void set_default_pdf_filename(void)
{
  const char *dvi_base;
#ifdef HAVE_XBASENAME
  dvi_base = xbasename (dvi_filename);
#else   
  dvi_base = basename (dvi_filename);
#endif   
  if (strlen (dvi_base) < 5 || strncmp (".dvi", dvi_base+strlen(dvi_base)-4, 4)) 
  {
    pdf_filename = NEW (strlen(dvi_base)+5, char);
    strcpy (pdf_filename, dvi_base);
    strcat (pdf_filename, ".pdf");
  } else
  {
    pdf_filename = NEW (strlen(dvi_base)+1, char);
    strncpy (pdf_filename, dvi_base, strlen(dvi_base)-4);
    strcpy (pdf_filename+strlen(dvi_base)-4, ".pdf");
  }
}
static void usage (void)
{
   fprintf (stdout, "\n%s, version %s, Copyright (C) 1998, 1999 by Mark A. Wicks\n", PACKAGE, VERSION);
   fprintf (stdout, "dvipdfm comes with ABSOLUTELY NO WARRANTY.\n");
   fprintf (stdout, "This is free software, and you are welcome to redistribute it\n");
   fprintf (stdout, "under certain conditions.  Details are distributed with the software.\n");
   fprintf (stdout, "\nUsage: dvipdfm [options] dvifile\n");
   fprintf (stdout, "-c      \tIgnore color specials (for B&W printing)\n");
   fprintf (stdout, "-f filename\tSet font map file name [t1fonts.map]\n");
   fprintf (stdout, "-o filename\tSet output file name [dvifile.pdf]\n");
   fprintf (stdout, "-l \t\tLandscape mode\n");
   fprintf (stdout, "-m number\tSet additional magnification\n");
   fprintf (stdout, "-p papersize\tSet papersize (letter, legal,\n");
   fprintf (stdout, "            \tledger, tabloid, a4, or a3) [letter]\n");
   fprintf (stdout, "-r resolution\tSet resolution (in DPI) for raster fonts [600]\n");
   fprintf (stdout, "-s pages\tSelect page ranges (-)\n");
   fprintf (stdout, "-t      \tEmbed thumbnail images\n");
   fprintf (stdout, "-d      \tRemove thumbnail images when finished\n");
   fprintf (stdout, "-x dimension\tSet horizontal offset [1.0in]\n");
   fprintf (stdout, "-y dimension\tSet vertical offset [1.0in]\n");
   fprintf (stdout, "-e          \tDisable partial font embedding [default is enabled])\n");
   fprintf (stdout, "-z number\tSet compression level (0-9) [default is 9])\n");
   fprintf (stdout, "-v          \tBe verbose\n");
   fprintf (stdout, "-vv         \tBe more verbose\n");
   fprintf (stdout, "\nAll dimensions entered on the command line are \"true\" TeX dimensions.\n");
   fprintf (stdout, "Argument of \"-s\" lists physical page ranges separated by commas, e.g., \"-s 1-3,5-6\"\n\n");
   exit(1);
}

static double paper_width = 612.0, paper_height = 792.0;
static char landscape_mode = 0;
static char ignore_colors = 0;
static double mag = 1.0, x_offset=72.0, y_offset=72.0;
static int font_dpi = 600;
static int really_quiet = 0;

struct page_range 
{
  unsigned int first, last;
} *page_ranges = NULL;
int max_page_ranges = 0, num_page_ranges = 0;

void set_landscape_mode (void)
{
  dev_set_page_size (paper_height, paper_width);
}

#define pop_arg() {argv += 1; argc -= 1;}
static void do_args (int argc, char *argv[])
{
  char *flag;
  while (argc > 0 && *argv[0] == '-') {
    for (flag=argv[0]+1; *flag != 0; flag++) {
      switch (*flag) {
      case 'D':
	if (argc < 2) {
	  fprintf (stderr, "PS->PDF conversion command line template missing\n\n");
	  usage();
	}
	set_distiller_template(argv[1]);
	pop_arg();
	break;
      case 'r':
	if (argc < 2) {
	  fprintf (stderr, "\nResolution specification missing a number\n\n");
	  usage();
	}
	{
	  char *result, *end, *start = argv[1];
	  end = start + strlen(argv[1]);
	  result = parse_number (&start, end);
	  if (result != NULL && start == end) {
	    font_dpi = (int) atof (result);
	  }
	  else {
	    fprintf (stderr, "\nError in number following resolution specification\n\n");
	    usage();
	  }
	  if (result != NULL) {
	    RELEASE (result);
	  }
	}
	pop_arg();
	break;
      case 'm':
	if (argc < 2) {
	  fprintf (stderr, "\nMagnification specification missing a number\n\n");
	  usage();
	}
	{
	  char *result, *end, *start = argv[1];
	  end = start + strlen(argv[1]);
	  result = parse_number (&start, end);
	  if (result != NULL && start == end) {
	    mag = atof (result);
	  }
	  else {
	    fprintf (stderr, "\nError in number following magnification specification\n\n");
	    usage();
	  }
	  if (result != NULL) {
	    RELEASE (result);
	  }
	}
	pop_arg();
	break;
      case 'g':
	if (argc < 2) {
	  fprintf (stderr, "\nAnnotation \"grow\" specification missing a number\n\n");
	  usage();
	}
	{
	  char *result, *end, *start = argv[1];
	  end = start + strlen(argv[1]);
	  result = parse_number (&start, end);
	  if (result != NULL && start == end) {
	    pdf_special_set_grow (atof (result));
	  }
	  else {
	    fprintf (stderr, "\nError in number following magnification specification\n\n");
	    usage();
	  }
	  if (result != NULL) {
	    RELEASE (result);
	  }
	}
	pop_arg();
	break;
      case 'x':
	if (argc < 2) {
	  fprintf (stderr, "\nX Offset specification missing a number\n\n");
	  usage();
	}
	{
	  char *result, *end, *start = argv[1];
	  double unit;
	  end = start + strlen(argv[1]);
	  result = parse_number (&start, end);
	  if (result != NULL) {
	    x_offset = atof (result);
	  }
	  else {
	    fprintf (stderr, "\nError in number following xoffset specification\n\n");
	    usage();
	  }
	  if (result != NULL) {
	    RELEASE (result);
	  }
	  unit = parse_one_unit(&start, end);
	  if (unit > 0.0) {
	    x_offset *= unit;
	  }
	  else {
	    fprintf (stderr, "\nError in dimension specification following xoffset\n\n");
	    usage();
	  }
	}
	pop_arg();
	break;
      case 'y':
	if (argc < 2) {
	  fprintf (stderr, "\nY offset specification missing a number\n\n");
	  usage();
	}
	{
	  char *result, *end, *start = argv[1];
	  double unit;
	  end = start + strlen(argv[1]);
	  result = parse_number (&start, end);
	  if (result != NULL) {
	    y_offset = atof (result);
	  }
	  else {
	    fprintf (stderr, "\nError in number following yoffset specification\n\n");
	    usage();
	  }
	  if (result != NULL) {
	    RELEASE (result);
	  }
	  unit = parse_one_unit(&start, end);
	  if (unit > 0.0) {
	    y_offset *= unit;
	  }
	  else {
	    fprintf (stderr, "\nError in dimension specification following yoffset\n\n");
	    usage();
	  }
	}
	pop_arg();
	break;
      case 'o':  
	if (argc < 2)
	  ERROR ("Missing output file name");
	pdf_filename = NEW (strlen(argv[1])+1,char);
	strcpy (pdf_filename, argv[1]);
	pop_arg();
	break;
      case 's':
	{
	  char *result, *end, *start = argv[1];
	  if (argc < 2)
	    ERROR ("Missing page selection specification");
	  end = start + strlen (argv[1]);
	  while (start < end) {
	    /* Enlarge page range table if necessary */
	    if (num_page_ranges >= max_page_ranges) {
	      max_page_ranges += 4;
	      page_ranges = RENEW (page_ranges, max_page_ranges,
				   struct page_range);
	    }
	    skip_white (&start, end);
	    page_ranges[num_page_ranges].first = 0;
	    if ((result = parse_unsigned (&start, end))) {
	      page_ranges[num_page_ranges].first = atoi(result)-1;
	      page_ranges[num_page_ranges].last =
		page_ranges[num_page_ranges].first;
	      RELEASE(result);
	    }
	    skip_white (&start, end);
	    if (*start == '-') {
	      start += 1;
	      page_ranges[num_page_ranges].last = UINT_MAX;
	      skip_white (&start, end);
	      if (start < end &&
		  ((result = parse_unsigned (&start, end)))) {
		page_ranges[num_page_ranges].last = atoi(result)-1;
		RELEASE (result);
	      }
	    }
	    num_page_ranges += 1;
	    skip_white (&start, end);
	    if (start < end && *start == ',') {
	      start += 1;
	      continue;
	    }
	    skip_white (&start, end);
	    if (start < end) {
	      fprintf (stderr, "Page selection? %s", start);
	      ERROR ("Bad page range specification");
	    }
	  }
	  pop_arg();
	}
	break;
      case 't':
	{
#ifdef HAVE_LIBPNG
	  pdf_doc_enable_thumbnails ();
#else
	  ERROR ("The thumbnail option requires libpng, which you apparently don't have.");
#endif /* HAVE_LIBPNG */
	}
	break;
      case 'd':
	{
#ifdef HAVE_LIBPNG
	  thumb_remove ();
#else
	  ERROR ("The thumbnail option requires libpng, which you apparently don't have.");
#endif /* HAVE_LIBPNG */
	}
	break;
      case 'p':
	{
	  rect paper_size = get_paper_size (argv[1]);
	  if (argc < 2)
	    ERROR ("Missing paper size");
	  paper_width = paper_size.width;
	  paper_height = paper_size.height;
	  pop_arg();
	}
	break;
      case 'c':
	ignore_colors = 1;
	break;
      case 'l':
	landscape_mode = 1;
	break;
      case 'f':
	dev_read_mapfile (argv[1]);
	pop_arg();
	break;
      case 'e':
	type1_disable_partial();
	break;
      case 'q':
	really_quiet = 1;
	break;
      case 'v':
	encoding_set_verbose();
	dev_set_verbose();
	dvi_set_verbose();
	type1_set_verbose();
	vf_set_verbose();
	pk_set_verbose();
	pdf_obj_set_verbose();
	pdf_doc_set_verbose();
	tfm_set_verbose();
#ifdef HAVE_TTF_FORMATS	 
	ttf_set_verbose();
#endif	 
	break;
      case 'V':
	{
	  unsigned level = 2;
	  if (isdigit (*(flag+1))) {
	    level = *(++flag) - '0';
	  } else {
	    char *result, *end, *start = argv[1];
	    if (argc < 2) {
	      fprintf (stderr, "\nVersion specification missing number (2 or 3)\n\n");
	      usage();
	    }
	    end = start + strlen(argv[1]);
	    result = parse_number (&start, end);
	    if (result != NULL && start == end) {
	      level = (int) atof (result);
	    }
	    else {
	      fprintf (stderr, "\nError in number following version specification\n\n");
	      usage();
	    }
	    if (result != NULL) {
	      RELEASE (result);
	    }
	    pop_arg();
	  }
	  if (level >= 2 && level <= 3) {
	    pdf_set_version(level);
	  } else {
	    fprintf (stderr, "\nNumber following version specification is out of range\n\n");
	  }
	}
	break;
      case 'z': 
	{
	  int level = 9;
#ifndef HAVE_ZLIB
	  fprintf (stderr, "\nYou don't have compression compiled in.  Possibly libz wasn't found by configure.\nCompression specification will be ignored.\n\n");
#endif  /* HAVE_ZLIB */
	  if (isdigit (*(flag+1))) {
	    level = *(++flag) - '0';
	  } else {
	    char *result, *end, *start = argv[1];
	    if (argc < 2) {
	      fprintf (stderr, "\nCompression specification missing number for level\n\n");
	      usage();
	    }
	    end = start + strlen(argv[1]);
	    result = parse_number (&start, end);
	    if (result != NULL && start == end) {
	      level = (int) atof (result);
	    }
	    else {
	      fprintf (stderr, "\nError in number following compression specification\n\n");
	      usage();
	    }
	    if (result != NULL) {
	      RELEASE (result);
	    }
	    pop_arg();
	  }
	  if (level >= 0 && level <= 9) {
	    pdf_obj_set_compression(level);
	  } else {
	    fprintf (stderr, "\nNumber following compression specification is out of range\n\n");
	  }
	}
	break;
      default:
	usage();
      }
    }
    argc -= 1 ;
    argv += 1;
  }
  if (argc > 1) {
    fprintf (stderr, "\nMultiple dvi filenames?\n\n");
    usage();
  }
  /* The only legitimate way to have argc == 0 here is
     is do_args was called from config file.  In that case, there is
     no dvi file name.  Check for that case  */
  if (argc > 0) {
    if (strncmp (".dvi", argv[0]+strlen(argv[0])-4, 4)) {
      dvi_filename = NEW (strlen (argv[0])+1+4, char);
      strcpy (dvi_filename, argv[0]);
      strcat (dvi_filename, ".dvi");
    }
    else {
      dvi_filename = NEW (strlen (argv[0])+1, char);
      strcpy (dvi_filename, argv[0]);
    }
  }
}


static void cleanup(void)
{
  RELEASE (dvi_filename);
  RELEASE (pdf_filename);
  if (page_ranges)
    RELEASE (page_ranges);
  psimage_close();
}

static char *config_file_name = "config";
static void read_config_file (void)
{
  char *full_config_name, *start, *end;
  char *argv[2], *option;
  FILE *config_file;
  
  if ((full_config_name = kpse_find_file (config_file_name,
				   kpse_program_text_format,
				   true)) == NULL) {
    return;
  }
  if (!(config_file = MFOPEN (full_config_name, FOPEN_R_MODE))) {
    fprintf (stderr, "\nError opening configuration file.  Continuing with defaults.\n");
    return;
  }
  while ((start = mfgets (work_buffer, WORK_BUFFER_SIZE,
			  config_file))) {
    int argc = 0;
    end = work_buffer+strlen(work_buffer);
    skip_white (&start, end);
    if (start >= end)
      continue;
    /* Build up an argument list as if it were passed on the command
       line */
    if ((option = parse_ident (&start, end))) {
      argc = 1;
      argv[0] = NEW (strlen(option)+2, char);
      strcpy (argv[0]+1, option);
      RELEASE (option);
      *argv[0] = '-';
      skip_white (&start, end);
      if (start < end) {
	argc += 1;
	if (*start == '"') {
	  argv[1] = parse_c_string (&start, end);
	}
	else
	  argv[1] = parse_ident (&start, end);
      }
    }
    do_args (argc, argv);
    while (argc > 0) {
      RELEASE (argv[--argc]);
    }
  }
}

void error_cleanup (void)
{
  pdf_error_cleanup();
  remove (pdf_filename);
  fprintf (stderr, "\nOutput file removed.\n");
}

int CDECL main (int argc, char *argv[]) 
{
  int i;
  int at_least_one_page = 0;
  if (argc < 2) {
    fprintf (stderr, "\nNo dvi filename specified.\n\n");
    usage();
    return 1;
  }
#ifdef KPATHSEA
  kpse_set_program_name (argv[0], NULL);
#endif

  argv+=1;
  argc-=1;

  /* Process config file, if any */
  read_config_file();

  do_args (argc, argv);

#ifdef KPATHSEA
  kpse_init_prog ("", font_dpi, NULL, NULL);
#endif
  pk_set_dpi (font_dpi);
#ifdef KPATHSEA
  kpse_set_program_enabled (kpse_pk_format, true, kpse_src_texmf_cnf);
#endif

  if (!dvi_filename) {
    fprintf (stderr, "\nNo dvi filename specified.\n\n");
    usage();
  }

  /* Check for ".dvi" at end of argument name */
  if (pdf_filename == NULL)
    set_default_pdf_filename();
  
  if (!really_quiet)
    fprintf (stdout, "\n%s -> %s\n", dvi_filename, pdf_filename);

  dvi_init (dvi_filename, pdf_filename, mag, x_offset, y_offset);

  if (ignore_colors) {
    color_special_ignore_colors();
    pdf_special_ignore_colors();
  }
  
  if (landscape_mode)
    dev_set_page_size (paper_height, paper_width);
  else
    dev_set_page_size (paper_width, paper_height);

  if ((num_page_ranges))
    for (i=0; i<num_page_ranges; i++) {
      unsigned j;
      if (page_ranges[i].first <= page_ranges[i].last)
	for (j=page_ranges[i].first; j<=page_ranges[i].last && j<dvi_npages(); j++) {
 	  fprintf (stderr, "[%d", j+1);
   	  dvi_do_page (j);
	  at_least_one_page = 1;
	  fprintf (stderr, "]");
	}
      else
	for (j=page_ranges[i].first;
	     j>=page_ranges[i].last && j<dvi_npages(); j--) {
	  fprintf (stderr, "[%d", j+1);
	  dvi_do_page (j);
	  at_least_one_page = 1;
	  fprintf (stderr, "]");
	}
    }
  if (!at_least_one_page && num_page_ranges) {
    fprintf (stderr, "No pages fall in range!\nFalling back to entire document.\n");
  }
  if (!at_least_one_page) /* Fall back to entire document */
    for (i=0; i<dvi_npages(); i++) {
      fprintf (stderr, "[%d", i+1);
      dvi_do_page (i);
      fprintf (stderr, "]");
    }
  dvi_close();
  fprintf (stderr, "\n");
  cleanup();
  return 0;
}