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 } );