com.innoq.iQpdfutil.Main.java Source code

Java tutorial

Introduction

Here is the source code for com.innoq.iQpdfutil.Main.java

Source

/**
 * iQpdfutil -- concat pdfs and add page numbers
 * <p>
 * Copyright (C) 2016 innoQ
 * <p>
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * <p>
 * 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 Affero General Public License for more details.
 * <p>
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.innoq.iQpdfutil;

import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * This class reads in all pds, concatenates them to one,
 * adds pagenumbers to all but the first pages, and stores
 * the resulting pdf as the output file.
 *
 * Requires at least 2 arguments: The last argument specifies
 * the output file while all others provide the input files
 * in the order of concatenation.
 *
 * @author ghadir
 * @since 08/12/15 12:30
 */
public class Main {

    public static void main(String[] args) throws Exception {

        try {
            Args arguments = new Args(args);

            System.out.println(arguments);

            try (TempFile tempFile = TempFile.createTempFileIn("./")) {
                concatFiles(arguments.inputFiles, tempFile.tempFile);
                copyPDFandAddPageNumbers(tempFile.tempFile, arguments.outputFile);
            }
        } catch (IllegalArgumentException e) {
            System.err.println(e.getMessage());
        }
    }

    /**
     *
     * */
    private static void copyPDFandAddPageNumbers(File pdfFile, String outputFilename)
            throws IOException, DocumentException {
        try (OutputStream os = new FileOutputStream(outputFilename)) {
            numberPages(new PdfReader(pdfFile.getName()), os);
        }
    }

    /**
     * This method takes the filenames of multiple pdf files,
     * from which their contents are concatenated and stored
     * into a new pdf file with the given output file.
     *
     * @param inputFilenames of pdf files to be concatenated
     * @param outputFile that will be created/overwritten
     *
     * @throws IOException
     * @throws DocumentException
     * */
    private static void concatFiles(List<String> inputFilenames, File outputFile)
            throws IOException, DocumentException {
        try (OutputStream os = new FileOutputStream(outputFile)) {
            try (Readers readers = new Readers(inputFilenames)) {
                concatPDFs(readers, os);
            }
        }
    }

    /**
     * This method produces a new pdf that is written to the output stream.
     *
     * <p>
     *     The newly created pdf contains all the pages of all the provided
     *     input pdf files in the right order. Where necessary empty pages
     *     ensure that the pages of every input pdf begin on an odd page.
     * </p>
     * */
    public static void concatPDFs(Readers readers, OutputStream os) throws DocumentException, IOException {
        Document document = new Document();

        PdfCopy copy = new PdfCopy(document, os);
        document.open();

        for (PdfReader reader : readers) {
            copyPages(reader, copy);
        }

        document.close();
    }

    /**
     * copyPages copies all pages from the input reader into the pdfcopy.
     * <p>
     *     If necessary -- that is if the input pdf contains an odd number
     *     of pages -- this method appends an empty page.
     * </p>
     * */
    private static boolean copyPages(PdfReader reader, PdfCopy copy) throws IOException, DocumentException {
        int nrOfPagesInCurrentFile = reader.getNumberOfPages();
        for (int page = 0; page < nrOfPagesInCurrentFile;) {
            copy.addPage(copy.getImportedPage(reader, ++page));
        }

        final boolean isEmptyPageRequired = nrOfPagesInCurrentFile % 2 == 1;
        if (isEmptyPageRequired) {
            copy.addPage(reader.getPageSize(nrOfPagesInCurrentFile), 0);
        }

        return isEmptyPageRequired;
    }

    /**
     * This method adds a page number to all pages (except the first one)
     * from the given input pdf and writes the modified pdf to
     * the output-stream.
     *
     * <p>
     * The page number is placed in the center at the bottom of the page.
     * </p>
     *
     * <pre>
     *  +-----+
     *  |     |
     *  |     |
     *  |     |
     *  | -2- |
     *  +-----+
     * </pre>
     * */
    private static void numberPages(PdfReader reader, OutputStream os) throws IOException, DocumentException {

        PdfStamper stamper = new PdfStamper(reader, os);

        try {
            int n = reader.getNumberOfPages();

            ColumnText text;
            PdfContentByte contents;
            Paragraph paragraph;
            Font headerFont = new Font(Font.FontFamily.COURIER, 12, Font.NORMAL);
            for (int i = 2; i <= n; i++) {
                contents = stamper.getOverContent(i);
                text = new ColumnText(contents);
                text.setSimpleColumn(1, 10, PageSize.A4.getWidth() - 1, 30, 1, Element.ALIGN_CENTER);
                paragraph = new Paragraph(String.format("- %d -", i), headerFont);
                paragraph.setAlignment(Element.ALIGN_CENTER);
                text.addElement(paragraph);
                text.go();
            }
        } finally {
            try {
                stamper.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private static class Readers implements AutoCloseable, Iterable<PdfReader> {

        final List<PdfReader> inputFiles;

        public Readers(List<String> filenames) {

            this.inputFiles = filenames.stream().map(this::createPdfReader).collect(Collectors.toList());
        }

        @Override
        public void close() {
            inputFiles.forEach(PdfReader::close);
        }

        public PdfReader createPdfReader(String filename) {
            try {
                return new PdfReader(filename);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public Iterator<PdfReader> iterator() {
            return inputFiles.iterator();
        }
    }

    private static class Args {

        final List<String> inputFiles;
        final String outputFile;

        public Args(String[] args) {

            if (args.length < 2) {
                throw new IllegalArgumentException("Required arguments: inputFile+ outputFile");
            }

            this.inputFiles = Arrays.asList(args).subList(0, args.length - 1);
            this.outputFile = args[args.length - 1];

            Optional<String> missingFiles = inputFiles.stream().map(File::new)
                    .filter(f -> !(f.exists() && f.isFile())).map(File::getName).reduce((a, b) -> a + ", " + b);

            if (missingFiles.isPresent()) {
                throw new IllegalArgumentException("These files must exist: " + missingFiles.get());
            }
        }

        @Override
        public String toString() {
            return "Args{" + "inputFiles=" + inputFiles + ", outputFile='" + outputFile + '\'' + '}';
        }
    }

    /**
     * TempFile is AutoCloseable
     * When closed, the file is deleted automatically
     *
     * @since 2016-04-14
     * */
    private static class TempFile implements AutoCloseable {

        File tempFile;

        public static TempFile createTempFileIn(String dirname) throws IOException {
            return new TempFile(File.createTempFile("temp___", ".pdf", new File(dirname)));
        }

        public TempFile(File tempFile) {
            this.tempFile = tempFile;
        }

        @Override
        public void close() throws Exception {
            Files.delete(tempFile.toPath());
            ;
        }
    }
}