001/*
002 *  ConcatPDF.java
003 *  $URL: svn://svn.webarts.bc.ca/open/trunk/projects/WebARTS/ca/bc/webarts/tools/pdf/ConcatPDF.java $
004 *  $REVISION: $
005 *  $Date: 2019-04-18 18:30:08 -0700 (Thu, 18 Apr 2019) $
006 *  Copyright (c) 2003 Tom B. Gutwin P.Eng.
007 *
008 *  This program is free software; you can redistribute it and/or
009 *  modify it under the terms of the GNU General Public License
010 *  as published by the Free Software Foundation; either version 2
011 *  of the License, or any later version.
012 *
013 *  This program is distributed in the hope that it will be useful,
014 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
015 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 *  GNU General Public License for more details.
017 *
018 *  You should have received a copy of the GNU General Public License
019 *  along with this program; if not, write to the Free Software
020 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
021 */
022package ca.bc.webarts.tools.pdf;
023
024import java.io.File;
025
026import com.itextpdf.text.*;
027import com.itextpdf.text.pdf.*;
028
029import java.io.*;
030
031
032/**
033 *  This is a simple application class that concatenates pdf files.
034 *  <br>It has 3 modes of operation.<br><ol>
035 *  <li>DEFAULT operation simply concat's files one file after the other.</li>
036 *  <li>Merge one file into another at a specified page.</li>
037 *  <li>Concat ALL files in a given directory.</li></ol>
038 *  <br><b><u>Syntax for basic concatenation (one file after the other) into
039 *  a NEW PDF file:</u>
040 *  </b><br>
041 *  <pre>java ca.bc.webarts.tools.ConcatPDF destfile file1 file2 [file3 ...]</pre>
042 *  <br><br><b><u>Syntax for 'insert into' concatenation (one into the other at the
043 *  spec'd page) into a NEW PDF file:</u></b><br>
044 *  <pre>java ca.bc.webarts.tools.ConcatPDF destfile file1 file2 insertPageNum</pre>
045 *  <br><br><b><u>Syntax for directory concatenation (all files in a dir
046 *  concat'd into one NEW file):</u></b><br>
047 *  <pre>java ca.bc.webarts.tools.ConcatPDF destfile directoryName</pre>
048 *
049 * @author    TGutwin
050 */
051public class ConcatPDF
052{
053  /**
054   * An optional page number at which to insert the the 2nd file into the first.
055   * For example if this field is 14, the 2nd PDF file will be concat'd into
056   * the first starting at page 14.<br>The resultant file will have 13 pages of
057   * the 1st PDF, all of the 2nd PDF and then the rest of the 1st PDF.
058   **/
059   private int insertPage_ = 0;
060
061   private static String eol_ = System.getProperty("line.separator");
062
063
064  /**
065   * The simple usage help message..
066   **/
067   private static final String usage_ =
068     "This is a simple application class that concatenates pdf files."+
069     eol_+
070     "It has 3 modes of operation:"+eol_+
071     "   1) DEFAULT operation simply concat's files one file after the other."+
072     eol_+
073     "   2) Merge 2nd file into 1st at a specified page."+eol_+
074     "   3) Concat ALL files in a given directory."+eol_+
075     eol_+
076     "Syntax for basic concatenation (one file after the other) into"+
077     "a NEW PDF file:"+eol_+
078     "   java ca.bc.webarts.tools.ConcatPDF destfile file1 file2 [file3 ...]"+
079     eol_+eol_+
080     "Syntax for 'insert into' concatenation (one into the other at the"+
081     "spec'd page) into a NEW PDF file:"+eol_+
082     "   java ca.bc.webarts.tools.ConcatPDF destfile insertIntoFile insertedFile insertPageNum"+
083     eol_+eol_+
084     "Syntax for directory concatenation (all files in a dir concat'd into "+
085     "one NEW file):"+eol_+
086     "   java ca.bc.webarts.tools.ConcatPDF destfile directoryName";
087
088
089  /**
090   *  A basic empty constructor for ConcatPDF class that does nothing but
091   *  instantiate the class.
092   */
093  public ConcatPDF()
094  {
095    // the worker methods will have to be called on the instantiated object.
096  }
097
098
099  /**
100   *  A constructor for ConcatPDF class that takes a array of filenames and
101   *  concatenates them all into the targetPDF file.
102   *
103   * @param  targetPdf  The resultant target PDF file after concatenation.
104   * @param  filenames  the pdf files to concat.
105   */
106  public ConcatPDF(String targetPdf, String[] filenames)
107  {
108    concatFiles(targetPdf, filenames);
109  }
110
111
112  /**
113   *  A constructor for ConcatPDF class that takes a directory  and
114   *  concatenates ALL the pdf files into the targetPDF file.
115   *
116   * @param  targetPdf  The resultant target PDF file after concatenation.
117   * @param  pdfDirectory  A directory to search for PDFs.
118   */
119  public ConcatPDF(String targetPdf, String pdfDirectory)
120  {
121    concatFilesFromDirectory(targetPdf, pdfDirectory, false);
122  }
123
124
125  /**
126   *  A constructor for ConcatPDF class that does 'insert into' concatenation
127   * (one into the other at the spec'd page) into a NEW PDF file.
128   *
129   * @param  targetPdf  The resultant target PDF file after concatenation.
130   * @param  first  The 1st PDF file for concatenation.
131   * @param  second  The 2nd PDF file for concatenation.
132   * @param  insertIntoPage  the page number to insert the 2nd PDF at..
133   */
134  public ConcatPDF(String targetPdf, String first, String second, int insertIntoPage)
135  {
136    insertPage_ = insertIntoPage;
137    insertConcat(targetPdf, first, second, insertIntoPage);
138  }
139
140
141  /**
142   *  A helper method to do the DEFAULT concating of individual files one
143   *  after the other.
144   *
145   * @param  targetPdf  The resultant target PDF file after concatenation.
146   * @param  filenames  the pdf files to concat.
147   */
148  public boolean concatFiles(String targetPdf,  String[] filenames)
149  {
150
151    boolean retVal = true;
152    // first test if all the files are available for readings
153    File tmpFile = null;
154    boolean fileFailure = false;
155    String errorMsg = "";
156    for (int i=0; !fileFailure && i<filenames.length; i++)
157    {
158      try
159      {
160        tmpFile = new File(filenames[i]);
161        if (!tmpFile.exists() || !tmpFile.canRead())
162        {
163          fileFailure = true;
164          errorMsg = "Cannot read file: "+filenames[i];
165        }
166      }
167      catch (NullPointerException npe)
168      {
169        fileFailure = true;
170        errorMsg = "File ERROR encountered for file: "+filenames[i];
171      }
172    }
173
174    // On with the concat
175    if (!fileFailure)
176    {
177      try
178      {
179        int i = 0;
180        PdfReader pdfreader = new PdfReader(filenames[i]);
181        int numPagesInCurrPdf = pdfreader.getNumberOfPages();
182        System.out.println("There are " + numPagesInCurrPdf
183             + " pages in "+filenames[i]);
184        Document document
185             = new Document(pdfreader.getPageSizeWithRotation(1));
186
187        // Open the target PDF file for writing
188        PdfWriter pdfwriter
189             = PdfWriter.getInstance(document,
190            new FileOutputStream(targetPdf));
191        document.open();
192        PdfContentByte pdfcontentbyte = pdfwriter.getDirectContent();
193
194        // loop through the files
195        while (i < filenames.length)
196        {
197          int currPageNum = 0;
198
199          // Loop through all the pages in the current file to merge in
200          while (currPageNum < numPagesInCurrPdf)
201          {
202            currPageNum++;
203            document.setPageSize
204                (pdfreader.getPageSizeWithRotation(currPageNum));
205            document.newPage();
206
207            // get the page to merge in
208            PdfImportedPage pdfimportedpage
209                 = pdfwriter.getImportedPage(pdfreader, currPageNum);
210            int currPageRotationDegrees = pdfreader.getPageRotation(currPageNum);
211
212            // merge it in
213            if (currPageRotationDegrees == 90 || currPageRotationDegrees == 270)
214            {
215              pdfcontentbyte.addTemplate(
216                  pdfimportedpage, 0.0F, -1.0F, 1.0F, 0.0F, 0.0F,
217                  pdfreader.getPageSizeWithRotation(currPageNum).getHeight());
218            }
219            else
220            {
221              pdfcontentbyte.addTemplate(
222                  pdfimportedpage, 1.0F, 0.0F, 0.0F, 1.0F, 0.0F,
223                  0.0F);
224            }
225            //System.out.println("Processed page " + currPageNum);
226          } // more pages?
227
228          // Is there more files to do? Then increment and do anaother file
229          if (++i < filenames.length)
230          {
231            pdfreader = new PdfReader(filenames[i]);
232            numPagesInCurrPdf = pdfreader.getNumberOfPages();
233            System.out.println("There are " + numPagesInCurrPdf
234             + " pages in "+filenames[i]);
235          }
236        }
237
238        // Done All files
239        document.close();
240      }
241      catch (Exception exception)
242      {
243        retVal = false;
244        System.err.println(exception.getClass().getName() + ": "
245             + exception.getMessage());
246      }
247    }
248    else
249    {
250      retVal = false;
251      System.out.println(errorMsg);
252    }
253    return retVal;
254  }
255
256
257  /**
258    * Set Method for class field 'insertPage_'.
259    *
260    * @param insertPage is the value to set this class field to.
261    *
262    **/
263  public  void setInsertPage(int insertPage)
264  {
265    this.insertPage_ = insertPage;
266  }  // setInsertPage Method
267
268
269  /**
270    * Get Method for class field 'insertPage_'.
271    *
272    * @return int - The value the class field 'insertPage_'.
273    *
274    **/
275  public int getInsertPage()
276  {
277    return insertPage_;
278  }  // getInsertPage Method
279
280
281  /** Prints the Class usage to System.out.**/
282  public static void printUsage()
283  {
284    System.out.println(usage_);
285  }
286
287
288  /**
289   *  A helper method to take a directory name, search it for PDF files and then
290   *  concatenate them into the targetPdf.
291   *
292   * @param  targetPdf  The resultant target PDF file after concatenation.
293   * @param  pdfDirectory  A directory to search for PDFs.
294   * @param  recurse  recurse into the sub-dirs (NOT IMPLEMENTED YET).
295   */
296  public boolean concatFilesFromDirectory(String targetPdf,
297                                          String pdfDirectory,
298                                          boolean recurse)
299  {
300    // this method simply reads the dir for the pdf files and then calls the
301    // concatFiles(String targetPdf,  String[] filenames) method.
302    boolean retVal = true;
303    String [] filesToConcat = null;
304    int concatFilesCount = 0;
305    File dirFile = new File(pdfDirectory);
306    if (dirFile != null && dirFile.isDirectory())
307    {
308      String srcFileNames[] = dirFile.list();
309      if (srcFileNames != null)
310      {
311        filesToConcat = new String[countPdfsInDir(pdfDirectory)];
312        for (int i = 0; i < srcFileNames.length; i++)
313        {
314          File currFile = new File(srcFileNames[i]);
315          if (currFile != null &&
316             !currFile.isDirectory() &&
317             isThisAPdfFile(currFile))
318          {
319            filesToConcat[concatFilesCount++] = currFile.getAbsolutePath();
320          }
321        }
322
323        // Now send this array off to the helper method
324        retVal = concatFiles(targetPdf,  filesToConcat);
325      }
326    }
327    else
328    {
329      retVal = false;
330      System.out.println(
331         "Commandline parameter specified is NOT a directoryname");
332    }
333
334    return retVal;
335  }
336
337
338  /**
339   *  A helper method to concats the 2nd PDF into the 1st at the spec'd page
340   * into the targetPdf.
341   *
342   * @param  targetPdf  The resultant target PDF file after concatenation.
343   * @param  firstPDF  The first PDF to concat the second into.
344   * @param  secondPDF  The PDF to insert into the first at the spec'd page.
345   * @param  insertPage  the page to insert the 2nd file at.
346   */
347  public boolean insertConcat(String targetPdf,
348                              String firstPDF,
349                              String secondPDF,
350                              int insertPage)
351  {
352    boolean retVal = true;
353    // first test if all the files are available for readings
354    File tmpFile = null;
355    boolean fileFailure = false;
356    String errorMsg = "";
357    try
358    {
359      tmpFile = new File(firstPDF);
360      if (!tmpFile.exists() || !tmpFile.canRead())
361      {
362        fileFailure = true;
363        errorMsg = "Cannot read file: "+firstPDF;
364      }
365      tmpFile = new File(secondPDF);
366      if (!tmpFile.exists() || !tmpFile.canRead())
367      {
368        fileFailure = true;
369        errorMsg = "Cannot read file: "+secondPDF;
370      }
371    }
372    catch (NullPointerException npe)
373    {
374      fileFailure = true;
375      errorMsg = "File ERROR encountered for file: " +
376                 tmpFile.getAbsolutePath();
377    }
378
379    // On with the concat
380    if (!fileFailure)
381    {
382      try
383      {
384        PdfReader firstPpdfreader = new PdfReader(firstPDF);
385        PdfReader secondPpdfreader = new PdfReader(secondPDF);
386        PdfReader pdfreader = firstPpdfreader;
387        int numPagesInFirstPdf = firstPpdfreader.getNumberOfPages();
388        int numPagesInSecondPdf = secondPpdfreader.getNumberOfPages();
389        System.out.println("There are " + numPagesInFirstPdf
390             + " pages in "+firstPDF);
391        System.out.println("There are " + numPagesInSecondPdf
392             + " pages in "+secondPDF);
393        Document document
394             = new Document(firstPpdfreader.getPageSizeWithRotation(1));
395
396        // Open the target PDF file for writing
397        PdfWriter pdfwriter
398             = PdfWriter.getInstance(document,
399            new FileOutputStream(targetPdf));
400        document.open();
401        PdfContentByte pdfcontentbyte = pdfwriter.getDirectContent();
402
403        int currPageNum1 = 1;
404        int currPageNum2 = 1;
405        int currPageNumResult = 1;
406        float currPageSizeWithRotation;
407        int currPageRotationDegrees =0;
408        PdfImportedPage pdfImportedpage = null;
409        // Loop through all the pages in the FIRST file
410        while (currPageNumResult <= numPagesInFirstPdf+numPagesInSecondPdf)
411        {
412          document.setPageSize
413              (firstPpdfreader.getPageSizeWithRotation(currPageNum1));
414          document.newPage();
415
416          // get the page to merge in
417          if (currPageNumResult < insertPage ||
418             currPageNumResult >= insertPage + numPagesInSecondPdf)
419          {
420            // then just get the first file page
421            System.out.println(currPageNumResult +"> Insert " + currPageNum1 + " from 1st PDF.");
422            pdfreader = firstPpdfreader;
423            pdfImportedpage
424                 = pdfwriter.getImportedPage(pdfreader, currPageNum1);
425            currPageRotationDegrees = pdfreader.getPageRotation(currPageNum1);
426            currPageSizeWithRotation =
427                pdfreader.getPageSizeWithRotation(currPageNum1).getHeight();
428            currPageNum1++;
429          }
430          else
431          {
432            // insert a 2nd file page
433            System.out.println(currPageNumResult +"> Insert " + currPageNum2 + " from 2nd PDF.");
434            pdfreader = secondPpdfreader;
435            pdfImportedpage
436                 = pdfwriter.getImportedPage(pdfreader, currPageNum2);
437            currPageRotationDegrees = pdfreader.getPageRotation(currPageNum2);
438            currPageSizeWithRotation =
439                pdfreader.getPageSizeWithRotation(currPageNum2).getHeight();
440
441            // increment the page counter and continue with the rest of the 1st PDF
442            currPageNum2++;
443          }
444
445          // merge it in
446          if (currPageRotationDegrees == 90 || currPageRotationDegrees == 270)
447          {
448            pdfcontentbyte.addTemplate(
449                pdfImportedpage, 0.0F, -1.0F, 1.0F, 0.0F, 0.0F,
450                currPageSizeWithRotation);
451          }
452          else
453          {
454            pdfcontentbyte.addTemplate(
455                pdfImportedpage, 1.0F, 0.0F, 0.0F, 1.0F, 0.0F,
456                0.0F);
457          }
458
459          // increment the meged PDF page counter and keep going
460          //System.out.println("   Processed page " + currPageNumResult);
461          currPageNumResult++;
462        } // more pages?
463
464
465        // Done All files
466        document.close();
467      }
468      catch (Exception exception)
469      {
470        retVal = false;
471        System.err.println(exception.getClass().getName() + ": "
472             + exception.getMessage());
473      }
474    }
475    else
476    {
477      retVal = false;
478      System.out.println(errorMsg);
479    }
480
481    return retVal;
482  }
483
484
485    /**
486    *Counts the PDFs in a given dir.
487    @return the number of PDF files in the spec'd directory
488    **/
489  public int countPdfsInDir(String pdfDirectory)
490  {
491    File dirFile = new File(pdfDirectory);
492    int filesCount = 0;
493    if (dirFile != null && dirFile.isDirectory())
494    {
495      String srcFileNames[] = dirFile.list();
496      if (srcFileNames != null)
497      {
498        for (int i = 0; i < srcFileNames.length; i++)
499        {
500          File currFile = new File(srcFileNames[i]);
501          if (currFile != null &&
502             !currFile.isDirectory() &&
503             isThisAPdfFile(currFile))
504          {
505            filesCount++;
506          }
507        }
508      }
509    }
510    return filesCount;
511  }
512
513
514  /** Checks if the passef File is a PDF file (ie has a pdf extension).
515    **/
516  protected boolean isThisAPdfFile(File fileToCheck)
517  {
518    boolean retVal = false;
519
520    try
521    {
522      String filename = fileToCheck.getAbsolutePath();
523      if (filename.trim().toLowerCase().endsWith(".pdf"))
524      {
525        retVal = true;
526      }
527    }
528    catch (Exception exception)
529    {
530      retVal = false;
531      System.err.println(exception.getClass().getName() + ": "
532           + exception.getMessage());
533    }
534
535    return retVal;
536  }
537
538
539  /**
540   *  The main program for the ConcatPDF class. <br>
541 *  <br><b><u>Syntax for basic concatenation (one file after the other) into
542 *  a NEW PDF file:</u>
543 *  </b><br>
544 *  <pre>java ca.bc.webarts.tools.ConcatPDF destfile file1 file2 [file3 ...]</pre>
545 *  <br><br><b><u>Syntax for 'insert into' concatenation (one into the other at the
546 *  spec'd page) into a NEW PDF file:</u></b><br>
547 *  <pre>java ca.bc.webarts.tools.ConcatPDF destfile file1 file2 insertPageNum</pre>
548 *  <br><br><b><u>Syntax for directory concatenation (all files in a dir
549 *  concat'd into one NEW file):</u></b><br>
550 *  <pre>java ca.bc.webarts.tools.ConcatPDF destfile directoryName</pre>
551   *
552   * @param  args  The command line arguments
553   */
554  public static void main(String[] args)
555  {
556    if (args.length < 2)
557    {
558      // WRONG
559      printUsage();
560    }
561    else if (args.length < 3)
562    {
563      // the concat dir mode
564      new ConcatPDF(args[0], args[1]);
565    }
566    else // more than 2 args were passed
567    {
568      // this can be DEFAULT concat or insert into concat mode
569      try
570      {
571        int insertPage =  Integer.parseInt(args[3]);
572        // if we get here without exception then INSERT concat mode
573        new ConcatPDF(args[0], args[1], args[2], insertPage);
574      }
575      catch (java.lang.NumberFormatException numFormatEx)
576      {
577        String [] cFilenames = new String [args.length-1];
578        for (int i = 1; i <args.length; i++)
579          cFilenames[i-1] = args[i];
580        new ConcatPDF(args[0], cFilenames);
581      }
582    }
583  }
584}
585
586