/*  
    
    This is DVIPDFMx, an eXtended version of DVIPDFM by Mark A. Wicks.

    Copyright (C) 2009-2013 by Jin-Hwan Cho, Matthias Franz, and Shunsaku Hirata,
    the dvipdfmx project team.
    
    Copyright (C) 1998, 1999 by Mark A. Wicks <mwicks@kettering.edu>

    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.
*/

#if HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <ctype.h>

#include "system.h"
#include "mem.h"

#include "dpxconf.h"
#include "dpxfile.h"
#include "dpxutil.h"

#include "dvi.h"

#include "pdflimits.h"
#include "pdfdoc.h"
#include "pdfdev.h"
#include "pdfparse.h"
#include "pdfencrypt.h"

#include "spc_tpic.h"
#include "specials.h"

#include "mpost.h"

#include "fontmap.h"
#include "pdffont.h"
#include "pdfximage.h"
#include "cid.h"

#include "dvipdfmx.h"
#include "xbb.h"

#include "error.h"

int compat_mode = 0;     /* 0 = dvipdfmx, 1 = dvipdfm */

static int verbose = 0;

static int mp_mode = 0;

static long opt_flags = 0;

#define OPT_TPIC_TRANSPARENT_FILL (1 << 1)
#define OPT_CIDFONT_FIXEDPITCH    (1 << 2)
#define OPT_FONTMAP_FIRST_MATCH   (1 << 3)
#define OPT_PDFDOC_NO_DEST_REMOVE (1 << 4)

static char   ignore_colors = 0;
static double annot_grow    = 0.0;
static int    bookmark_open = 0;
static double mag           = 1.0;
static int    font_dpi      = 600;
static int    really_quiet  = 0;
/*
 * Precision is essentially limited to 0.01pt.
 * See, dev_set_string() in pdfdev.c.
 */
static int pdfdecimaldigits = 2;
// PUTeX
static int ascent = 0, descent = 0;

/* Image cache life in hours */
/*  0 means erase all old images and leave new images */
/* -1 means erase all old images and also erase new images */
/* -2 means ignore image cache (default) */
static int image_cache_life = -2;

/* Encryption */
static int do_encryption    = 0;
static unsigned key_bits    = 40;
static unsigned permission  = 0x003C;

/* Page device */
static double paper_width  = 595.0;
static double paper_height = 842.0;
static double x_offset = 72.0;
static double y_offset = 72.0;
static char   landscape_mode    = 0;


char *dvi_filename = NULL, *pdf_filename = NULL;

static void
read_config_file (const char *config);

#ifdef WIN32
#define STRN_CMP strncasecmp
#else
#define STRN_CMP strncmp
#endif

static void
set_default_pdf_filename(void)
{
  const char *dvi_base;

  dvi_base = xbasename(dvi_filename);
  if (mp_mode &&
      strlen(dvi_base) > 4 &&
      !STRN_CMP(".mps", dvi_base + strlen(dvi_base) - 4, 4)) {
    pdf_filename = NEW(strlen(dvi_base)+1, char);
    strncpy(pdf_filename, dvi_base, strlen(dvi_base) - 4);
    pdf_filename[strlen(dvi_base)-4] = '\0';
  } else if (strlen(dvi_base) > 4 &&
//             !STRN_CMP(".dvi", dvi_base+strlen(dvi_base)-4, 4)) {
// PUTeX
             !STRN_CMP(".cdi", dvi_base+strlen(dvi_base)-4, 4)) {
    pdf_filename = NEW(strlen(dvi_base)+1, char);
    strncpy(pdf_filename, dvi_base, strlen(dvi_base)-4);
    pdf_filename[strlen(dvi_base)-4] = '\0';
  } else {
    pdf_filename = NEW(strlen(dvi_base)+5, char);
    strcpy(pdf_filename, dvi_base);
  }

  strcat (pdf_filename, ".pdf");
}

