Resolve Overset Text

JavaScript for Adobe InDesign

Detects overset text, creates a text frame off-page, and links to the frame so the hidden content is revealed. Once confident all content to keep is showing, and the revealed overset text is excess, the script can then delete the unwanted content and added frames.

Overset text is visible in Story Editor, but having to open Story Editor to resolve each instance of overset text takes time. The purpose of the script is to help speed up the process.

  • Make visible all overset text in a document
  • Delete all overset text
  • Adapt open source to create other scripts
Resolve Overset Text screen
Download
Resolve Overset Text

You decide. Reward the author an
amount the solution is worth to you.

Script creates text frames on pasteboard next to pages with overset text, revealing hidden content.

How-to Video

How to use the script

The interface is a single section with two choices: Reveal overset text, and Delete revealed text. Select one and click the OK button to begin.

Reveal overset text — document stories are scanned for overset text, and when found, the ending text frame is located and linked to a new text frame created on the pasteboard next to the page. Before linking the new frame, the script inserts a paragraph end after the last character showing in the ending frame, to prevent reflow that can occur when overset text is revealed. The new frame on the pasteboard is given a magenta, dashed stroke so it stands out. In cases of multiple instances of overset text on the same page, the added frames are stacked, but each is offset over and down so they are not directly atop each other.

Before inspecting the results, enable Show Hidden Characters (Type menu), and enable Show Text Threads (View menu, Extras). Then whitespace is visible, in some cases all the overset text contains, and selecting the frame of revealed text shows the connection to the story ending frame on the page, to which the new frame is linked.

Once overset text is revealed, the user should inspect the content to determine whether the excess is a mistake and is not needed, or wanted content has moved into the overset portion. In that case, adjust the layout to get the desired content to appear in the frame on the page. Be aware of the paragraph end added after the last character showing in the ending frame, which must be removed if overset text needs to flow back into the text frame on the page.

Delete revealed text — after inspecting overset text and adjusting the layout, the excess content and text frames may be deleted if desired. This can be done manually, one-by-one, but to help, the script can remove all revealed text and frames at once using this option.

It’ s possible to make the script take both steps in one operation, but I feel it’s better to split the process into two steps, so the user can confirm the text revealed before deleting it, and hopefully avoid a costly mistake.

Source code

(download button below)

/*

Resolve Overset Text
Copyright 2021 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.

*/

//@target indesign;

