@Tom25894182epyt, you've already had some good help, and @Peter Kahrel's script is excellent and very clever.
I just thought this would be a fun scripting exercise, so I've written something too, taking a different approach, and so I'll post it here too in case it suits different situations. My idea was to put a shape, eg. a rectangle on the start and end pages, give them names "@start" and "@end" so the script can find them, and interpolate them on the intermediate pages. I think set up is very simple (see attached indesign demo document and instructions are in the script).
If you try it out, let me know how it goes.
- Mark
/**
* @file Update Page Progress Graph.js
*
* Updates a "Page Progress Bar", meaning a graph drawn using Indesign page items
* where one element, eg. a rectangle, grows as the page index increases, to show
* the reader's progress within a document.
*
* Usage:
* 1. Create a path item named `@start` on the first page of the page progress bar.
* 2. Create a path item named `@end` on the last page of the page progress bar.
* 3. Run script, which will create (or update) one shape on each intervening page
* such that is interpolated between `@start` and `@end`.
*
* Rules:
* - `@start` and `@end` page items must have paths, and the same number of path points.
* - The interpolation is similar to Illustrator blends, so `@start` and `@end` items'
* path points are expected to match up, point 0 to point 0 etc.
* - `@start`, `@mid` and `@end` page items must be on a normal page, not on a master page.
*
* Tips:
* - You can create any number of static elements to visually support the page progress bar,
* such as a background box or border. These are not used by the script.
* - The simplest `@start` and `@end` page items are rectangles. Just draw one for the start,
* name it `@start` then copy/paste it to the end page, rename it to `@end` and adjust the
* points to suit, eg scale it to the maximum size of the bar.
* - Because the script interpolates between `@start` and `@end` paths, you can make your
* progress bar go horizontally or vertically simply by the relationship between the paths.
* - If you don't want the progress bars on every page, then run script once, and manually
* remove the bars you don't want, and thereafter run script with `createNewBars: false`
* (script will now update bars that exist, and won't create new bars if they are missing.)
*
* @author m1b
* @discussion https://community.adobe.com/t5/indesign-discussions/progress-current-page-bar-script-running-vertically/m-p/14751998
*/
function main() {
if (0 === app.documents.length)
return alert('Please open a document and try again.');
updatePageProgressBar({
doc: app.activeDocument,
createNewBars: true,
})
}
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'Update Page Progress Graph');
/**
* Updates a Page Progress Bar, by interpolating
* between a `@start` item and an `@end` item
* across the document's pages.
*
* Tips:
* - supply different start/mid/end names if there
* are multiple progress bars in the document.
* - set `createNewBars` to false if you have deliberately
* removed `mid` items from your document. This way you
* can update only those pages that have the `mid` items.
*
* @author m1b
* @version 2024-07-23
* @param {Object} options
* @param {Document} options.doc - an Indesign Document.
* @param {Boolean} [options.createNewBars] - whether to create a new bar on each intervening page, if missing (default: true).
* @param {String} [options.startName] - the start item's name (default: '@start').
* @param {String} [options.endName] - the end item's name (default: '@end').
* @param {String} [options.midName] - the middle items' name (default: '@mid').
*/
function updatePageProgressBar(options) {
options = options || {};
var doc = options.doc,
createNewBars = false !== options.createNewBars,
startName = options.startName || '@start',
endName = options.endName || '@end',
midName = options.midName || '@mid',
items = doc.pageItems,
start = items.item(startName),
end = items.item(endName);
if (
!start.isValid || !start.paths || !start.paths.length
|| !end.isValid || !end.paths || !end.paths.length
)
return alert('Update Page Progress Failed:\nInvalid `' + startName + '` or `' + endName + '` items.');
var startIndex = start.parentPage.documentOffset,
endIndex = end.parentPage.documentOffset,
startPoints = start.paths[0].pathPoints,
endPoints = end.paths[0].pathPoints;
if (startPoints.length !== endPoints.length)
return alert('Update Page Progress Failed:\n`' + startName + '` or `' + endName + '` do not have same number of path points.');
// update the `mid` on each page
for (var i = startIndex + 1, mid, page, t; i < endIndex; i++) {
page = doc.pages[i];
mid = getThing(page.allPageItems, 'name', midName);
if (
mid && mid.isValid
&& mid.paths[0].pathPoints.length !== startPoints.length
)
// remove old faulty item
mid.remove();
if (!mid || !mid.isValid) {
if (createNewBars)
// create a new mid item
mid = start.duplicate(page);
else {
// nothing to do on this page
continue;
}
}
// we can't change it if its layer is locked
mid.itemLayer.locked = false;
// name this so we can pick it up later
mid.name = midName;
// location between start and end, as a unit interval
t = (i - startIndex) / (endIndex - startIndex);
// interpolate each path point
for (var j = 0; j < mid.paths[0].pathPoints.length; j++)
interpolatePathPoint(start.paths[0].pathPoints[j], end.paths[0].pathPoints[j], mid.paths[0].pathPoints[j], t);
}
};
/**
* Transforms `mid` path point by performing
* linear interpolation between `start` and `end`,
* given interval `t`.
* @author m1b
* @version 2024-07-23
* @param {PathPoint} start - the first PathPoint.
* @param {PathPoint} end - the last PathPoint.
* @param {PathPoint} mid - the middle PathPoint.
* @param {Number} t - a unit interval, eg. 0.5 is half way between start and end.
*/
function interpolatePathPoint(start, end, mid, t) {
mid.properties = {
anchor: linearIntPoint(start.anchor, end.anchor),
leftDirection: linearIntPoint(start.leftDirection, end.leftDirection),
rightDirection: linearIntPoint(start.rightDirection, end.rightDirection),
};
function linearIntPoint(p0, p1) { return [linearInt(p0[0], p1[0]), linearInt(p0[1], p1[1])] };
function linearInt(n, m) { return n + t * (m - n) };
};
/**
* Returns a thing with matching property.
* If `key` is undefined, evaluate the object itself.
* @author m1b
* @version 2024-04-21
* @param {Array|Collection} things - the things to look through.
* @param {String} [key] - the property name (default: undefined).
* @param {*} value - the value to match.
* @returns {*?} - the thing, if found.
*/
function getThing(things, key, value) {
for (var i = 0, obj; i < things.length; i++)
if ((undefined == key ? things[i] : things[i][key]) == value)
return things[i];
};
Edit 2024-07-23: fixed typo in script. Oops.