static void
show_version (void)
{
  fprintf(stdout, "\nThis is %s-%s by the DVIPDFMx project team,\n", PACKAGE, VERSION);
  fprintf(stdout, "modified for TeX Live,\n");
  fprintf(stdout, "an extended version of dvipdfm-0.13.2c developed by Mark A. Wicks.\n");
  fprintf(stdout, "\nCopyright (C) 2002-2013 by the DVIPDFMx project team\n");
  fprintf(stdout, "\nThis is free software; you can redistribute it and/or modify\n");
  fprintf(stdout, "it under the terms of the GNU General Public License as published by\n");
  fprintf(stdout, "the Free Software Foundation; either version 2 of the License, or\n");
  fprintf(stdout, "(at your option) any later version.\n");
}

static void
show_usage (void)
{
  fprintf(stdout, "\nUsage: dvipdfmx [options] dvifile\n");
  fprintf(stdout, "-c \t\tIgnore color specials (for B&W printing)\n");
  fprintf(stdout, "-d number\tSet PDF decimal digits (0-5) [2]\n");
  fprintf(stdout, "-f filename\tSet font map file name [cid-x.map]\n");
  fprintf(stdout, "-g dimension\tAnnotation \"grow\" amount [0.0in]\n");
  fprintf(stdout, "-l \t\tLandscape mode\n");
  fprintf(stdout, "-m number\tSet additional magnification\n");
  fprintf(stdout, "-o filename\tSet output file name [dvifile.pdf]\n");
  fprintf(stdout, "-p papersize\tSet papersize [a4]\n");
  fprintf(stdout, "-q \t\tBe quiet\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 \t\tEmbed thumbnail images of PNG format [dvifile.1] \n");
  fprintf(stdout, "-x dimension\tSet horizontal offset [1.0in]\n");
  fprintf(stdout, "-y dimension\tSet vertical offset [1.0in]\n");
  fprintf(stdout, "-z number  \tSet zlib compression level (0-9) [9]\n");

  fprintf(stdout, "-v \t\tBe verbose\n");
  fprintf(stdout, "-vv\t\tBe more verbose\n");
  fprintf(stdout, "-C number\tSpecify miscellaneous option flags [0]:\n");
  fprintf(stdout, "\t\t  0x0001 reserved\n");
  fprintf(stdout, "\t\t  0x0002 Use semi-transparent filling for tpic shading command,\n");
  fprintf(stdout, "\t\t\t instead of opaque gray color. (requires PDF 1.4)\n");
  fprintf(stdout, "\t\t  0x0004 Treat all CIDFont as fixed-pitch font.\n");
  fprintf(stdout, "\t\t  0x0008 Do not replace duplicate fontmap entries.\n");
  fprintf(stdout, "\t\t  0x0010 Do not optimize PDF destinations.\n");
  fprintf(stdout, "\t\tPositive values are always ORed with previously given flags.\n");
  fprintf(stdout, "\t\tAnd negative values replace old values.\n");
  fprintf(stdout, "-D template\tPS->PDF conversion command line template [none]\n");
  fprintf(stdout, "-E \t\tEnable DVIPDFM emulation mode\n");
  fprintf(stdout, "-I number\tImage cache life in hours [-2]\n");
  fprintf(stdout, "         \t 0: erase all old images and leave new images\n");
  fprintf(stdout, "         \t-1: erase all old images and also erase new images\n");
  fprintf(stdout, "         \t-2: ignore image cache\n");
  fprintf(stdout, "-K number\tEncryption key length [40]\n");
  fprintf(stdout, "-O number\tSet maximum depth of open bookmark items [0]\n");
  fprintf(stdout, "-P number\tSet permission flags for PDF encryption [0x003C]\n");
  fprintf(stdout, "-S \t\tEnable PDF encryption\n");
  fprintf(stdout, "-V number\tSet PDF minor version [4]\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");
  fprintf(stdout, "Papersize is specified by paper format (e.g., \"a4\") or by w<unit>,h<unit> (e.g., \"20cm,30cm\").\n");
}

static void
usage (void)
{
  if (really_quiet)
    return;
  fprintf(stdout, "\nTry \"dvipdfmx --help\" for more information.\n");
  exit(1);
}