(function () {

    var title = "Resolve Overset Text";

    // Script variables.
    var count;
    var doc;
    var error;

    // Reusable UI variables.
    var g; // group
    var p; // panel
    var w; // window

    // Permanent UI variables.
    var btnCancel;
    var btnOk;
    var rbDelete;
    var rbReveal;

    // SETUP

    // Works on active document only.
    // Ensure something is open.
    if (!app.documents.length) {
        alert("Open a document.", title, false);
        return;
    }

    doc = app.activeDocument;

    // CREATE USER INTERFACE

    w = new Window("dialog", title);
    w.alignChildren = "fill";

    // Panel
    p = w.add("panel");
    p.alignChildren = "left";
    g = p.add("group");
    g.orientation = "column";
    g.alignChildren = "left";
    rbReveal = g.add("radiobutton", undefined, "Reveal overset text");
    rbDelete = g.add("radiobutton", undefined, "Delete revealed text");

    // Action Buttons
    g = w.add("group");
    g.alignment = "center";
    btnOk = g.add("button", undefined, "OK");
    btnCancel = g.add("button", undefined, "Cancel");

    // DEFAULTS
    rbReveal.value = true;

    // UI ELEMENT EVENT HANDLERS

    // Action Buttons
    btnOk.onClick = function () {
        w.close(1);
    };
    btnCancel.onClick = function () {
        w.close(0);
    };

    // DISPLAY THE DIALOG
    if (w.show() == 1) {
        try {
            process();
        } catch (e) {
            if (!error) {
                error = e;
            }
            alert("An error has occurred.\nLine " + error.line + ": " + error.message, title, true);
        }
    }

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

    function process() {
        app.scriptPreferences.measurementUnit = MeasurementUnits.POINTS;
        count = 0;
        try {
            progress();
            if (rbReveal.value) {
                app.doScript(processReveal, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, "Reveal overset text");
                progress.close();
                if (count) {
                    alert(
                        "Revealed " + count + " cases of overset text.\n" +
                        "Inspect revealed text, resolve problems, then " +
                        "use 'Delete revealed text' to finish.",
                        title, false
                    );
                } else {
                    alert("No overset text found.", title, false);
                }
            }
            if (rbDelete.value) {
                app.doScript(processDelete, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, "Delete revealed text");
                progress.close();
                if (count) {
                    alert("Deleted " + count + " cases of revealed text.", title, false);
                } else {
                    alert("No revealed text found.", title, false);
                }
            }
        } finally {
            progress.close();
        }
    }

    function processDelete() {
        var cb; // content begin
        var ce; // content end
        var i;
        var textFrame;

        try {
            // Loop through all text frames.
            // Loop backwards because removing items.
            for (i = doc.textFrames.length - 1; i > -1; i--) {
                textFrame = doc.textFrames[i];
                if (textFrame.label === "oversettext") {
                    if (textFrame.contents) {
                        // Delete content from begin of frame to end of story.
                        cb = textFrame.texts[0].insertionPoints.firstItem();
                        ce = textFrame.parentStory.texts[0].insertionPoints.lastItem();
                        textFrame.parentStory.texts.itemByRange(cb, ce).remove();
                    }
                    // Delete frame.
                    textFrame.remove();
                    // Increment count.
                    count++;
                }
            }
        } catch (e) {
            // Preserve error because inside undo call.
            error = e;
            throw error;
        }
    }

    function processReveal() {
        var endFrame; // last frame of story
        var h = doc.documentPreferences.pageHeight;
        var i;
        var lastXY;
        var page;
        var reFrame; // revealing frame
        var w = doc.documentPreferences.pageWidth;
        var x;
        var y;

        var osFrames = []; // collection of overset frames
        osFrames.pushUnique = function (x) {
            for (var i = 0; i < this.length; i++) {
                if (this[i].id === x.id) {
                    return;
                }
            }
            this.push(x);
        };
        var reFrames = []; // collection of revealing frames
        reFrames.position = function (pageId) {
            // Loop backwards to match last added.
            for (var i = this.length - 1; i > -1; i--) {
                if (this[i].id === pageId) {
                    return [this[i].x, this[i].y];
                }
            }
            return null;
        };

        try {
            // Loop through all text frames.
            for (i = 0; i < doc.textFrames.length; i++) {
                if (doc.textFrames[i].parentStory.overflows) {
                    // Get last frame in thread.
                    endFrame = doc.textFrames[i].endTextFrame;
                    // Add to overset frame collection.
                    osFrames.pushUnique(endFrame);
                }
            }
            // Loop through overset frames.
            for (i = 0; i < osFrames.length; i++) {
                endFrame = osFrames[i];
                // Get frame's page.
                page = endFrame.parentPage;
                if (!page) {
                    // Ignore frames on pasteboard.
                    continue;
                }
                // Add paragraph end so text doesn't flow into new frame adding next.
                endFrame.texts[0].insertionPoints.lastItem().contents = "\r";
                // Add new frame and link end frame to it.
                reFrame = page.textFrames.add();
                endFrame.nextTextFrame = reFrame;
                // Position new frame off page.
                if (page.index === 1) {
                    // Right-hand page.
                    if (doc.viewPreferences.rulerOrigin === RulerOrigin.SPREAD_ORIGIN) {
                        // Rulers using spread origin.
                        x = w * 2 + 18;
                    } else {
                        // Rulers using page or spine origin.
                        x = w + 18;
                    }
                } else {
                    // Left-hand page.
                    if (doc.viewPreferences.rulerOrigin === RulerOrigin.SPINE_ORIGIN) {
                        // Rulers using spine origin.
                        x = -(w + w / 2) - 18;
                    } else {
                        // Rulers using page or spread origin.
                        x = -(w / 2) - 18;
                    }
                }
                y = 0;
                lastXY = reFrames.position(page.id);
                if (lastXY) {
                    // A reavealing frame next to this page has already been added.
                    // Shift new frame so not on top of last.
                    if (page.index === 1) {
                        // Right-hand page.
                        x = lastXY[0] += 24;
                    } else {
                        // Left-hand page.
                        x = lastXY[0] -= 24;
                    }
                    y = lastXY[1] += 24;
                }
                // Set frame position and size.
                reFrame.geometricBounds = [y, x, y + h / 2, x + w / 2];
                // Mark frame to identify it as holding overset text so can
                //   clear its content when calling 'Delete revealed text.'
                reFrame.label = "oversettext";
                // Stroke frame so it stands out.
                reFrame.strokeColor = "Magenta";
                reFrame.strokeWeight = 4;
                reFrame.strokeType = "Dashed";
                // Remember revealing frame.
                reFrames.push({
                    id: page.id,
                    x: x,
                    y: y
                });
                // Increment count.
                count++;
            }
        } catch (e) {
            // Preserve error because inside undo call.
            error = e;
            throw error;
        }
    }

    function progress() {
        var w = new Window("palette", "Progress", undefined, {
            closeButton: false
        });
        w.add("statictext", undefined, "Working... Please wait...");
        w.preferredSize = [450, -1];
        progress.close = function () {
            w.close();
        };
        w.show();
        w.update();
    }

})();
Download
Resolve Overset Text

License details included in download

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

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