Skip to main content
Participant
January 29, 2025
Answered

Find the first character of a footnote on a page

  • January 29, 2025
  • 5 replies
  • 622 views

I would appreciate help writing code to find the first character of footnote text on a page, even if the first character is a continuation of a note from a previous page.
I tried using AI, I managed to achieve the desired result but it works very slowly...
Thanks in advance

Correct answer Robert at ID-Tasker

This is the culprit: 

 

for (var c = 0; c < footnote.characters.length; c++) {

 

AI is still stupid... 

 

It should iterate lines of text - not characters... 

 

5 replies

Robert at ID-Tasker
Legend
January 30, 2025

@effi2003

 

If you work on a PC - I can give you access to the full version of my ID-Tasker tool for some time - which will shine in this case.

 

Robert at ID-Tasker
Legend
January 30, 2025

 

1st Task - loads "general" info about Footnotes.

2nd Task - loads "detailed" info about Footnotes - each TextLine separately.

3rd Task - unselects TextLines that are in the middle of the "block" - leaving selected first and last in the "block" (*) - "block" is defined by Sort Order - in this case PAGE INDEX + POS Y + PAGE NAME. Last column in Sort Order - PAGE NAME - is used to recognise a "block" - the same values, which, in this case, means the same page.

 

(*) Right now "Preflight 'First-Last'" rule leaves both first and last lines selected - just need to modify it to leave selected only first or last. 

 

Of course, first lines of each Footnote can be included as well.

 

Robert at ID-Tasker
Legend
January 30, 2025

@FRIdNGE, @m1b

 

I can bet that OP wants to insert graphic that would show that this is a continuation of a Footnote from a previous page - same thing that dublove tried to achieve - few days ago. 

 

FRIdNGE
January 30, 2025

Maybe you talk about this kind of game:

 

https://youtu.be/Kl0b5l79Siw?si=NZG-lCK3x0-xZZ1Z

 

… written for a client 8 years ago!

 

(^/)

m1b
Community Expert
Community Expert
January 30, 2025

Hi @effi2003, I have written a script along the lines of your script above, but I didn't use its code because it was strange and very incomplete. Perhaps this will give you an idea of some approaches to your problem. I've made it somewhat configurable via the `settings` object in the code.

 

There are quick instructions in the code, but let me know if you don't understand anything or if it has bugs. 🙂

- Mark

 

P.S. I have added my demo.indd file which you can try the script out with, if you want to first match my result below.

 

 

 

/**
 * @file Decorate Footnotes.js
 *
 * Usage:
 * 
 *   0. First configure the `settings` object below to suit your needs.
 * 
 *   1. Select a page item. This is the decoration item that will be anchored
 *      to each footnote. Note: in Mode.CURRENT_STORY, you must select the
 *      decoration item AND a text frame containing the target story. The script
 *      will know which is which.
 * 
 *   2. Run Script, which will anchor a copy of the decoration into footnotes
 *      as per the settings configuration.
 *
 * @author m1b
 * @version 2025-01-30
 * @discussion https://community.adobe.com/t5/indesign-discussions/find-the-first-character-of-a-footnote-on-a-page/m-p/15120332
 */
function main() {

    var DecorationPosition = {
        BEFORE_FOOTNOTE_REFERENCE: 0,
        AFTER_FOOTNOTE_REFERENCE: 1,
        AFTER_DELIMITER: 2,
    };

    var Mode = {
        CURRENT_STORY: 'Current Story Only',
        ALL_STORIES: 'All Document Stories',
        CURRENT_PAGE: 'Current Page Only',
    };

    // CONFIGURE SCRIPT HERE:
    var settings = {

        // the object style to use for positioning of the decoration item
        anchoredObjectStyleName: 'Footnote Decoration',

        // which insertion point to use, can be a `DecorationPosition`
        // or a number where 0 is first insertion point (before footnote reference)
        insertionIndex: DecorationPosition.AFTER_DELIMITER,

        // the operational mode: can be Mode.CURRENT_STORY, Mode.ALL_STORIES, Mode.CURRENT_PAGE
        mode: Mode.CURRENT_STORY,

        // whether to remove existing anchored items first
        removeExistingDecorations: true,

        // whether to show alert dialog after script runs
        showResults: true,

    };

    var doc = app.activeDocument,
        items = doc.selection;

    if (
        Mode.CURRENT_STORY === settings.mode
        && 2 !== items.length
    )
        return alert('Please select a page item to use as decoration AND a text frame containing the target story and try again.');

    else if (
        Mode.CURRENT_STORY !== settings.mode
        && 1 !== items.length
    )
        return alert('Please select a single page item to use as decoration and try again.');

    var footnotes = [],
        item = items[0];

    if (
        Mode.ALL_STORIES === settings.mode
        || Mode.CURRENT_PAGE === settings.mode
    )
        footnotes = app.activeDocument.stories.everyItem().footnotes.everyItem().getElements();

    else if (Mode.CURRENT_STORY === settings.mode) {

        for (var i = 0; i < items.length; i++) {

            if (
                items[i].hasOwnProperty('footnotes')
                && items[i].footnotes.length > 0
            )
                // footnotes from the target story
                footnotes = items[i].parentStory.footnotes.everyItem().getElements();
            else
                // the decoration item
                item = items[i];

        }
    }

    if (0 === footnotes.length)
        return alert('No footnotes found.');

    var anchoredObjectStyle = doc.objectStyles.itemByName(settings.anchoredObjectStyleName);

    if (!anchoredObjectStyle.isValid)
        return alert('Could not find "' + settings.anchoredObjectStyleName + '" object style.');

    var counter = 0;

    for (var i = footnotes.length - 1; i >= 0; i--) {

        if (!footnotes[i].isValid)
            continue;

        if (settings.removeExistingDecorations) {
            // remove existing anchored objects from first paragraph
            var existing = footnotes[i].paragraphs[0].pageItems;
            for (var j = existing.length - 1; j >= 0; j--)
                existing[j].remove();
        }

        // target insertion point
        var ip = footnotes[i].paragraphs[0].insertionPoints[settings.insertionIndex].getElements()[0];

        if (
            Mode.CURRENT_PAGE === settings.mode
            && doc.layoutWindows[0].activePage !== ip.parentTextFrames[0].parentPage
        )
            continue;

        // anchor the new decoration
        anchorItems([item.duplicate()], ip, anchoredObjectStyle);
        ip.parentStory.recompose();
        counter++;

    }

    if (settings.showResults)
        alert('Decorate Footnotes\nMode: ' + settings.mode + '\nDecorations added: ' + counter);

};
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'Decorate Footnotes');