static int
read_length (double *vp, const char **pp, const char *endptr)
{
  char   *q;
  const char *p = *pp;
  double  v, u = 1.0;
  const char *_ukeys[] = {
#define K_UNIT__PT  0
#define K_UNIT__IN  1
#define K_UNIT__CM  2
#define K_UNIT__MM  3
#define K_UNIT__BP  4
    "pt", "in", "cm", "mm", "bp",
     NULL
  };
  int     k, error = 0;

  q = parse_float_decimal(&p, endptr);
  if (!q) {
    *vp = 0.0; *pp = p;
    return  -1;
  }

  v = atof(q);
  RELEASE(q);

  q = parse_c_ident(&p, endptr);
  if (q) {
    if (strlen(q) > strlen("true") &&
        !memcmp(q, "true", strlen("true"))) {
      q += strlen("true"); /* just skip "true" */
    }
    for (k = 0; _ukeys[k] && strcmp(_ukeys[k], q); k++);
    switch (k) {
    case K_UNIT__PT: u *= 72.0 / 72.27; break;
    case K_UNIT__IN: u *= 72.0; break;
    case K_UNIT__CM: u *= 72.0 / 2.54 ; break;
    case K_UNIT__MM: u *= 72.0 / 25.4 ; break;
    case K_UNIT__BP: u *= 1.0 ; break;
    default:
      WARN("Unknown unit of measure: %s", q);
      error = -1;
      break;
    }
    RELEASE(q);
  }

  *vp = v * u; *pp = p;
  return  error;
}

static void
select_paper (const char *paperspec)
{
  const struct paper *pi;
  int   error = 0;

  pi = paperinfo(paperspec);
  if (pi && papername(pi)) {
    paper_width  = paperpswidth (pi);
    paper_height = paperpsheight(pi);
  } else {
    const char  *p = paperspec, *endptr, *comma;
    comma  = strchr(p, ',');
    endptr = p + strlen(p);
    if (!comma)
      ERROR("Unrecognized paper format: %s", paperspec);
    error = read_length(&paper_width,  &p, comma);
    p = comma + 1;
    error = read_length(&paper_height, &p, endptr);
  }
  if (error || paper_width <= 0.0 || paper_height <= 0.0)
    ERROR("Invalid paper size: %s (%.2fx%.2f)", paperspec, paper_width, paper_height);
}

struct page_range 
{
  long first, last;
} *page_ranges = NULL;

int num_page_ranges = 0;
int max_page_ranges = 0;

static void
select_pages (const char *pagespec)
{
  char  *q;
  const char *p = pagespec;

  while (*p != '\0') {
    /* 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);
    }

    page_ranges[num_page_ranges].first = 0;
    page_ranges[num_page_ranges].last  = 0;

    for ( ; *p && isspace(*p); p++);
    q = parse_unsigned(&p, p + strlen(p)); /* Can't be signed. */
    if (q) { /* '-' is allowed here */
      page_ranges[num_page_ranges].first = atoi(q) - 1;
      page_ranges[num_page_ranges].last  = page_ranges[num_page_ranges].first;
      RELEASE(q);
    }
    for ( ; *p && isspace(*p); p++);

    if (*p == '-') {
      for (++p; *p && isspace(*p); p++);
      page_ranges[num_page_ranges].last = -1;
      if (*p) {
        q = parse_unsigned(&p, p + strlen(p));
        if (q) {
          page_ranges[num_page_ranges].last = atoi(q) - 1;
          RELEASE(q);
        }
        for ( ; *p && isspace(*p); p++);
      }
    } else {
      page_ranges[num_page_ranges].last = page_ranges[num_page_ranges].first;
    }

    num_page_ranges++;

    if (*p == ',')
      p++;
    else  {
      for ( ; *p && isspace(*p); p++);
      if (*p)
        ERROR("Bad page range specification: %s", p);
    }
  }
  return;
}

#define POP_ARG() {argv += 1; argc -= 1;}
/* It doesn't work as expected (due to dvi filename). */
#define CHECK_ARG(n,m) if (argc < (n) + 1) {\
  if (!really_quiet)\
    fprintf(stderr, "\nMissing %s after \"-%c\".\n", (m), *flag);\
  usage();\
}

