// 2-up saddle-stitch pdf imposition signature utility

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <error.h>
#include <errno.h>

#ifndef FALSE
#define FALSE (0!=0)
#define TRUE (0==0)
#endif

static int DEBUG = FALSE;

/*
Table 1: Paper Size
     w bp h bp w in  h in  w mm h mm
A0b  2594 3370 36.03 46.81 915  1189
A0   2380 3368 33.06 46.78 840  1188
A1   1684 2380 23.39 33.06 594   840
A2   1190 1684 16.53 23.39 420   594
A3   842  1190 11.69 16.53 297   420
A4   595  842   8.26 11.69 210   297
A5   421  595   5.85  8.26 149   210
A6   298  421   4.14  5.85 105   149
A7   211  298   2.93  4.14  74   105
A8   149  211   2.07  2.93  53    74
A9   106  149   1.47  2.07  37    53
A10   75  106   1.04  1.47  26    37
 */

#define A0b          0   //  "{36.03in,46.81in}"
#define A0           1   //  "{33.06in,46.78in}"
#define A1           2   //  "{23.39in,33.06in}"
#define A2           3   //  "{16.53in,23.39in}"
#define A3           4   //  "{11.69in,16.53in}"
#define TABLOID      5   //  "{11in,17in}"
#define LEGAL        6   //  "{11in,14in}"
#define LETTER       7   //  "{8.5in,11in}"
#define A4           8   //  "{8.26in,11.6in}"
#define A5           9   //  "{5.85in,8.26in}"
#define HALF_LETTER 10   //  "{5.5,4.25in}"
#define A6          11   //  "{4.14in,5.85in}"
#define A7          12   //  "{2.93in,4.14in}"
#define A8          13   //  "{2.07in,2.93in}"
#define A9          14   //  "{1.47in,2.07in}"
#define A10         15   //  "{1.04in,1.47in}"
#define SMALLEST A10

int two_up_size[] = {
  -1,                // "A0b",
  -1,                // "A0",
  A0,                // "A1",
  A1,                // "A2",
  A2,                // "A3",
  A2,                // "TABLOID",
  A2,                // "LEGAL",
  TABLOID,           // "LETTER",
  A3,                // "A4",
  A4,                // "A5",
  LETTER,            // "HALF_LETTER",
  A5,                // "A6",
  A6,                // "A7",
  A7,                // "A8",
  A8,                // "A9",
  A9,                // "A10",
};

char *papername[] = {
  "a0b",
  "a0",
  "a1",
  "a2",
  "a3",
  "tabloid",
  "legal",
  "letter",
  "a4",
  "a5",
  "halfletter",
  "a6",
  "a7",
  "a8",
  "a9",
  "a10",
};

char *papersize[] = {
  "{36.03in,46.81in}", // a0b
  "{33.06in,46.78in}", // a0
  "{23.39in,33.06in}", // a1
  "{16.53in,23.39in}", // a2
  "{11.69in,16.53in}", // a3
  "{11in,17in}",       // tabloid
  "{11in,14in}",       // legal
  "{8.5in,11in}",      // letter
  "{8.26in,11.6in}",   // a4
  "{5.85in,8.26in}",   // a5
  "{5.5in,4.25in}",      // half-letter
  "{4.14in,5.85in}",   // a6
  "{2.93in,4.14in}",   // a7
  "{2.07in,2.93in}",   // a8
  "{1.47in,2.07in}",   // a9
  "{1.04in,1.47in}",   // a10
  NULL
};

float papersizef[] = {
  36.03,46.81, // a0b
  33.06,46.78, // a0
  23.39,33.06, // a1
  16.53,23.39, // a2
  11.69,16.53, // a3
  11.0,17.0,   // tabloid
  11.0,14.0,   // legal
  8.5,11.0,    // letter
  8.26,11.6,   // a4
  5.85,8.26,   // a5
  5.5,4.25,    // half-letter
  4.14,5.85,   // a6
  2.93,4.14,   // a7
  2.07,2.93,   // a8
  1.47,2.07,   // a9
  1.04,1.47,   // a10
};

