Code:

Page Duplication with Adobe Acrobat and JavaScript

This is a script that I wrote a few years ago for my previous job. I worked in the printing industry, and we were making the transition from a popular imposition tool to one coded by an in-house development team. During the change, we lost a feature that was rather important to those of us running production: the ability to rapidly duplicate pages within a PDF file. The new software did not yet support it. This functionality is useful when creating tear-off pads and carbonless forms, among other things, and – needless to say – the disappearance of this feature did not mean that the jobs which required it vanished, too.

My solution was to automate the manual process that we had to use until something better came along: extract a range of pages to a temporary file and then reinsert them into the main PDF file as many times as required. For pads of 100 pages, this could get quite tedious.

I was able to put together the script below and install it on our computers about a month before we finally got the feature from our in-house development team. I still use the script at my present job, and it is my thought that perhaps it will be useful to other professionals in the printing industry who find themselves without the ability to quickly duplicate pages within a document, or who would rather do so from within the PDF document (our developers insisted on making the tool external to Acrobat). That this can be done at all is a testament to the wisdom of including a scripting language within your project. To my knowledge, Adobe has not yet implemented a page duplication feature as part of Acrobat – but we are still using Adobe CS4 at work, and I only have CS3 at home, so...who knows?

Director's Commentary on the Script

You will need to install this script in C:\Program Files\Adobe\Acrobat {Your Version}\JavaScripts and then close and reopen Acrobat before you will be able to use the script. You can call the file repeatpages.js or something similar.

The script installs itself as a menu item in the Document menu, about halfway down. As of CS4, it appears very near Rotate Page, but the ordering of Acrobat's menu items varies across versions – and even updates. You can tweak the value in the second-to-last line of the script to get it positioned where you want it.

The bulk of the script has to do with defining the dialogue box that allows the user to specify which pages they want to duplicate, and where they want those pages to be inserted. Once the user has made these decisions, the script extracts the pertinent pages into a temporary file, then simply runs a loop to reinsert those pages at the designated position in the file. The main document is not saved after the insertion, so if you discover you've made a miscalculation about where or how many pages you want duplicated, you can simply revert to the saved version and try again.

The code is fairly well-documented, so you should be able to figure out what each step does. There are a lot of error checks and validations because I wrote the script so that anyone – even the less technically-minded of our team members – could use it, if necessary, without bringing about the Apocalypse. There is still a way to cause a minor Armageddon, however: to my knowledge, Acrobat does not provide a way to intelligently enable the script only when a document is open, which means that it is possible to select Repeat Pages even when no document is open – with undefined results. At worst, it will crash Acrobat.

Otherwise, the script does what it was intended to do, and it does a lot faster than we could do it on our own. Hopefully, it will be useful to you, too. If nothing else, it provides a useful example of how to extend Acrobat with JavaScript.

The Script