static void
set_verbose (int argc, char *argv[])
{
  while (argc > 0) {
    if(*argv[0] == '-') {
      char *flag;

      for (flag = argv[0] + 1; *flag != 0; flag++) {
        if (*flag == '-')
          break;
        if (*flag == 'q')
          really_quiet = 1;
        if (*flag == 'v')
          verbose++;
      }
    }
    POP_ARG();
  }

  if (!really_quiet) {
    int i;

    for (i = 0; i < verbose; i++) {
      dvi_set_verbose();
      pdf_dev_set_verbose();
      pdf_doc_set_verbose();
      pdf_enc_set_verbose();
      pdf_obj_set_verbose();
      pdf_fontmap_set_verbose();
      dpx_file_set_verbose();
    }
  }
}

// PUTeX
#define PDFUNIT(x) ((double) ROUND((1000.0*(x)/(head->unitsPerEm)), 1))
void
get_ascent_descent(char *name)
{
  char    *fullname;
  sfnt    *sfont;
  unsigned long   offset = 0;
  int font_index = 0;  /* Fix me!! */
  struct tt_head_table *head;
  struct tt_os2__table *os2;

  if ((fullname = kpse_find_file(name, kpse_truetype_format, 1)) == NULL) {
  	fprintf(stderr, "\nCan't locate TTF font file: %s\n", name);
  	exit(1);
  }
  if ((sfont = sfnt_open(fullname)) == NULL) {
  	fprintf(stderr, "\nCan't open TTF font file: %s\n", name);
  	exit(1);
  }
  switch (sfont->type) {
  case SFNT_TYPE_TTC:
    offset = ttc_read_offset(sfont, font_index);
    break;
  case SFNT_TYPE_TRUETYPE:
    if (font_index > 0) {
  	  fprintf(stderr, "\nCan't locate TTF font file: %s\n", name);
  	  exit(1);
    } else
      offset = 0;
    break;
  default:
    sfnt_close(sfont);
  	fprintf(stderr, "\nUnknown TrueType format: %s\n", name);
  	exit(1);
  }
  if (sfnt_read_table_directory(sfont, offset) < 0)
    ERROR("Reading TrueType table directory failed.");
  
  os2  = tt_read_os2__table(sfont);
  head = tt_read_head_table(sfont);
  ascent = PDFUNIT(os2->sTypoAscender);
  descent = PDFUNIT(os2->sTypoDescender);
}


