Printer Spreads

Script for Adobe InDesign

Arrange document pages into printer spreads.

  • Overrides parent pages
  • Clears pasteboard and all guides
  • Merges all layers
  • Adapt open source to customize or create other scripts
Download
Printer Spreads
Help me keep making new scripts by supporting my work. Click the PayPal button to contribute any amount you choose. Thank you. William Campbell

How to use the script

There is no interface other than alert messages that may appear if the document does not meet the following requirements:

Facing pages — the document must be facing pages.

Page count — printer spreads require the document page count is divisible by 4. If not, the user is prompted with an option to add blank pages to the end of the document.

First and last spread — both first and last spread must be a single page.

If the requirements are met, the script proceeds. The following steps are completed:

  1. Everything is unlocked including guides.
  2. All guides are removed.
  3. Items on the pasteboard are removed.
  4. Parent page items are overridden, and all parent page items are removed.
  5. Text markers 'Current Page Number' are resolved to their actual value.
  6. Layers are merged to a single layer.
  7. Margins are set to zero and rulers are reset to page origin.
  8. First and last pages are adjusted so that no elements extend beyond the spine edge.
  9. Crossover elements that span spread pages are divided onto the pages where the elements appear.
  10. Page order is rearranged to create nested saddle stitch 4-page signatures that tuck inside one another and are bound at the spine, typically with a pair of staples. The last page is paired with the first (back and front cover), and pages to follow are arranged increasing low folio pages paired with decreasing high folio pages.
  11. For each spread, elements are grouped, page size is doubled, and the group is put back to its original position.
  12. The leftover empty pages are removed.
  13. Excess parent pages are removed, and all pages are assigned the one remaining parent page.
  14. A center guide and fold marks are added to the parent page.

The result is a document half the number of single pages that are double the width of the trim size, with two pages side-by-side as a spread across each document page.

Important note when exporting PDF

The script adds center fold marks off each page in the slug area, which the script sets to 24 points. For these fold marks to appear on exported PDFs, ensure that in the PDF Export Options, section “Marks and Bleeds”, the option “Include Slug Area” is enabled. Otherwise, because the marks are beyond bleed, they do not appear.

Center fold mark
Include slug area

Source code

(download button below)

/*

Printer Spreads
Copyright 2023 William Campbell
All Rights Reserved
https://www.marspremedia.com/contact

Permission to use, copy, modify, and/or distribute this software
for any purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

*/