/**
 * Anchors supplied `items`, one at a time,
 * to the supplied `insertionPoint`. Can apply
 * an optional Object Style to each item.
 * Note the insertionPoint will move along
 * so that each anchored item will be added
 * after the previous one.
 * @author m1b
 * @version 2024-01-05
 * @param {Array<PageItem>} items - indesign page items.
 * @param {InsertionPoint} insertionPoint - the place to add the items.
 * @param {ObjectStyle} [anchoredObjectStyle] - the object style to apply to each item (default: none).
 */
function anchorItems(items, insertionPoint, anchoredObjectStyle) {

    if (undefined == items)
        throw error('anchorSelectedPageItems failed: no `items` supplied.');

    if (undefined == insertionPoint)
        throw error('anchorSelectedPageItems failed: no `insertionPoint` supplied.');

    for (var i = 0; i < items.length; i++) {

        // do the insertion
        items[i].anchoredObjectSettings.insertAnchoredObject(insertionPoint);

        if (
            undefined != anchoredObjectStyle
            && anchoredObjectStyle.isValid
            && undefined != items[i].clearObjectStyleOverrides
        ) {
            // apply style
            items[i].appliedObjectStyle = anchoredObjectStyle;
            // clear overrides
            items[i].clearObjectStyleOverrides();
        }

    }

};

 