static void
do_args (int argc, char *argv[])
{
  while (argc > 0 && *argv[0] == '-') {
    char *flag, *nextptr;
    const char *nnextptr;

    for (flag = argv[0] + 1; *flag != 0; flag++) {
      switch (*flag) {
      case '-':
        if (++flag) {
          if (!strcmp(flag, "help")) {
            show_version();
            show_usage();
            exit(0);
          } else if (!strcmp(flag, "version")) {
            show_version();
            exit(0);
          }
        }
	if (!really_quiet)
	  fprintf(stderr, "Unknown option in \"--%s\"", flag);
        usage();
        break;
      case 'D':
        CHECK_ARG(1, "PS->PDF conversion command line template");
        set_distiller_template(argv[1]);
        POP_ARG();
        break;
      case 'r':
        CHECK_ARG(1, "bitmap font dpi");
        font_dpi = atoi(argv[1]);
        if (font_dpi <= 0)
          ERROR("Invalid bitmap font dpi specified: %s", argv[1]);
        POP_ARG();
        break;
      case 'm':
        CHECK_ARG(1, "magnification value");
        mag = strtod(argv[1], &nextptr);
        if (mag < 0.0 || nextptr == argv[1])
          ERROR("Invalid magnification specifiied: %s", argv[1]);
        POP_ARG();
        break;
      case 'g':
        CHECK_ARG(1, "annotation \"grow\" amount");
        nnextptr = nextptr = argv[1];
        read_length(&annot_grow, &nnextptr, nextptr + strlen(nextptr));
        POP_ARG();
        break;
      case 'x':
        CHECK_ARG(1, "horizontal offset value");
        nnextptr = nextptr = argv[1];
        read_length(&x_offset, &nnextptr, nextptr + strlen(nextptr));
        POP_ARG();
        break;
      case 'y':
        CHECK_ARG(1, "vertical offset value");
        nnextptr = nextptr = argv[1];
        read_length(&y_offset, &nnextptr, nextptr + strlen(nextptr));
        POP_ARG();
        break;
      case 'o':
        CHECK_ARG(1, "output file name");
        pdf_filename = NEW (strlen(argv[1])+1,char);
        strcpy(pdf_filename, argv[1]);
        POP_ARG();
        break;
      case 's':
        CHECK_ARG(1, "page selection specification");
        select_pages(argv[1]);
        POP_ARG();
        break;
      case 't':
        pdf_doc_enable_manual_thumbnails();
        break;
      case 'p':
        CHECK_ARG(1, "paper format/size");
        select_paper(argv[1]);
        POP_ARG();
        break;
      case 'c':
        ignore_colors = 1;
        break;
      case 'l':
        landscape_mode = 1;
        break;
      case 'f':
        CHECK_ARG(1, "fontmap file name");
        if (opt_flags & OPT_FONTMAP_FIRST_MATCH)
          pdf_load_fontmap_file(argv[1], FONTMAP_RMODE_APPEND);
        else
          pdf_load_fontmap_file(argv[1], FONTMAP_RMODE_REPLACE);
        POP_ARG();
        break;
      case 'q': case 'v':
        break;
      case 'V':
      {
        int ver_minor;

        if (isdigit(*(flag+1))) {
          flag++;
          ver_minor = atoi(flag);
        } else {
          CHECK_ARG(1, "PDF minor version number");
          ver_minor = atoi(argv[1]);
          POP_ARG();
        }
        if (ver_minor < PDF_VERSION_MIN) {
          WARN("PDF version 1.%d not supported. Using PDF 1.%d instead.",
	       ver_minor, PDF_VERSION_MIN);
	  ver_minor = PDF_VERSION_MIN;
        } else if (ver_minor > PDF_VERSION_MAX) {
          WARN("PDF version 1.%d not supported. Using PDF 1.%d instead.",
	       ver_minor, PDF_VERSION_MAX);
	  ver_minor = PDF_VERSION_MAX;
        }
	pdf_set_version((unsigned) ver_minor);
      }
      break;
      case 'z':
      {
        int level;

        if (isdigit(*(flag+1))) {
          flag++;
          level = atoi(flag);
        } else {
          CHECK_ARG(1, "compression level");
          level = atoi(argv[1]);
          POP_ARG();
        }
        pdf_set_compression(level);
      }
      break;
      case 'd':
	if (compat_mode) {
	  WARN("dvipdfm \"-d\" option not supported.");
	  break;
	}
        if (isdigit(*(flag+1))) {
          flag++;
          pdfdecimaldigits = atoi(flag);
        } else {
          CHECK_ARG(1, "number of fractional digits");
          pdfdecimaldigits = atoi(argv[1]);
          POP_ARG();
        }
        break;
      case 'I':
        CHECK_ARG(1, "image cache life in hours");
        image_cache_life = atoi(argv[1]);
        POP_ARG();
        break;
      case 'S':
        do_encryption = 1;
        break;
      case 'K': 
        CHECK_ARG(1, "encryption key length");
        key_bits = (unsigned) atoi(argv[1]);
        if (key_bits < 40 || key_bits > 128 || (key_bits & 0x7))
          ERROR("Invalid encryption key length specified: %s", argv[1]);
        POP_ARG();
        break;
      case 'P': 
        CHECK_ARG(1, "encryption permission flag");
        permission = (unsigned) strtoul(argv[1], &nextptr, 0);
        if (nextptr == argv[1])
          ERROR("Invalid encryption permission flag: %s", argv[1]);
        POP_ARG();
        break;
      case 'O':
        /* Bookmark open level */
        CHECK_ARG(1, "bookmark open level");
        bookmark_open = atoi(argv[1]);
        POP_ARG();
        break;
      case 'M':
        mp_mode = 1;
        break;
      case 'C':
        CHECK_ARG(1, "a number");
        {
          long flags;

          flags = (unsigned) strtol(argv[1], &nextptr, 0);
          if (nextptr == argv[1])
            ERROR("Invalid flag: %s", argv[1]);
          if (flags < 0)
            opt_flags  = -flags;
          else
            opt_flags |=  flags;
        }
        POP_ARG();
        break;
      case 'E':
        compat_mode = 1;
        break;
      case 'i':
        CHECK_ARG(1, "subsidiary config file");
        read_config_file(argv[1]);
        POP_ARG();
        break;
      case 'e':
	if (compat_mode) {
	  WARN("dvipdfm \"-e\" option not supported.");
	  break;
	} /* else fall through */
      default:
	if (!really_quiet)
	  fprintf(stderr, "Unknown option in \"-%s\"", flag);
        usage();
        break;
      }
    }
    POP_ARG();
  }

  if (argc > 1) {
    if (!really_quiet)
        // PUTeX
      fprintf(stderr, "Multiple cdi filenames?");
    usage();
  } else if (argc > 0) {
    /*
     * The only legitimate way to have argc == 0 here is
     * do_args was called from config file.  In that case, there is
     * no dvi file name.  Check for that case .
     */
    if (!mp_mode && STRN_CMP(".cdi", argv[0] + strlen(argv[0]) - 4, 4)) {
      dvi_filename = NEW(strlen(argv[0]) + 5, char);
      strcpy(dvi_filename, argv[0]);
      strcat(dvi_filename, ".cdi");
    } else {
      dvi_filename = NEW(strlen(argv[0]) + 1, char);
      strcpy(dvi_filename, argv[0]);
    }
  }
}

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

