/*
 * $Id: nup_pdf.java,v 0.2 2005/2/19
 *
 * This code is free software. It may only be copied or modified
 * if you include the following copyright notice:
 *
 * Copyright (C) 2005, Marcus May (QuoVadis). All rights reserved.
 * eMail: quovadis(at)nbsi.de
 *
 * This code 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.
 *
 */

/**
 * This class produces n-Up's from PDFs
 * @author: Marcus May (QuoVadis)
 */

// package com.lowagie.tools;

import java.io.*;

import com.lowagie.text.*;
import com.lowagie.text.pdf.*;

public class nup_pdf extends java.lang.Object {

	public static void main (String args[]) {
		if (args.length < 3) {
			System.err.println("Usage: java nup_pdf <infile> <outfile> <pages> [options]");
			System.err.println("");
			System.err.println("   infile          : input PDF file");
			System.err.println("   outfile         : output PDF file");
			System.err.println("   pages           : number of pages per sheet. pages = 2^n * x^2");
			System.err.println("");
			System.err.println("Options:");
			System.err.println("   -b              : draw bounding boxes");
			System.err.println("   -m <margin>     : outer margin (measured in points)");
			System.err.println("   -s <spacing>    : spacing between pages (measured in points)");
			System.err.println("   -k <mode>       : 0=n-Up, 1=Booklet");
			System.err.println("");
			System.err.println("(C) 2005 Marcus May (QuoVadis/NBS)");
		}
		else {
			try {

				// bounding box around each cell
				boolean bounding_box = false;
				// autorotate
				boolean autorotate = false;
				// the margin of a pages (pts)
				float margin = 0;
				// spacing between pages
				float spacing = 0;
				// print mode (normal/booklet)
				int printmode = 0;

				for (int i = 3; i < args.length; i++) {
					if (args[i].equals("-b")) {
						bounding_box = true;
					}
					if (args[i].equals("-a")) {
						autorotate = true;
					}
					if ((args[i].startsWith("-m")) && (args.length > i + 1)) {
						if (isNumeric(args[i + 1])) {
							margin = Float.parseFloat(args[i + 1]);
						} else {
							System.out.println(args[i + 1] + " is not a number! Margin ignored!");
						}
					}
					if ((args[i].startsWith("-s")) && (args.length > i + 1)) {
						if (isNumeric(args[i + 1])) {
							spacing = Float.parseFloat(args[i + 1]);
						} else {
							System.out.println(args[i + 1] + " is not a number! Spacing ignored!");
						}
					}
					if ((args[i].startsWith("-k")) && (args.length > i + 1)) {
						if (isNumeric(args[i + 1])) {
							printmode = Integer.parseInt(args[i +1]);
						} else {
							System.out.println(args[i + 1] + " is not a number! Printmode ignored!");
						}
					}
				}

				if (!isNumeric(args[2])) {
					throw new DocumentException(args[2] + " is not a number!");
				}

				int pages = Integer.parseInt(args[2]);

				int columns = 0;
				int rows = 0;

				// check if pages is a value than can be created by f(x,n)=2^n * x^2
				int u = 0;
				double v = (double) pages;
				while ((!isSqrt(v)) && ((v % 2) == 0)) {
					u++;
					v = v / 2;
				}
				if (isSqrt(v)) {
					columns = (int) (Math.pow(2, u) * Math.sqrt(v));
					rows = (int) Math.sqrt(v);
				} else {
					throw new DocumentException("The number of pages must be a result of f(x,n)=2^n * x^2 (2, 4, 8, 9, 16, 18, 25, ...)");
				}

/*
				System.out.println("Columns: " + columns + " / Rows: " + rows);
*/
				// create a reader for a certain document
				PdfReader reader = new PdfReader(args[0]);

				// retrieve the total number of pages
				int n = reader.getNumberOfPages();
				System.out.println("There are " + n + " pages in the original file.");

				// get orientation of the first page, assuming nearly all other pages have the same orientation.
				// this is to adjust the orientation of the target document according to pages
				Rectangle source_rect = reader.getPageSizeWithRotation(1);
				boolean portrait = true;
				if (source_rect.width() > source_rect.height()) {
					portrait = false;
					// exchange the value of columns & rows
					columns = columns + rows;
					rows = columns - rows;
					columns = columns - rows;
				}

				// create a document-object (our target document) with the dimensions of our source document
				Rectangle target_rect;
				if (u % 2 == 1) {
					target_rect = source_rect.rotate();
				} else {
					target_rect = source_rect;
				}
				Document document = new Document(target_rect);

				// width & height of each cell (+1 because there is always one more margin then rows/columns)
				float cellwidth = (target_rect.width() - (2 * margin) - (columns * spacing)) / columns;
                float cellheight = (target_rect.height() - (2 * margin) - (rows * spacing)) / rows;
                boolean cell_portrait = cellheight > cellwidth;

/*
                System.out.println("cellwith: " + cellwidth + " / cellheight: " + cellheight);
*/
				// create a writer that listens to the document
				PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(args[1]));
				writer.setViewerPreferences(PdfWriter.PageModeUseThumbs);

				// open the document
				document.open();

				PdfContentByte cb = writer.getDirectContent();
				PdfImportedPage page;

				int rotation;
				int p = 0;
				int i = 0;
				int pagecounter = 0;
				int pagetoprint = 0;

                float xpos = 0;
                float ypos = 0;
                float ptxpos = 0;
                float ptypos = 0;
                float factor = 0;

                boolean source_portrait;

				// we add content

				while (pagecounter < n) {
					i++;
					pagetoprint = getPageToPrint(i, n, printmode);
					if (pagetoprint != 0) {
						pagecounter++;

						xpos = p % columns;
						ypos = rows - (p / columns) - 1;

						source_rect = reader.getPageSizeWithRotation(pagetoprint);
						source_portrait = source_rect.height() > source_rect.width();

						page = writer.getImportedPage(reader, pagetoprint);
						rotation = reader.getPageRotation(pagetoprint);

						float adjustx = 0;
						float adjusty = 0;
						float stretchx = cellwidth / source_rect.width();
						float stretchy = cellheight / source_rect.height();

						if (autorotate == true && source_portrait != cell_portrait) {
							stretchx = cellwidth / source_rect.height();
							stretchy = cellheight / source_rect.width();
						}

						// calculate the stretch factor and h/v derivation to center the document in the cell
						if (autorotate == true && source_portrait != cell_portrait) {
							if (stretchx > stretchy) {
								factor = stretchy;
								adjustx = (cellwidth - (source_rect.height() * stretchy)) / 2;
							} else {
								factor = stretchx;
								adjusty = (cellheight - (source_rect.width() * stretchx)) / 2;
							}
						} else {
							if (stretchx > stretchy) {
								factor = stretchy;
								adjustx = (cellwidth - (source_rect.width() * stretchy)) / 2;
							} else {
								factor = stretchx;
								adjusty = (cellheight - (source_rect.height() * stretchx)) / 2;
							}
						}

						// adjust vertical alignment if source page is landscape
						if ((rotation == 90 || rotation == 270) ^ (autorotate == true && source_portrait != cell_portrait)) {
							if (portrait == true) {
								adjusty = adjusty + (cellheight / 2);
							} else {
								adjusty = adjusty + cellheight;
							}
						}

						ptxpos = (xpos * (cellwidth + spacing)) + margin + (spacing / 2) + adjustx;
						ptypos = (ypos * (cellheight + spacing)) + margin + (spacing / 2) + adjusty;
						if (autorotate == true) {
							if (rotation == 90 || rotation == 270) {
								if (!source_portrait && !cell_portrait) {
									ptypos = (ypos * (cellheight + spacing)) + margin + (spacing / 2) + adjusty;
								} else if (source_portrait && !cell_portrait) {
									ptypos = (ypos * (cellheight + spacing)) + margin + adjusty;
								} else if (!source_portrait && cell_portrait) {
									ptypos = (ypos * (cellheight + spacing)) + margin + (spacing / 2) + adjusty;
								} else if (source_portrait && cell_portrait) {
									ptypos = (ypos * (cellheight + spacing)) + margin + adjusty;
								}
							} else {
								if (!source_portrait && !cell_portrait) {
									ptypos = (ypos * (cellheight + spacing)) + margin + adjusty;
								} else if (source_portrait && !cell_portrait) {
									ptypos = (ypos * (cellheight + spacing)) + margin + (spacing / 2) + adjusty;
								} else if (!source_portrait && cell_portrait) {
									ptypos = (ypos * (cellheight + spacing)) + margin + adjusty;
								} else if (source_portrait && cell_portrait) {
									ptypos = (ypos * (cellheight + spacing)) + margin + (spacing / 2) + adjusty;
								}
							}
						}


						if ((rotation == 90 || rotation == 270) ^ (autorotate == true && source_portrait != cell_portrait)) {
//							only for testing purposes. proportional stretching disabled
//							cb.addTemplate(page, 0, -stretchx, stretchy, 0, ptxpos, ptypos);
							cb.addTemplate(page, 0, -factor, factor, 0, ptxpos, ptypos);
						}
						else {
//							only for testing purposes. proportional stretching disabled
//							cb.addTemplate(page, stretchx, 0, 0, stretchy, ptxpos, ptypos);
							cb.addTemplate(page, factor, 0, 0, factor, ptxpos, ptypos);
						}

						System.out.println("");
						System.out.println("X-Abweichung: " + (adjustx));
						System.out.println("Y-Abweichung: " + (adjusty));
						System.out.println("Page: " + i);
						System.out.println("Source-Width: " + source_rect.width() + " / Source-Height: " + source_rect.height());
						System.out.println("Rotation: " + reader.getPageRotation(pagetoprint));
						System.out.println("Source Portrait: " + source_portrait);
						System.out.println("Cell Portrait: " + cell_portrait);
						System.out.println("xpos: " + xpos + " / ypos: " + ypos);
						System.out.println("ptxpos: " + ptxpos + " / ptypos: " + ptypos + " / factor: " + factor);

						System.out.println("Processed page " + i);
	                }
					// draw the bounding box
					if ((p == 0) && (bounding_box == true)) {
						// draw the bounding boxes
						PdfPCell cell;
						cell = new PdfPCell();
						cell.setFixedHeight((target_rect.height() - (2 * margin)) / rows);
						PdfPTable table = new PdfPTable(columns);
			            for (int k = 0; k < (rows * columns) ; ++k) {
			                table.addCell(cell);
			            }
			            table.setTotalWidth(target_rect.width() - (2 * margin));
			            table.writeSelectedRows(0, -1, margin, target_rect.height() - margin, cb);
					}
					p++;
					if (p == pages) {
						// create a new page
						p = 0;
						document.newPage();
					}
				}
				// we close the document
				document.close();
			} catch(Exception e) {
				System.err.println(e.getClass().getName() + ": " + e.getMessage());
			}
		}
    }

	static int getPageToPrint(int n, int totalPages, int printMode) {
		switch (printMode) {
		  	case 1:
		  		return autoDuplexBooklet(n, totalPages);
		  	default:
		    	return n;
		}
  	}

	static int autoDuplexBooklet(int n, int totalPages) {
		int m = (int) Math.ceil((float) totalPages / 4) * 4;
		int tupel = ((n -1) / 2) + 1;
		//System.out.println("n4: " + n + " / m: " + m + " / tupel: " + tupel + " / tupel % 2: " + tupel % 2 + " / n % 2: " + n % 2);
		int r;
		if ((tupel % 2 + n % 2) == 1)  {
			r = Math.min(tupel, m + 1 - tupel);
		} else {
			r = Math.max(tupel, m + 1 - tupel);
		}
		if (r <= totalPages) {
			return r;
		} else {
			return 0;
		}
  	}

	static Rectangle getPageSizeFromString(String psize) {
		if (psize == "LETTER") {
			return PageSize.LETTER;
		} else if (psize == "NOTE") {
			return PageSize.NOTE;
		} else if (psize == "LEGAL") {
			return PageSize.LEGAL;
		} else if (psize == "A0") {
			return PageSize.A0;
		} else if (psize == "A1") {
			return PageSize.A1;
		} else if (psize == "A2") {
			return PageSize.A2;
		} else if (psize == "A3") {
			return PageSize.A3;
		} else if (psize == "A4") {
			return PageSize.A4;
		} else if (psize == "A5") {
			return PageSize.A5;
		} else if (psize == "A6") {
			return PageSize.A6;
		} else if (psize == "A7") {
			return PageSize.A7;
		} else if (psize == "A8") {
			return PageSize.A8;
		} else if (psize == "A9") {
			return PageSize.A9;
		} else if (psize == "A10") {
			return PageSize.A10;
		} else if (psize == "B0") {
			return PageSize.B0;
		} else if (psize == "B1") {
			return PageSize.B1;
		} else if (psize == "B2") {
			return PageSize.B2;
		} else if (psize == "B3") {
			return PageSize.B3;
		} else if (psize == "B4") {
			return PageSize.B4;
		} else if (psize == "B5") {
			return PageSize.B5;
		} else if (psize == "ARCH_E") {
			return PageSize.ARCH_E;
		} else if (psize == "ARCH_D") {
			return PageSize.ARCH_D;
		} else if (psize == "ARCH_C") {
			return PageSize.ARCH_C;
		} else if (psize == "ARCH_B") {
			return PageSize.ARCH_B;
		} else if (psize == "ARCH_A") {
			return PageSize.ARCH_A;
		} else if (psize == "FLSA") {
			return PageSize.FLSA;
		} else if (psize == "FLSE") {
			return PageSize.FLSE;
		} else if (psize == "HALFLETTER") {
			return PageSize.HALFLETTER;
		} else if (psize == "_11X17") {
			return PageSize._11X17;
		} else if (psize == "LEDGER") {
			return PageSize.LEDGER;
		} else {
			return PageSize.A4;
		}
  	}

	static int manualDuplexBooklet(int n, int totalPages) {
		return n;
  	}

	static boolean isSqrt(double x) {
		double s = Math.sqrt(x);
		if (s == Math.floor(s)) {
			return true;
		} else {
			return false;
		}
  	}

	static boolean isInteger(double x) {
		if (x == Math.floor(x)) {
			return true;
		} else {
			return false;
		}
  	}

  	static boolean isNumeric(String strIn) {
  		try {
  			float test = Float.parseFloat(strIn);
  			return true;
  		} catch(Exception e) {
  			return false;
  		}
  	}
}