thanks so much! I only know enough about this to get into a little trouble, so thank you for your patience!
I sent you a link to a demo file.
One more note, and something to add to the wishlist would be a way to control the color order so it's pink, orange, green, navy, turquoise.
Hi @Paperback Pat, okay so on private message you've added a couple of new parts: (1) target a paragraph style, and (2) specify and a color order with no repetitions. By supplying a demo document (I've attached it here for anyone to follow along) you've also confirmed that you only want colors from a specific color group. That's enough information for me to configure the script. I'll list it again, rather than update the original one, because this also has the extra function `getTexts` which is the function that collects the headings to color.
Also, to make it easier for you to see what you can adjust, I've moved the configuration details into a `settings` object at the start of the script. You will see what I mean when you read the script below.
Oh, I've also added a `shuffler` function for randomizing the colors such that they are shuffled around in batches—so each batch of colours is shuffled each time they cycle, with a quick check to make sure that the first and last colors in the batches don't match. The result should be that no two colors are adjacent. You can change "colorPicker: shuffler" to "colorPicker: cycler" if you want to see what happens if each color is applied in turn.
There's bound to be bugs, because I only tested on your simple document, but it's a starting point. Let me know how it goes.
- Mark
/**
* @file Color Characters.js
*
* There are three working parts to this script, each of which
* can be configured:
*
* 1. `getTexts` function which collects texts from the document.
*
* 2. `getColors` function, which will collect colors
* from the document based on the parameters specified; and
*
* 3. `colorCharacters` function, which assigns colors to characters.
*
* See each function's documentation below.
*
* @author m1b
* @discussion https://community.adobe.com/t5/indesign-discussions/indesign-script-to-randomly-apply-color-swatches-to-text-characters/m-p/14567096
*/
function main() {
var settings = {
paragraphStyleName: 'Chap_Title',
colorGroupName: 'Chap_Title_Colors',
colorPicker: shuffler,
};
if (0 === app.documents.length)
return alert('Please select some text and try again.');
var doc = app.activeDocument;
var texts = getTexts({
items: doc,
findGrepPreferences: {
appliedParagraphStyle: settings.paragraphStyleName,
},
});
if (0 === texts.length)
return alert('No texts were found with the current options.');
var colors = getColors({
doc: doc,
includeColorGroups: [settings.colorGroupName],
});
if (0 === colors.length)
return alert('No colors were found with the current options.');
// do the coloring
colorCharacters(texts, colors, settings.colorPicker);
// finished!
/**
* Returns a random number between 0 and `max`,
* cycling when `index` exceeds `max`.
* @author m1b
* @version 2024-04-24
* @param {Character} [ch] - a character (not used)
* @param {Number} index - the character index.
* @param {Number} max - the maximum index.
* @returns {Number} - a random index
*/
function shuffler(ch, index, max) {
index = index % max;
var self = callee;
if (0 === index) {
// randomize once every cycle
self.arr = [];
for (var i = 0; i < max; i++)
self.arr[i] = i;
var i = max,
r,
temp;
while (i--) {
r = Math.floor(Math.random() * (i + 1));
// swap randomly chosen element with current element
temp = self.arr[i];
self.arr[i] = self.arr[r];
self.arr[r] = temp;
if (0 === i && self.previousIndex === self.arr[0]) {
// swap first two to avoid adjacent values
temp = self.arr[0];
self.arr[0] = self.arr[1];
self.arr[1] = temp;
}
}
self.previousIndex = self.arr[max - 1];
}
return self.arr[index];
};
};
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'Color Characters');
/**
* Returns an array of colors from a document.
* ----------------------------------------------
* General usage notes:
* - leave `includeSwatches` undefined to use every swatch
* - using `filter` will override all other parameters.
* ----------------------------------------------
* Example 1 - Get all swatches, except some:
*
* var colors = getColors({
* doc: doc,
* excludeSwatches: ['None', 'Registration', 'Paper'],
* });
* ----------------------------------------------
* Example 3 - Get all swatches in a color group:
*
* var colors = getColors({
* doc: doc,
* includeColorGroup: 'My Favourite Colours',
* });
* ----------------------------------------------
* Example 2 - Get all swatches with name starting "PANTONE":
*
* var colors = getColors({
* doc: doc,
* filter: function (swatchName) {
* return /^PANTONE/.test(swatchName);
* },
* });
* ----------------------------------------------
* @author m1b
* @version 2024-04-20
* @param {Object} options
* @param {Array<String>} [options.includeSwatches] - swatch names to specifically include (default: include all swatches).
* @param {Array<String>} [options.excludeSwatches] - swatch names to specifically exclude (default: no exclusions).
* @param {Array<String>} [options.includeColorGroups] - color group names to specifically include (default: include all color groups).
* @param {Array<String>} [options.excludeColorGroups] - color group names to specifically exclude (default: no exclusions).
* @param {Function} [options.filter] - function that returns true to include swatch (default: no filter).
* @returns {Array<Color>}
*/
function getColors(options) {
options = options || {};
var doc = options.doc || app.activeDocument,
includeSwatches = options.includeSwatches || [],
excludeSwatches = options.excludeSwatches || [],
includeColorGroups = options.includeColorGroups || [],
excludeColorGroups = options.excludeColorGroups || [],
filter = options.filter;
const INCLUDE_ALL_SWATCHES = (
0 === includeColorGroups.length
&& 0 === includeSwatches.length
&& undefined == filter
);
var allSwatches = doc.colorGroups.everyItem().colorGroupSwatches.everyItem().swatchItemRef,
swatches = [];
for (var i = 0, sw; i < allSwatches.length; i++) {
sw = allSwatches[i];
if (undefined != filter) {
// include according to filter function
if (filter(sw.name))
swatches.push(sw);
else
continue;
}
if (
getThing(excludeSwatches, undefined, sw.name)
|| getThing(excludeColorGroups, undefined, sw.parentColorGroup.name)
)
// ignore specifically-excluded swatch
continue;
if (
// include all swatches by default
INCLUDE_ALL_SWATCHES
// include swatch in specific color group
|| getThing(includeColorGroups, undefined, sw.parentColorGroup.name)
// include specific swatch
|| getThing(includeSwatches, undefined, sw.name)
)
swatches.push(sw);
}
return swatches;
};
/**
* Assigns fill color to selected text randomly.
* ----------------------------------------------
* Example `colorIndexPicker` functions:
*
* function cycler(ch, index, max) {
* // return the next value, in order, cycling
* return index % max;
* };
*
* ----------------------------------------------
* Example `characterPicker` functions:
*
* function pickHalfCharacters() {
* return Math.random() < 0.5;
* }
*
* function pickEveryNthCharacter(ch, index) {
* var n = 2;
* return (index + 1) % n === 0;
* }
* ----------------------------------------------
* @author m1b
* @version 2024-04-20
* @param {Array|Collection<TextFrame|Text>} things - array or collection of DOM object's with characters.
* @param {Array<Color|null>} colors - array of colors to use.
* @param {Function} [colorIndexPicker] - function that determines which color to use (default: pick random color).
* @param {Function} [characterPicker] - function that determines if a character is colored (default: pick every character).
*/
function colorCharacters(things, colors, colorIndexPicker, characterPicker) {
colorIndexPicker = colorIndexPicker || defaultColorPicker;
characterPicker = characterPicker || defaultCharacterpPicker;
if (undefined == things[0])
things = [things];
for (var i = 0, characters; i < things.length; i++) {
if (!things[i].hasOwnProperty('characters'))
continue;
if (things[i].hasOwnProperty('pageItems'))
colorCharacters(things[i].pageItems, colors, colorIndexPicker, characterPicker);
if (things[i].hasOwnProperty('tables'))
colorCharacters(things[i].tables.everyItem().cells, colors, colorIndexPicker, characterPicker);
if (things[i].hasOwnProperty('footnotes'))
colorCharacters(things[i].footnotes, colors, colorIndexPicker, characterPicker);
characters = things[i].characters;
characterLoop:
for (var j = 0, r, len = characters.length; j < len; j++) {
if (!characterPicker(characters[j], j, len - 1))
continue characterLoop;
// get the next color index
r = colorIndexPicker(characters[j], j, colors.length);
// color the character
characters[j].fillColor = colors[r];
}
}
function defaultColorPicker(ch, index, max) {
return Math.floor(Math.random() * max);
};
function defaultCharacterpPicker() {
return true;
};
};
/**
* Returns a thing with a `key` matching `value`.
* If `key` is undefined, evaluate the object itself.
* @author m1b
* @version 2024-04-20
* @param {Array|Collection} things - the things to look through.
* @param {String} [key] - the property name (default: undefined).
* @param {*} value - the value to match.
* @returns {*}
*/
function getThing(things, key, value) {
for (var i = 0, obj; i < things.length; i++)
if ((undefined == key ? things[i] : thing[i][key]) == value)
return things[i];
};
/**
* Returns texts found in `items`.
*
* Note: this is intended to use with
* findGrep - it won't work without
* a `findGrepPreferences` object.
* ----------------------------------------------
* Example - styled paragraphs of document:
*
* var texts = getTexts({
* items: doc,
* findGrepPreferences: {
* appliedParagraphStyle: 'My Style',
* },
* });
* ----------------------------------------------
* @author m1b
* @version 2024-04-24
* @param {Object} options
* @param {*} options.items - the DOM objects to extract text from, eg. Document or Document.selection.
* @param {Object} options.findGrepPreferences - properties to configure findGrep.
* @param {Function} [options.filter] - function that, given the object, returns true to collect its text (default: no filter).
* @returns {Array<Text>}
*/
function getTexts(options) {
options = options || {};
if (undefined == options.items)
throw Error('getTexts: expected `options.items` parameter.');
if (undefined == options.findGrepPreferences)
throw Error('getTexts: expected `options.findGrepPreferences` parameter.');
var items = options.items,
texts = [];
if ('Document' === items.constructor.name)
items = items.stories;
if (!items.hasOwnProperty(0))
items = [items];
// set up find grep
app.findGrepPreferences = NothingEnum.NOTHING;
app.changeGrepPreferences = NothingEnum.NOTHING;
for (var key in options.findGrepPreferences)
if (options.findGrepPreferences.hasOwnProperty(key))
app.findGrepPreferences[key] = options.findGrepPreferences[key];
for (var i = 0; i < items.length; i++) {
if (
undefined != options.filter
&& true !== options.filter(items[i])
)
continue;
if (
undefined != items[i].contents
&& 0 === items[i].contents.length
)
continue;
if ('function' !== typeof items[i].findGrep)
continue;
texts = texts.concat(items[i].findGrep());
}
return texts;
};