static void
read_config_file (const char *config)
{
  const char *start, *end;
  char *option;
  FILE *fp;

  fp = DPXFOPEN(config, DPX_RES_TYPE_TEXT);
  if (!fp) {
    WARN("Could not open config file \"%s\".", config);
    return;
  }
  while ((start = mfgets (work_buffer, WORK_BUFFER_SIZE, fp)) != NULL) {
    char *argv[2];
    int   argc;

    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]);
    }
  }
  if (fp)
    MFCLOSE(fp);
}

static void
system_default (void)
{
  if (systempapername() != NULL) {
    select_paper(systempapername());
  } else if (defaultpapername() != NULL) {
    select_paper(defaultpapername());
  }
}

void
error_cleanup (void)
{
  pdf_close_images();  /* delete temporary files */
  pdf_error_cleanup();
  if (pdf_filename) {
    remove(pdf_filename);
    if (!really_quiet)
      fprintf(stderr, "\nOutput file removed.\n");
  }
}

#define SWAP(v1,v2) do {\
   double _tmp = (v1);\
   (v1) = (v2);\
   (v2) = _tmp;\
 } while (0)

static void
do_dvi_pages (void)
{
  long     page_no, page_count, i, step;
  double   page_width, page_height;
  pdf_rect mediabox;

  spc_exec_at_begin_document();

  if (num_page_ranges == 0) {
    if (!page_ranges) {
      page_ranges = NEW(1, struct page_range);
      max_page_ranges = 1;
    }
    page_ranges[0].first = 0;
    page_ranges[0].last  = -1; /* last page */
    num_page_ranges = 1;
  }

  page_width  = paper_width;
  page_height = paper_height;
  page_count  = 0;

  mediabox.llx = 0.0;
  mediabox.lly = 0.0;
  mediabox.urx = paper_width;
  mediabox.ury = paper_height;

  pdf_doc_set_mediabox(0, &mediabox); /* Root node */

  for (i = 0; i < num_page_ranges; i++) {
    if (page_ranges[i].last < 0)
      page_ranges[i].last += dvi_npages();

    step    = (page_ranges[i].first <= page_ranges[i].last) ? 1 : -1;
    page_no = page_ranges[i].first;
    for (;;) {
      if (page_no < dvi_npages()) {
        double w, h, xo, yo;
        char   lm;

        MESG("[%d", page_no+1);
        /* Users want to change page size even after page is started! */
        w = page_width; h = page_height; lm = landscape_mode;
        xo = x_offset; yo = y_offset;
        dvi_scan_specials(page_no, &w, &h, &xo, &yo, &lm, NULL, NULL, NULL, NULL, NULL, NULL);
        if (lm != landscape_mode) {
          SWAP(w, h);
          landscape_mode = lm;
        }
        if (page_width  != w || page_height != h) {
          page_width  = w;
          page_height = h;
        }
        if (x_offset != xo || y_offset != yo) {
          x_offset = xo;
          y_offset = yo;
        }
        if (page_width  != paper_width ||
            page_height != paper_height) {
          mediabox.llx = 0.0;
          mediabox.lly = 0.0;
          mediabox.urx = page_width;
          mediabox.ury = page_height;
          pdf_doc_set_mediabox(page_count+1, &mediabox);
        }
        dvi_do_page(page_no,
                    page_width, page_height, x_offset, y_offset);
        page_count++;
        MESG("]");
      }

      if (step > 0 &&
          page_no >= page_ranges[i].last)
        break;
      else if (step < 0 &&
               page_no <= page_ranges[i].last)
        break;
      else {
        page_no += step;
      }
    }
  }

  if (page_count < 1) {
    ERROR("No pages fall in range!");
  }

  spc_exec_at_end_document();
}