int main(int argc, char **argv) {
  int sheet = 1;
  int i, rc;
  static char pdfseparate[1024];
  static char pdfinfo[1024];
  static char twoup[1024];
  static char landscape[1024];
  static char portrait[1024];
  int skip_cover = FALSE;
  int pages = 0, rotation, paper = -1;
  int base_page;
  FILE *info;
  float width, height;
  char *line = NULL;
  size_t len = 0;
  ssize_t nread;

  while (argc >= 2) {
    int p = SMALLEST;
    char pp[64];
    int argpaper = -1;
    while (p >= 0) {
      sprintf(pp, "--%s", papername[p]);
      if (strcasecmp(argv[1], pp)==0) {
        argpaper = p;
        break;
      }
      p -= 1;
    }
    if (argpaper >= 0) {
      argv += 1; argc -= 1; paper = argpaper; // allow for more than one explicit paper assignment. Keep last.
      continue;
    }
    if (strcasecmp(argv[1], "--nocover")==0) {
      argv += 1; argc -= 1; skip_cover = TRUE; continue;
    } else if ((strcasecmp(argv[1], "--debug")==0) || (strcasecmp(argv[1], "-d")==0)) {
      argv += 1; argc -= 1; DEBUG = TRUE; continue;
    } else if ((argc >= 2) && ((strcasecmp(argv[1], "--11x17")==0) || (strcasecmp(argv[1], "--ledger")==0))) {
      argv += 1; argc -= 1; paper = TABLOID; continue;
    } else if (*argv[1] == '-') {
      fprintf(stderr, "syntax: layout [--nocover] file.pdf\n");
      exit(1);
    }
    break; // remainder should be filename only
  }
  
  if (argc != 2) {
    fprintf(stderr, "syntax: layout [--nocover] file.pdf\n");
    exit(1);
  }

  sprintf(pdfinfo, "pdfinfo %s", argv[1]);
  info = popen(pdfinfo, "r");
  if (info == NULL) {
    fprintf(stderr, "layout: %s\n", strerror(errno));
    exit(3);
  }

  while ((nread = getline(&line, &len, info)) != -1) {

    rc = sscanf(line, "Pages:          %d\n", &pages);
    if (rc == 1) {
      if (skip_cover) {
        fprintf(stderr, "Document contains a cover (not to be included) and %d pages\n", pages-1);
      } else {
        fprintf(stderr, "Document contains %d pages\n", pages);
      }
    }

    rc = sscanf(line, "Page size:      %f x %f pts\n", &width, &height);
    if (rc == 2) {
      fprintf(stderr, "Page size is %f by %f pts\n", width, height);
      if (paper < 0) {
        paper = SMALLEST;

        // pdfinfo TG.pdf|grep "^Page size:"
        // Page size:      610.56 x 773.52 pts
        #define ConvertToInches (36.03/2594)
        
        while (paper >= 0) {
#ifdef NEVER
          fprintf(stderr, "Comparing %f x %f pts (%f x %f in) to %f x %f in (%s)\n", width, height,
                  width*ConvertToInches, height*ConvertToInches,
                  papersizef[paper*2], papersizef[paper*2+1],
                  papername[paper]);
#endif
          if ((width*ConvertToInches <= papersizef[paper*2]) && (height*ConvertToInches <= papersizef[paper*2+1])) {
            fprintf(stderr, "Assuming input paper option is --%s", papername[paper]);
            if (paper > 0) fprintf(stderr, ". Can be overridden, e.g. with --%s", papername[paper-1]);
            fprintf(stderr, "\n");
            fprintf(stderr, "Output paper size will be %s %s\n", papername[two_up_size[paper]], papersize[two_up_size[paper]]);
            break;
          }
          paper -= 1;
        }
      }
      if (paper < 0) {
        paper = TABLOID; // my default
        fprintf(stderr, "Unknown large paper size - using --%s\n", papername[paper]);
      }
    }

    rc = sscanf(line, "Page rot:       %d\n", &rotation);
    if (rc == 1) {
      if (rotation != 0) fprintf(stderr, "WARNING: "); // don't know if this is handled at all... untested...
      fprintf(stderr, "Rotation is %d\n", rotation);
    }
  }
  free(line);
  pclose(info);

  if (paper < 0) {
    fprintf(stderr, "layout: cannot determine input page size\?\n");
    exit(4);
  }
  
  if (two_up_size[paper] < 0) {
    fprintf(stderr, "layout: no output paper large enough\?\n");
    exit(4);
  }
  
  if (pages == 0) {
    fprintf(stderr, "layout: no pages to arrange\?\n");
    exit(4);
  }
  
  system("rm -f foliotmp[0-9]*.pdf");
  if (skip_cover) {
    sprintf(pdfseparate, "pdfseparate -f 2 %s foliotmp%%d.pdf", argv[1]); // pages numbered from 1 upwards.  so cover sheet is page 1
    pages -= 1; base_page = 2;
  } else {
    sprintf(pdfseparate, "pdfseparate %s foliotmp%%d.pdf", argv[1]);
    base_page = 1;
  }
  if (DEBUG) fprintf(stderr, "%s\n", pdfseparate);
  rc = system(pdfseparate);
  if (WIFSIGNALED(rc) && (WTERMSIG(rc) == SIGINT || WTERMSIG(rc) == SIGQUIT)) exit(rc);

  if (rc != 0) {
    if (errno != 0) fprintf(stderr, "layout: %s\n", strerror(errno));
    exit(2);
  }

  // convert each pdf page to an image,
  // use imagemagik (convert) to threshold it to remove grey background (feature removed for now)
  // repackage back into same-sized pages
  //   pdftoppm foliotmp69.pdf |pnmtopng -hist |convert png:- -sharpen 0x3 -white-threshold '90%' foliotmp69-new.pdf
  
  printf("     two-sided printing\n");
  printf(" lower side     upper side\n");
  system("rm -f twoup[0-9]*.pdf");
  for (i = 0; i < pages/2; i += 2) {
    printf("+-----+-----+  +-----+-----+\n");
    printf("|     |     |  |     |     |\n");
    printf("| %02d  |  %02d |  | %02d  | %02d  |\n",  pages+1-(i+2)+1, i+0+1, i+1+1, pages+1-(i+3)+1);
    printf("|     |     |  |     |     |\n");
    printf("|     |     |  |     |     |\n");
    printf("+-----+-----+  +-----+-----+  sheet %d\n\n", sheet);
    sprintf(twoup, "/usr/bin/pdfjam --nup '2x1' --landscape --papersize '%s'"
            " --noautoscale 'true' --outfile twoup%04d-a.pdf -- foliotmp%0d.pdf - foliotmp%0d.pdf -%s",
            papersize[two_up_size[paper]], sheet, pages+1-(i+2)+(skip_cover ? 2 : 1), i+0+(skip_cover ? 2 : 1), DEBUG ? "" : " > /dev/null 2> /dev/null");
    if (DEBUG) fprintf(stderr, "%s\n", twoup);
    system(twoup);

    sprintf(twoup, "/usr/bin/pdfjam --nup '2x1' --landscape --papersize '%s'"
            " --noautoscale 'true' --outfile twoup%04d-b.pdf -- foliotmp%0d.pdf - foliotmp%0d.pdf -%s",
            papersize[two_up_size[paper]], sheet, i+1+(skip_cover ? 2 : 1), pages+1-(i+3)+(skip_cover ? 2 : 1), DEBUG ? "" : " > /dev/null 2> /dev/null");
    if (DEBUG) fprintf(stderr, "%s\n", twoup);
    system(twoup);

    sheet += 1;
  }
  if (DEBUG) {
    system("pdfjam twoup*.pdf");
  } else {
    system("pdfjam twoup*.pdf > /dev/null 2> /dev/null");
  }
  sprintf(landscape, "mv twoup%04d-b-pdfjam.pdf landscape-tmp.pdf", sheet-1);
  system(landscape);
  if (DEBUG) {
    system("cpdf -rotate 90 -i landscape-tmp.pdf -o portrait-tmp.pdf");
  } else {
    system("cpdf -rotate 90 -i landscape-tmp.pdf -o portrait-tmp.pdf > /dev/null 2> /dev/null");
  }
  // --landscape below?
  sprintf(twoup, "/usr/bin/pdfjam --papersize '%s' --twoside --scale 2.2 --outfile print-%s  portrait-tmp.pdf%s",
          papersize[two_up_size[paper]], argv[1], DEBUG ? "" : " > /dev/null 2> /dev/null");
  system(twoup);
  fprintf(stderr, "Output is in print-%s\n", argv[1]);
  if (!DEBUG) system("rm -f foliotmp[0-9]*.pdf twoup[0-9]*.pdf landscape-tmp.pdf portrait-tmp.pdf");
  if (pages&3) {
    fprintf(stderr, "Warning: not a multiple of 4 pages.  Need to tweak the code to add padding...\n");
    exit(5);
  }
  
  exit(0);
}