/** Module: Repeat Pages
    Repeats one or more pages in the specified PDF file.

    Copyright (C) 2006, 2013 Michael T. Malicoat.  All rights reserved.

    This work is licensed under the Creative Commons Attribution-ShareAlike 3.0
    Unported License. To view a copy of this license, visit
    http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to
        Creative Commons
        444 Castro Street
        Suite 900
        Mountain View, California, 94041
        USA.

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


// This function is called by the menu item
function repeatPages()  {
  // The starting page number
  var start = 0;
  // The ending page number
  var finish = 0;
  // The number of repeitions
  var repeat = 0;
  // Used to determine where to place repeated pages within the main document
  var insertAfter = this.numPages - 1;
  // Used to load the current page into the dialog when it opens (user-friendly)
  var currentPage = new String(this.pageNum + 1);

  var rptDialog = {
    // Initializes the dialog
    initialize: function(dialog) {
      dialog.load({
        "rpFm": currentPage,
        "rpTo": currentPage,
        "rpCt": "1",
        "rend": true
      });
      dialog.enable({
        "insp": false
      });
    },
    // Validates field values
    validate: function(dialog) {
      // Retrieve the values stored in the dialog
      var values = dialog.store();
      // If there is a problem with the page range specified, indicate an error
      if ((parseInt(values["rpFm"]) < 1) ||
        (parseInt(values["rpFm"]) > parseInt(values["pgct"])) ||
        (parseInt(values["rpTo"]) > parseInt(values["pgct"]))) {
          app.alert("You must specify a page range that falls within the boundaries of the document (i.e., values from 1 to " + values["pgct"] + ").");
          return false;
      }
      // If the starting page number is greater than the ending page number,
      // indicate an error
      if (parseInt(values["rpFm"]) > parseInt(values["rpTo"])) {
        app.alert("The starting page number must be lower than the ending page number.");
        return false;
      }
      // If no repeat count is specified, indicate an error
      if (values["rpCt"] == "" || parseInt(values["rpCt"]) == 0) {
        app.alert("You must specify at least one repetition.");
        return false;
      }
      if (values["rins"]) {
        // If we are to insert after a particular page and no page is specified,
        // indicate an error
        if (values["insp"] == "") {
          app.alert("You must specify the page number after which repeated pages will be placed.");
          return false;
        }
        // If we are to insert after a particular page and a nonexistent page is
        // specified, indicate an error
        if ((parseInt(values["insp"]) < 1) ||
          (parseInt(values["insp"]) > parseInt(values["pgct"]))) {
          app.alert("In order to properly place repeated pages, you must specify a page number that falls within the boundaries of the document (i.e., a value from 1 to" + values["pgct"] + ").");
          return false;
        }
      }
      return true;
    },
    // Does the dirty work
    commit: function(dialog) {
      // Retrieve the values stored in the dialog
      var values = dialog.store();
      start = parseInt(values["rpFm"]);
      finish = parseInt(values["rpTo"]);
      repeat = parseInt(values["rpCt"]);
      // Determine the page number after which we will insert repeated pages
      if (values["rtop"]) insertAfter = -1;
      else if (values["rins"]) insertAfter = parseInt(values["insp"]) - 1;
    },
    // Handle changes to the "rloc" option buttons
    rtop: function(dialog) {
      // Disable the "insp" field
      dialog.enable({
        "insp": false
      });
    },
    rend: function(dialog) {
      // Disable the "insp" field
      dialog.enable({
        "insp": false
      });
    },
    rins: function(dialog) {
      // Enable the "insp" field
      dialog.enable({
        "insp": true
      });
    },
    // Dialog descriptor
    description: {
      name: "Repeat Pages",
      align_children: "align_fill",
      elements: [
        {
          type: "view",
          align_children: "align_left",
          elements: [
            {
              type: "view",
              align_children: "align_row",
              elements: [
                {
                  type: "static_text",
                  name: "From:",
                  font: "default"
                },
                {
                  type: "edit_text",
                  item_id: "rpFm",
                  char_width: 4,
                  height: 20
                },
                {
                  type: "static_text",
                  name: "To:",
                  font: "default"
                },
                {
                  type: "edit_text",
                  item_id: "rpTo",
                  char_width: 4,
                  height: 20
                },
                {
                  type: "static_text",
                  name: "of"
                },
                {
                  type: "static_text",
                  char_width: 10,
                  item_id: "pgct",
                  name: this.numPages.toString()
                }
              ]
            },
            {
              type: "view",
              align_children: "align_row",
              elements: [
                {
                  type: "static_text",
                  name: "Number of repetitions:",
                  font: "default"
                },
                {
                  type: "edit_text",
                  SpinEdit: "true",
                  item_id: "rpCt",
                  char_width: 4,
                  height: 20
                }
              ]
            },
            {
              type: "cluster",
              align_children: "align_left",
              name: "Insert repeated pages...",
              elements: [
                {
                  type: "radio",
                  item_id: "rtop",
                  group_id: "rloc",
                  name: "...at the &beginning of the document"
                },
                {
                  type: "radio",
                  item_id: "rend",
                  group_id: "rloc",
                  name: "...at the &end of the document"
                },
                {
                  type: "view",
                  align_children: "align_row",
                  elements: [
                    {
                      type: "radio",
                      item_id: "rins",
                      group_id: "rloc",
                      name: "...&after page:"
                    },
                    {
                      type: "edit_text",
                      item_id: "insp",
                      char_width: 4,
                      height: 20
                    }
                  ]
                }
              ]
            },
            {
              type: "ok_cancel",
              alignment: "align_right"
            }
          ]
        }
      ]
    }
  };
  app.beginPriv();
  rc = app.execDialog(rptDialog);
  if (rc == "ok") {
    // Stash the current document
    var currentDoc = this;
    // Create a temporary document to hold the pages we will repeat
    var tempDoc = app.newDoc();
    // Copy the specified pages into the temporary file
    if (start != finish) {
      tempDoc.insertPages( {
        cPath: currentDoc.path,
        nStart: start - 1,
        nEnd: finish - 1
      });
    }
    else {
      tempDoc.insertPages( {
        cPath: currentDoc.path,
        nStart: start - 1
      });
    }
    // Delete the first (blank) page from the temporary document
    tempDoc.deletePages( {
      nStart: 0
    });
    // Copy all of the pages from the temporary file back into this file
    for (var i=0; i < repeat; i++) {
      currentDoc.insertPages( {
        cPath: tempDoc.path,
        nPage: insertAfter
      });
    }
    // Close the temporary document
    tempDoc.closeDoc(true);
    app.endPriv();
  }
}

// Now Acrobat will trust us enough to allow us to add our menu item
app.trustedFunction(repeatPages);

// Add our menu item
app.addMenuItem( {
  cName: "repeatPagery",
  cUser: "Repe&at Pages...",
  cParent: "Document",
  cExec: "repeatPages()",
  nPos: 11
} );