static void
do_mps_pages (void)
{
  FILE  *fp;

  /* _FIXME_ */
  fp = MFOPEN(dvi_filename, FOPEN_RBIN_MODE);
  if (fp) {
    mps_do_page(fp);
    MFCLOSE(fp);
  } else {
    int   i, page_no, step, page_count = 0;
    char *filename;
    /* Process filename.1, filename.2,... */
    filename = NEW(strlen(dvi_filename) + 16 + 1, char);
    for (i = 0; i < num_page_ranges; i++) {
      if (page_ranges[i].last < 0)
        ERROR("Invalid page number for MPS input: -1");

      step    = (page_ranges[i].first <= page_ranges[i].last) ? 1 : -1;
      page_no = page_ranges[i].first;
      for (;;) {
        sprintf(filename, "%s.%d", dvi_filename, page_no + 1);
        fp = MFOPEN(filename, FOPEN_RBIN_MODE);
        if (fp) {
          MESG("[%d<%s>", page_no + 1, filename);
          mps_do_page(fp);
          page_count++;
          MESG("]");
          MFCLOSE(fp);
        }
        if (step > 0 &&
            page_no >= page_ranges[i].last)
          break;
        else if (step < 0 &&
                 page_no <= page_ranges[i].last)
          break;
        else {
          page_no += step;
        }
      }
    }
    RELEASE(filename);
    if (page_count == 0)
      ERROR("No page output for \"%s\".", dvi_filename);
  }
}