(function () {

    var title = "Printer Spreads";

    if (!/indesign/i.test(app.name)) {
        alert("Script for InDesign", title);
        return;
    }

    // Script variables.
    var dest;
    var doc;
    var doneMessage;
    var error;
    var from;
    var last;
    var masterPage;
    var masterPages;
    var pageH;
    var pageW;
    var spreadGroup;
    var spreadsBounds = [];
    var swatchNone;
    var working;

    // SETUP

    // Script requires open document.
    if (!app.documents.length) {
        alert("Open a document", title);
        return;
    }
    doc = app.activeDocument;
    if (!doc.saved || doc.modified) {
        alert("Save document first", title, false);
        return;
    }
    swatchNone = doc.swatches.item("None");

    // CREATE WORKING WINDOW

    working = new Window("palette", undefined, undefined, {
        closeButton: false
    });
    working.preferredSize = [300, 80];
    working.add("statictext");
    working.t = working.add("statictext");
    working.add("statictext");
    working.display = function (message) {
        this.t.text = message || "Working... Please wait...";
        this.show();
        this.update();
    };

    // EXECUTE

    doneMessage = "";
    try {
        app.doScript(process, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, title);
        doneMessage = "Done";
    } catch (e) {
        error = error || e;
        if (/User cancel/.test(error.message)) {
            doneMessage = "Canceled";
        } else {
            doneMessage = "An error has occurred.\nLine " + error.line + ": " + error.message;
        }
    }
    working.close();
    doneMessage && alert(doneMessage, title, error);

    //====================================================================
    //               END PROGRAM EXECUTION, BEGIN FUNCTIONS
    //====================================================================

    function drawLine(page, layer, entirePath) {
        // entirePath = [[x1, y1], [x2, y2]]
        var line;
        var overprintStroke;
        var strokeColor;
        var strokeWeight;
        // Stroke properties:
        strokeColor = doc.swatches.itemByName("Registration");
        strokeWeight = 0.25; // points
        overprintStroke = true;
        //
        line = page.graphicLines.add(layer, {
            endCap: EndCap.BUTT_END_CAP,
            endJoin: EndJoin.MITER_END_JOIN,
            fillColor: doc.swatches.itemByName("None"),
            leftLineEnd: ArrowHead.NONE,
            miterLimit: 4,
            rightLineEnd: ArrowHead.NONE,
            strokeAlignment: StrokeAlignment.CENTER_ALIGNMENT,
            strokeColor: strokeColor,
            strokeTint: 100,
            strokeWeight: strokeWeight, // IMPORTANT!; must follow strokeColor for non-zero.
            overprintStroke: overprintStroke // IMPORTANT!; must follow stroke color, or "not applicable in the current state".
        });
        line.paths[0].entirePath = entirePath;
    }

    function ignoreWrap(pageItems) {
        var i;
        var pageItem;
        for (i = 0; i < pageItems.length; i++) {
            pageItem = pageItems[i];
            if (pageItem.getElements()[0].constructor.name == "Group") {
                // Is a group.
                // Recurse (call self)
                ignoreWrap(pageItem.pageItems);
            } else {
                // Not a group.
                if ("textFramePreferences" in pageItem) {
                    pageItem.textFramePreferences.ignoreWrap = true;
                }
            }
        }
    }

    function mergeLayers() {
        while (doc.layers.length > 1) {
            doc.layers[doc.layers.length - 2].merge(doc.layers[doc.layers.length - 1]);
        }
        doc.layers[0].name = "Layer 1";
        doc.layers[0].visible = true;
        doc.layers[0].locked = false;
        doc.layers[0].layerColor = 1766613612;
        doc.layers[0].ignoreWrap = false;
        doc.layers[0].showGuides = true;
        doc.layers[0].lockGuides = false;
        doc.layers[0].printable = true;
    }

    function overrideParentPage(page) {
        var appliedMaster;
        var gb;
        var i;
        var pageItem1;
        var pageItem2;
        var rulerOrigin;
        // Requires ruler set to page origin.
        rulerOrigin = doc.viewPreferences.rulerOrigin;
        doc.viewPreferences.rulerOrigin = RulerOrigin.PAGE_ORIGIN;
        appliedMaster = page.appliedMaster;
        if (appliedMaster) {
            // Loop backwards to keep stacking order.
            for (i = appliedMaster.pageItems.length - 1; i > -1; i--) {
                pageItem1 = appliedMaster.pageItems[i];
                try {
                    gb = pageItem1.geometricBounds;
                    pageItem2 = pageItem1.override(page);
                    pageItem2.move([gb[1], gb[0]]);
                } catch (_) {
                    // Ignore.
                }
            }
        }
        // Restore ruler origin.
        doc.viewPreferences.rulerOrigin = rulerOrigin;
    }

    function overrideParentPages() {
        var i;
        var ii;
        var masterSpread;
        var page;
        var textFrame;
        // All parent page items ignore wrap.
        for (i = 0; i < doc.masterSpreads.length; i++) {
            masterSpread = doc.masterSpreads[i];
            for (ii = 0; ii < masterSpread.pages.length; ii++) {
                ignoreWrap(masterSpread.pages[ii].pageItems);
            }
        }
        // Override parent page items.
        for (i = 0; i < doc.masterSpreads.length; i++) {
            masterSpread = doc.masterSpreads[i];
            for (ii = 0; ii < masterSpread.pages.length; ii++) {
                overrideParentPage(masterSpread.pages[ii]);
            }
        }
        // Override page items.
        for (i = 0; i < doc.pages.length; i++) {
            overrideParentPage(doc.pages[i]);
        }
        // Resolve variable current page number.
        app.findGrepPreferences.findWhat = "~N";
        for (i = 0; i < doc.pages.length; i++) {
            page = doc.pages[i];
            app.changeGrepPreferences.changeTo = String(page.name);
            for (ii = 0; ii < page.textFrames.length; ii++) {
                textFrame = page.textFrames[ii];
                if (textFrame.contents) {
                    textFrame.changeGrep();
                }
            }
        }
        // Clear all parent page items.
        for (i = 0; i < doc.masterSpreads.length; i++) {
            masterSpread = doc.masterSpreads[i];
            for (ii = masterSpread.pageItems.length - 1; ii > -1; ii--) {
                masterSpread.pageItems[ii].remove();
            }
        }
    }

    function process() {
        var baseName;
        var fileOutput;
        var fullPath;
        var gb;
        var i;
        var ii;
        var layer;
        var nameOutput;
        var page;
        var pageItem;
        var pageItemsLength;
        var pathOutput;
        var rect;
        var spread;
        app.scriptPreferences.userInteractionLevel = UserInteractionLevels.NEVER_INTERACT;
        app.scriptPreferences.measurementUnit = MeasurementUnits.POINTS;
        working.display();
        // Set ruler origin to spread.
        doc.viewPreferences.rulerOrigin = RulerOrigin.SPREAD_ORIGIN;
        doc.zeroPoint = [0, 0];
        try {
            // Check that document meets requirements.
            // Is document facing pages?
            if (!doc.documentPreferences.facingPages) {
                alert("Script expects the document is facing pages.", title);
                throw new Error("User cancel");
            }
            // Is page count divisible by 4?
            if (doc.pages.length % 4 != 0) {
                if (!confirm("Unexpected page count.\nPrinter spreads require page count is divisible by 4. Add blank pages to end of document?", true, title)) {
                    throw new Error("User cancel");
                }
                // Add pages until page count is divisible by 4.
                while (doc.pages.length % 4 != 0) {
                    doc.pages.add(LocationOptions.AT_END);
                }
            }
            // Is first spread a single page?
            spread = doc.spreads[0];
            if (spread.pages.length > 1) {
                alert("Script expects the first spread is a single page.", title);
                throw new Error("User cancel");
            }
            // Is last spread a single page?
            spread = doc.spreads[doc.spreads.length - 1];
            if (spread.pages.length > 1) {
                alert("Script expects the last spread is a single page.", title);
                throw new Error("User cancel");
            }
            // OK TO PROCEED
            // Unlock everything.
            // 1. Guides
            doc.guidePreferences.guidesLocked = false;
            // 2. Layers
            for (i = 0; i < doc.layers.length; i++) {
                doc.layers[i].locked = false;
            }
            // 3. Master spread page items.
            for (i = 0; i < doc.masterSpreads.length; i++) {
                for (ii = 0; ii < doc.masterSpreads[i].pageItems.length; ii++) {
                    doc.masterSpreads[i].pageItems[ii].locked = false;
                }
            }
            // 4. Spread page items.
            for (i = 0; i < doc.spreads.length; i++) {
                for (ii = 0; ii < doc.spreads[i].pageItems.length; ii++) {
                    doc.spreads[i].pageItems[ii].locked = false;
                }
            }
            // Clean up document.
            removeGuides();
            removeItemsPasteboard();
            overrideParentPages();
            mergeLayers();
            // Set document properties.
            doc.documentPreferences.allowPageShuffle = true;
            // Slug all sides 24 points to make room for center marks.
            doc.documentPreferences.documentSlugUniformSize = true;
            doc.documentPreferences.slugTopOffset = 24;
            // Margins to zero, single column.
            // Document and master pages.
            doc.marginPreferences.left = 0;
            doc.marginPreferences.top = 0;
            doc.marginPreferences.right = 0;
            doc.marginPreferences.bottom = 0;
            doc.marginPreferences.columnCount = 1;
            doc.marginPreferences.columnGutter = 0;
            masterPages = doc.masterSpreads[0].pages;
            if (masterPages) {
                for (i = 0; i < masterPages.length; i++) {
                    masterPage = masterPages[i];
                    masterPage.marginPreferences.left = 0;
                    masterPage.marginPreferences.top = 0;
                    masterPage.marginPreferences.right = 0;
                    masterPage.marginPreferences.bottom = 0;
                    masterPage.marginPreferences.columnCount = 1;
                    masterPage.marginPreferences.columnGutter = 0;
                }
            }
            // Get page size.
            pageH = doc.documentPreferences.pageHeight;
            pageW = doc.documentPreferences.pageWidth;
            // First spread resolve bleed in spine.
            spread = doc.spreads[0];
            page = spread.pages[0];
            for (i = 0; i < page.pageItems.length; i++) {
                pageItem = page.pageItems[i];
                gb = pageItem.geometricBounds;
                if (gb[1] < 0) {
                    gb[1] = 0;
                    pageItem.geometricBounds = gb;
                }
            }
            // Last spread resolve bleed in spine.
            spread = doc.spreads[doc.spreads.length - 1];
            page = spread.pages[0];
            for (i = 0; i < page.pageItems.length; i++) {
                pageItem = page.pageItems[i];
                gb = pageItem.geometricBounds;
                if (gb[3] > pageW) {
                    gb[3] = pageW;
                    pageItem.geometricBounds = gb;
                }
            }
            // RESOLVE CROSSOVERS
            for (i = 1; i < doc.spreads.length - 1; i++) {
                spread = doc.spreads[i];
                pageItemsLength = spread.pageItems.length;
                for (ii = 0; ii < pageItemsLength; ii++) {
                    pageItem = spread.pageItems[ii];
                    gb = pageItem.geometricBounds;
                    // Does item begin on left page and extend to right?
                    // Test right edge + 2 to ignore items with sliver on right page.
                    if (gb[1] < pageW && gb[3] > pageW + 2) {
                        // Copy and mask item on right page.
                        rect = doc.rectangles.add(layer, LocationOptions.AFTER, pageItem);
                        rect.fillColor = swatchNone;
                        rect.strokeColor = swatchNone;
                        rect.geometricBounds = [-9, pageW, pageH + 9, pageW + pageW + 9];
                        pageItem.select();
                        app.copy();
                        rect.select();
                        app.pasteInto();
                        // Cut and mask item on left page.
                        rect = doc.rectangles.add(layer, LocationOptions.AFTER, pageItem);
                        rect.fillColor = swatchNone;
                        rect.strokeColor = swatchNone;
                        rect.geometricBounds = [-9, -9, pageH + 9, pageW];
                        pageItem.select();
                        app.cut();
                        rect.select();
                        app.pasteInto();
                    }
                }
            }
            // MAKE PRINTER SPREADS
            last = doc.pages.length - 1;
            from = last - 1;
            dest = 1;
            while (dest < from) {
                doc.pages[from].move(LocationOptions.AFTER, doc.pages[dest]);
                dest++;
                doc.pages[from].move(LocationOptions.AFTER, doc.pages[dest]);
                dest += 3;
            }
            // Label page 1 as 1, last as its number.
            // Then move last page before first.
            doc.sections.add(doc.pages[last]);
            doc.sections[1].continueNumbering = false;
            doc.sections[1].pageNumberStart = last + 1;
            doc.pages[last].move(LocationOptions.AT_BEGINNING);
            doc.sections.add(doc.pages[1]);
            doc.sections[1].continueNumbering = false;
            doc.sections[1].pageNumberStart = 1;
            // Group page items each spread (if more than 1).
            for (i = 0; i < doc.spreads.length; i++) {
                spread = doc.spreads[i];
                if (spread.pageItems.length > 1) {
                    spreadGroup = doc.groups.add(doc.spreads[i].pageItems.everyItem(), doc.layers[0]);
                    spreadsBounds[i] = spreadGroup.geometricBounds;
                } else {
                    spreadsBounds[i] = spread.pageItems[0].geometricBounds;
                }
            }
            // Double page width.
            doc.documentPreferences.pageWidth = pageW * 2;
            // Move groups/items back to original position.
            for (i = 0; i < doc.spreads.length; i++) {
                spread = doc.spreads[i];
                if (spread.groups.length) {
                    spread.groups[0].move([spreadsBounds[i][1], spreadsBounds[i][0]]);
                } else {
                    spread.pageItems[0].move([spreadsBounds[i][1], spreadsBounds[i][0]]);
                }
            }
            // Turn off facing pages.
            doc.documentPreferences.facingPages = false;
            // Delete all even pages.
            for (i = doc.pages.length - 1; i > -1; i -= 2) {
                doc.pages[i].remove();
            }
            // Renumber pages starting at 1.
            doc.sections[0].continueNumbering = false;
            doc.sections[0].pageNumberStart = 1;
            // Remove extra master page left over from facing pages.
            masterPages = doc.masterSpreads[0].pages;
            if (masterPages) {
                while (masterPages.length > 1) {
                    masterPages[0].remove();
                }
            }
            // Remove all master spreads except first.
            for (i = doc.masterSpreads.length - 1; i > 0; i--) {
                doc.masterSpreads[i].remove();
            }
            // Apply first master spread to all document pages.
            for (i = 0; i < doc.pages.length; i++) {
                doc.pages[i].appliedMaster = doc.masterSpreads[0];
            }
            // Add center guide to master.
            masterPages[0].guides.add(doc.layers[0], {
                orientation: HorizontalOrVertical.VERTICAL,
                location: pageW,
                guideType: GuideTypeOptions.RULER
            });
            // Add center fold marks to master.
            drawLine(masterPages[0], doc.layers[0],
                [
                    [pageW, pageH + 9],
                    [pageW, pageH + 24]
                ]
            );
            drawLine(masterPages[0], doc.layers[0],
                [
                    [pageW, -9],
                    [pageW, -24]
                ]);
            // SAVE COPY
            baseName = doc.fullName.name.replace(/\.[^\.]*$/, "");
            pathOutput = doc.filePath;
            nameOutput = baseName + "-printer-spreads.indd";
            fullPath = pathOutput + "/" + nameOutput;
            fileOutput = new File(fullPath);
            doc.save(fileOutput);
        } catch (e) {
            error = e;
            throw e;
        } finally {
            app.scriptPreferences.userInteractionLevel = UserInteractionLevels.INTERACT_WITH_ALL;
        }
    }

    function removeGuides() {
        var i;
        var remove = function (spread) {
            var page;
            var x;
            var xx;
            for (x = spread.guides.length - 1; x > -1; x--) {
                spread.guides[x].remove();
            }
            for (x = 0; x < spread.pages.length; x++) {
                page = spread.pages[x];
                for (xx = page.guides.length - 1; xx > -1; xx--) {
                    page.guide[xx].remove();
                }
            }
        };
        try {
            for (i = 0; i < doc.masterSpreads.length; i++) {
                remove(doc.masterSpreads[i]);
            }
            for (i = 0; i < doc.spreads.length; i++) {
                remove(doc.spreads[i]);
            }
        } catch (e) {
            error = e;
            throw e;
        }
    }

    function removeItemsPasteboard() {
        var i;
        var remove = function (spread) {
            var pageItem;
            var x;
            for (x = spread.pageItems.length - 1; x > -1; x--) {
                pageItem = spread.pageItems[x];
                if (pageItem.parentPage === null) {
                    pageItem.select();
                    pageItem.remove();
                }
            }
        };
        try {
            for (i = 0; i < doc.masterSpreads.length; i++) {
                remove(doc.masterSpreads[i]);
            }
            for (i = 0; i < doc.spreads.length; i++) {
                remove(doc.spreads[i]);
            }
        } catch (e) {
            error = e;
            throw e;
        }
    }

})();
Help me keep making new scripts by supporting my work. Click the PayPal button to contribute any amount you choose. Thank you. William Campbell
Download
Printer Spreads

For help installing scripts, see How to Install and Use Scripts in Adobe Creative Cloud Applications.

IMPORTANT: scripts are developed for the latest Adobe Creative Cloud applications. Many scripts work in CC 2018 and later, even some as far back as CS6, but may not perform as expected, or run at all, when used in versions prior to 2018. Photoshop features Select Subject and Preserve Details 2.0 definitely fail prior to CC 2018 (version 19) as the features do not exist in earlier versions. For best results use the latest versions of Adobe Creative Cloud applications.

IMPORTANT: by downloading any of the scripts on this page you agree that the software is provided without any warranty, express or implied. USE AT YOUR OWN RISK. Always make backups of important data.

IMPORTANT: fees paid for software products are the purchase of a non-exclusive license to use the software product and do not grant the purchaser any degree of ownership of the software code. Author of the intellectual property and copyright holder William Campbell retains 100% ownership of all code used in all software products regardless of the inspiration for the software product design or functionality.