Edit 2025-01-30: to add demo.indd and screenshot.

FRIdNGE
January 30, 2025

… Hmm! Copy in the Clipboard the Page Item to be Pasted and Play this simplistic Grep Find/Replace …

 

 

… Grep F/R you can obviously translate to a Script if you want:

 

// Copy in the Clipboard the Page Item to be Pasted and Play the Game! …
app.findGrepPreferences = app.changeGrepPreferences = null;
app.findGrepPreferences.findWhat = "(?s)^~F\\h\\K(~a\\h)?";
app.changeGrepPreferences.changeTo = "~c ";
app.activeDocument.changeGrep();
app.findGrepPreferences = app.changeGrepPreferences = null;

 

(^/)  The Jedi

Robert at ID-Tasker
Legend
January 30, 2025

@FRIdNGE

 

And how does it finds 1st character of a continuation of a split Footnote??

 

FRIdNGE
January 29, 2025

Curious! Could you show the final result?

 

(^/)  The Jedi

 

effi2003Author
Participant
January 29, 2025

I forgot to mention the purpose of the code. I want to create a script that will find the first character to anchor a graphic object in front of it
This is the code that Claude created for me, but it works very slowly.

function decorateFootnotes(selectedObject) {
var doc = app.activeDocument;
var processedCount = 0;
var errors = [];
var storiesToProcess = [];
var processedPages = {};

try {

if (userChoice.processMode === "currentStory") {
if (userChoice.lastStory && userChoice.lastStory.isValid) {
storiesToProcess.push(userChoice.lastStory);
}
} else if (userChoice.processMode === "byStyle") {
for (var i = 0; i < doc.stories.length; i++) {
var story = doc.stories[i];
var hasMatchingContainer = false;

for (var tc = 0; tc < story.textContainers.length; tc++) {
var container = story.textContainers[tc];
if (container.hasOwnProperty("appliedObjectStyle") &&
container.appliedObjectStyle &&
container.appliedObjectStyle.name === userChoice.objectStyle) {
hasMatchingContainer = true;
break;
}
}

if (hasMatchingContainer) {
storiesToProcess.push(story);
}
}
} else { // allStories
for (var i = 0; i < doc.stories.length; i++) {
var story = doc.stories[i];
var shouldAdd = true;

for (var tc = 0; tc < story.textContainers.length; tc++) {
var container = story.textContainers[tc];
if (container.parentPage && container.parentPage.parent instanceof MasterSpread) {
shouldAdd = false;
break;
}
}

if (shouldAdd) {
storiesToProcess.push(story);
}
}
}

for (var i = 0; i < storiesToProcess.length; i++) {
var story = storiesToProcess[i];

var pageFootnoteChars = {}; 

for (var f = 0; f < story.footnotes.length; f++) {
var footnote = story.footnotes[f];

for (var c = 0; c < footnote.characters.length; c++) {
try {
var currentChar = footnote.characters[c];
var textFrame = currentChar.insertionPoints[0].parentTextFrames[0];
if (!textFrame) continue;

var page = textFrame.parentPage;
if (!page || processedPages[page.name]) continue;

if (!pageFootnoteChars[page.name] ||
currentChar.baseline < pageFootnoteChars[page.name].baseline ||
(currentChar.baseline === pageFootnoteChars[page.name].baseline &&
currentChar.horizontalOffset < pageFootnoteChars[page.name].horizontalOffset)) {
pageFootnoteChars[page.name] = currentChar;
}
} catch (err) {
continue;
}
}
}
Robert at ID-Tasker
Robert at ID-TaskerCorrect answer
Legend
January 30, 2025

This is the culprit: 

 

for (var c = 0; c < footnote.characters.length; c++) {

 

AI is still stupid... 

 

It should iterate lines of text - not characters... 

 

effi2003Author
Participant
January 30, 2025

Thank you! That was indeed the problem...