/* TODO: MetaPost mode */
#if defined(MIKTEX)
#  define main Main
#endif
int CDECL
main (int argc, char *argv[]) 
{
  double dvi2pts;

  {
    const char *base = xbasename(argv[0]);

    if (STRN_CMP(base, "dvipdfmx", 8) != 0 &&
        STRN_CMP(base, "cdi2pdf",7) != 0 &&
        (STRN_CMP(base, "dvipdfm",7) == 0 || STRN_CMP(base, "ebb", 3) == 0))
      compat_mode = 1;

    if (STRN_CMP(base, "extractbb", 9) == 0 ||
        STRN_CMP(base, "xbb", 3) == 0 ||
        STRN_CMP(base, "ebb", 3) == 0)
      return extractbb(argc, argv);
  }

  if (argc < 2) {
    if (!really_quiet)
      fprintf(stderr, "No cdi filename specified.");
    usage();
    return 1;
  }

#ifdef MIKTEX
  miktex_initialize();
#else
  kpse_set_program_name(argv[0], PACKAGE);
#endif

  paperinit();
  system_default();

  argv+=1;
  argc-=1;

  set_verbose(argc, argv);
  /* quiet mode cannot be set in config file */
  if (really_quiet)
    shut_up();

  pdf_init_fontmaps(); /* This must come before parsing options... */

  read_config_file(DPX_CONFIG_FILE);

  do_args (argc, argv);

#ifndef MIKTEX
  kpse_init_prog("", font_dpi, NULL, NULL);
  kpse_set_program_enabled(kpse_pk_format, true, kpse_src_texmf_cnf);
#endif
  pdf_font_set_dpi(font_dpi);
  dpx_delete_old_cache(image_cache_life);

  if (!dvi_filename) {
    WARN("No cdi filename specified.");
    usage();
    return 1;
  }

  /* Check for ".cdi" at end of argument name */
  if (pdf_filename == NULL)
    set_default_pdf_filename();

  MESG("%s -> %s\n", dvi_filename, pdf_filename);

  pdf_enc_compute_id_string(dvi_filename, pdf_filename);
  if (do_encryption) {
    if (key_bits > 40 && pdf_get_version() < 4)
      ERROR("Chosen key length requires at least PDF 1.4. "
	    "Use \"-V 4\" to change.");
    pdf_enc_set_passwd(key_bits, permission, NULL, NULL);
  }

  if (mp_mode) {
    x_offset = 0.0;
    y_offset = 0.0;
    dvi2pts  = 0.01; /* dvi2pts controls accuracy. */
  } else {
    unsigned ver_minor = 0;
    char owner_pw[MAX_PWD_LEN], user_pw[MAX_PWD_LEN];
    /* Dependency between DVI and PDF side is rather complicated... */
    dvi2pts = dvi_init(dvi_filename, mag);
    if (dvi2pts == 0.0)
      ERROR("dvi_init() failed!");

    pdf_doc_set_creator(dvi_comment());

    if (do_encryption)
      /* command line takes precedence */
      dvi_scan_specials(0, &paper_width, &paper_height, &x_offset, &y_offset, &landscape_mode,
			&ver_minor, NULL, NULL, NULL, NULL, NULL);
    else {
      dvi_scan_specials(0, &paper_width, &paper_height, &x_offset, &y_offset, &landscape_mode,
			&ver_minor, &do_encryption, &key_bits, &permission, owner_pw, user_pw);
      if (do_encryption) {
	if (key_bits < 40 || key_bits > 128 || (key_bits & 0x7))
	  ERROR("Invalid encryption key length specified: %u", key_bits);
	else if (key_bits > 40 && pdf_get_version() < 4)
	  ERROR("Chosen key length requires at least PDF 1.4. "
		"Use \"-V 4\" to change.");
	do_encryption = 1;
	pdf_enc_set_passwd(key_bits, permission, owner_pw, user_pw);
      }
    }
    if (ver_minor >= PDF_VERSION_MIN && ver_minor <= PDF_VERSION_MAX) {
      pdf_set_version(ver_minor);
    }
    if (landscape_mode) {
      SWAP(paper_width, paper_height);
    }
  }

  pdf_files_init();

  /* Set default paper size here so that all page's can inherite it.
   * annot_grow:    Margin of annotation.
   * bookmark_open: Miximal depth of open bookmarks.
   */
  pdf_open_document(pdf_filename, do_encryption,
                    paper_width, paper_height, annot_grow, bookmark_open,
		    !(opt_flags & OPT_PDFDOC_NO_DEST_REMOVE));

  /* Ignore_colors placed here since
   * they are considered as device's capacity.
   */
  pdf_init_device(dvi2pts, pdfdecimaldigits, ignore_colors);

  if (opt_flags & OPT_CIDFONT_FIXEDPITCH)
    CIDFont_set_flags(CIDFONT_FORCE_FIXEDPITCH);

  /* Please move this to spc_init_specials(). */
  if (opt_flags & OPT_TPIC_TRANSPARENT_FILL)
    tpic_set_fill_mode(1);

  if (mp_mode) {
    do_mps_pages();
  } else {
    do_dvi_pages();
  }

  pdf_files_close();

  /* Order of close... */
  pdf_close_device  ();
  /* pdf_close_document flushes XObject (image) and other resources. */
  pdf_close_document();

  pdf_close_fontmaps(); /* pdf_font may depend on fontmap. */

  if (!mp_mode)
    dvi_close();

  MESG("\n");
  cleanup();

  paperdone();
#ifdef MIKTEX
  miktex_uninitialize ();
#endif

  return 0;
}
