m1b
Community Expert
m1b
Community Expert
Activity
‎Feb 27, 2025
01:20 AM
Hi @Peeyush37849741 that looks like it is on a parent spread (master page) so try going to the master and changing it manually—if it is set up right it will change on every page.
Otherwise, do a Find Text as you normally would, like this:
Is that what you mean?
- Mark
... View more
‎Feb 27, 2025
12:40 AM
@aniri5j9ox8tw2ln
> The only thing I have now noticed is that the pictures that are marked green can be found in different folders.
The script applies the label to the linked files, wherever they are. Nothing else.
Beyond that, I'm not really sure what you mean. Maybe you should do a "Package" to collect the links into one folder? Or do you only want to label *some* of the links? Or do you just want to see a list of all the paths?
- Mark
... View more
‎Feb 27, 2025
12:16 AM
2 Upvotes
Hi @Eugene Tyson yes it is definitely possible to configure the document so that running a script will restore the relationship between page items, eg. to position the button near the answer text frame. But I can't tell if that will meet Wendy's needs.
Hi @JH-Wendy would it be possible for you to describe—from the point of view of the user—how your document would work? For example, do they open a pdf, fill out a form field with their answer to a question, then click a button to trigger the actual answer to appear? If so, what is the print document like?
Without knowing more, I'm wondering if you could set up a multi-state object in Indesign, and add in a little scripting in Acrobat to do the showing/hiding. The states would show (a) The message to the user and the answer form field and the answer submit button, (b) the answer, and (c) the print version. See quick example .indd attached. Then a script can be used to toggle between state 1 and state 3 (for printing). That's one idea anyway.
- Mark
... View more
‎Feb 27, 2025
12:04 AM
2 Upvotes
Hi @aniri5j9ox8tw2ln, yes it was a change to MacOS that required a little change to the AppleScript. Here is an updated script. I made it a bit easier to use with documentation and a "FinderLabels" enum. I'll leave the old version in case it only works with older versions of MacOS, although I think this new one should work too. Let me know how it goes.
- Mark
/**
* @file Set Finder Label Of Linked Files.js
*
* Note: MacOS only due to AppleScript usage.
*
* @author m1b
* @version 2025-02-27
* @discussion https://community.adobe.com/t5/indesign-discussions/suche-script-oder-plug-in-verknüpfungen-suchen-und-einfärben/m-p/15180561
*/
(function () {
var FinderLabel = {
NONE: 0,
ORANGE: 1,
RED: 2,
YELLOW: 3,
BLUE: 4,
PURPLE: 5,
GREEN: 6,
GRAY: 7,
};
colorLinksByFinderLabelIndex(app.activeDocument, FinderLabel.GREEN);
})();
/**
* Applies a Finder label (color) to each of `doc`'s linked files.
* @author m1b
* @version 2025-02-27
* @param {Document} doc- an Indesign Document.
* @param {Number} [labelIndex] - the label to apply, a number in range 0..7 (default: 'Orange')
*/
function colorLinksByFinderLabelIndex(doc, labelIndex) {
doc = doc || app.activeDocument;
if (undefined == labelIndex)
labelIndex = 1;
var links = doc.links;
for (var i = 0; i < links.length; i++) {
var link = links[i];
// don't bother if it's missing
if (link.status == LinkStatus.LINK_MISSING) continue;
// if file exists, label it
if (!File(link.filePath).exists)
continue;
// set the label using AppleScript
var applescript = 'tell application "Finder" to set label index of (POSIX file "' + link.filePath + '" as alias) to ' + labelIndex;
app.doScript(applescript, ScriptLanguage.APPLESCRIPT_LANGUAGE);
}
};
... View more
‎Feb 26, 2025
11:15 PM
Hi @Riannon368885617n15 this might be easy to fix with a script. Can you post a sample document with some of the problematic hyperlinks in it?
- Mark
... View more
‎Feb 26, 2025
11:09 PM
Hi @lizardo_4752, you have said you need to copy the artwork from some artboards to a new document. But why do you need a script for it? It is just a matter of selecting and copy/pasting. There must be reason a script is needed. Please tell us more. 🙂
- Mark
... View more
‎Feb 26, 2025
05:19 PM
1 Upvote
Hi @Phil5C41 you already have @Peter Kahrel's perfect answer so you're probably done, but I wanted to learn a bit about scripting style tag mapping myself, so I wrote a little script that does what your script is trying to do. I figured I may as well share it.
I changed your mapping system so that the tag names are the object keys and the values are RegExps. This is because I wanted to be able to have multiple paragraph styles that would be mapped to the same tag. For example, when headings appear in different colors to match a chapter color theme, we could name them "H1-Chapter 1", "H1-Chapter 2", etc, and the script would tag them all "H1". (This would have worked with your approach—using .indexOf—but it isn't as configurable as RegExp .test and it may give false positives, such as tagging a style named "MONTH1" as "H1".)
- Mark
/**
* @file Assign Paragraph Style Export Tags.js
*
* Will assign a pdf export tag to any matched
* paragraph style in active document.
*
* I have set the `tagMappings` to match a paragraph style name, either:
* - being the tag name by itself eg. "P", or
* - starting with tag name followed by a hyphen, eg. "P-Small" and "P-Medium".
*
* @author m1b
* @version 2025-02-27
* @discussion https://community.adobe.com/t5/indesign-discussions/script-to-automate-the-tagging-of-paragraph-styles-based-on-keyword/m-p/15179157
*/
function main() {
var doc = app.activeDocument;
if (!doc) {
alert("No active document found.");
return;
}
// tag name mapped to style name test RegExp
var tagMappings = {
"H1": /^H1($|-)/,
"H2": /^H2($|-)/,
"H3": /^H3($|-)/,
"H4": /^H4($|-)/,
"H5": /^H5($|-)/,
"H6": /^H6($|-)/,
"P": /^P($|-)/,
};
var paragraphStyles = doc.allParagraphStyles;
var counter = 0;
for (var i = 0; i < paragraphStyles.length; i++) {
var style = paragraphStyles[i];
tagMappingLoop:
for (var tagName in tagMappings) {
if (
tagMappings.hasOwnProperty(tagName)
&& tagMappings[tagName].test(style.name)
) {
var tagMap = style.styleExportTagMaps[0];
if (tagMap.isValid)
// update the first export tag map
tagMap.properties = {
exportTag: tagName,
exportType: 'PDF',
exportClass: '',
exportAttributes: '',
};
else
// add a new export tag map
tagMap = style.styleExportTagMaps.add('PDF', tagName, '', '');
counter++;
break tagMappingLoop; // Stop checking once a match is found
}
}
}
alert('Updated ' + counter + " paragraph styles with export tags.");
};
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'Assign Paragraph Style Export Tags');
... View more
‎Feb 25, 2025
04:03 PM
Robert is right, this is something that should be fixed in the code. Something like this:
var files = myFolder.getFiles();
files.sort(function(a, b) {
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
});
... View more
‎Feb 25, 2025
03:26 PM
1 Upvote
Great. I have written a script that will process your svg files. Instructions are in the script header. You can look through and get an idea of what it is doing. It just removes some unwanted attributes from each path and adds a class name "line" and adds a CSS style definition. You should edit the style definition to suit your needs. Let me know how it goes.
- Mark
Before:
After:
/**
* @File Style SVG Files.js
*
* Set up:
* Run this script once, to create "SVG IMPORT" and "SVG EXPORT"
* folders in the same location as this script file.
*
* Usage:
* 1. Put your svg files into "SVG IMPORT"
* 2. Run script
* 3. Collect the resulting svg files from "SVG EXPORT"
*
* @author m1b
* @version 2025-02-26
* @discussion https://community.adobe.com/t5/illustrator-discussions/illustrator-script-to-edit-multiply-files/m-p/15177289
*/
(function () {
// edit this to match your needs
// must be valid CSS
var style = [
'fill: none;',
'stroke: #1d1d1b;',
'stroke-width: .2px;',
'stroke-miterlimit: 10;',
];
var svgImportFolder = Folder(File($.fileName).parent + '/SVG IMPORT/'),
svgExportFolder = Folder(File($.fileName).parent + '/SVG EXPORT/');
if (!svgImportFolder.exists)
svgImportFolder.create();
if (!svgExportFolder.exists)
svgExportFolder.create();
// collect the svg files
var files = svgImportFolder.getFiles(),
fileCounter = 0;
// this is the style definition element
var styleElement = new XML(
'<defs><style>.line {##STYLE##}</style></defs>'
.replace('##STYLE##', style.join(' '))
);
fileLoop:
for (var i = 0; i < files.length; i++) {
var f = files[i];
f.open('r');
var svg = f.read();
if (!svg)
continue fileLoop;
try {
var xml = new XML(svg),
elements = xml.descendants(),
len = elements.length(),
elementCounter = 0;
} catch (error) {
// failed to parse XML
continue fileLoop;
}
elementLoop:
for (var j = 0; j < len; j++) {
var el = elements[j];
if ('path' !== el.localName())
continue elementLoop;
// remove unwanted attributes
delete el['@stroke-width'];
delete el['@stroke'];
delete el['@fill'];
// add our class
el['@class'] = 'line';
elementCounter++;
}
if (0 === elementCounter)
continue fileLoop;
// Append <defs> to the root <svg>
xml.insertChildBefore(elements[0], styleElement);
// write to file
var exportFile = File(svgExportFolder + '/' + decodeURI(f.name));
exportFile.open('w');
var success = exportFile.write(xml.toString());
if (success)
fileCounter++
}
alert('Processed ' + fileCounter + ' svg files.');
})();
Edit 2025-02-26: minor typo.
... View more
‎Feb 25, 2025
12:42 PM
Hi @Roci It might be possible with a script. Could you share one or two of the svg files?
- Mark
... View more
‎Feb 24, 2025
07:20 PM
Hi @Mariano– can you post the script that generates the CSV file? I suspect it would be easy to fix it.
- Mark
... View more
‎Feb 24, 2025
03:15 PM
Hi @Utson_Avila2888 I see. Yes I hadn't expected anyone to fill or stroke a clipping mask, but I've updated my code above with a check for it. Should work correctly now.
- Mark
P.S. A couple of notes on posting code: (1) please use the </> button to format your code, and (2) if you are posting someone else's code publicly, it is impolite to remove their authorship notice.
... View more
‎Feb 23, 2025
10:02 PM
2 Upvotes
Hi @optimisticperson, as others have rightly said, there is no other way than adjusting by hand because Illustrator has no capability to judge properties such as the "visual weight" of an object. No doubt one day it will! Making disparate logos sit comfortably together is a task involving significant skill.
However, perhaps it would still save some time to give a rough approximation? I have written a script that scales the selected page items such that the area of each item's bounds matches the average area of all the items.
It won't do a great job, because the bounds of a logo isn't a good metric for the visual weight, but it might provide a starting point. Let me know if it is helpful.
- Mark
/**
* @file Scale To Average Area.js
*
* Scales the selection such that the area
* of each item's geometric bounds match
* the average area of the selected items.
*
* @author m1b
* @version 2025-02-24
* @discussion https://community.adobe.com/t5/illustrator-discussions/how-to-automatically-resize-a-bunch-of-logos-so-they-re-visually-proportional/m-p/15168811
*/
(function () {
var doc = app.activeDocument,
items = doc.selection;
if (0 === items.length)
return alert('Please select some items and try again.');
scaleItemsToAverageArea(items);
})();
/**
* Scales each page item in `items` such that the area
* of each item's geometric bounds match the average area
* of the selected items.
* @date 2025-02-24
* @param {Array<PageItem>} items - the items to scale.
*/
function scaleItemsToAverageArea(items) {
var areas = [],
areaSum = 0;
for (var i = 0, bounds, area; i < items.length; i++) {
bounds = getItemBoundsIllustrator(items[i], false);
area = (bounds[2] - bounds[0]) * -(bounds[3] - bounds[1]);
areas[i] = area;
areaSum += area;
}
var averageArea = Math.sqrt(areaSum) / items.length;
for (var i = 0, scaleFactor; i < areas.length; i++) {
scaleFactor = averageArea / Math.sqrt(areas[i]);
items[i].resize(scaleFactor * 100, scaleFactor * 100);
}
};
/**
* Returns bounds of item(s).
* @author m1b
* @version 2024-03-10
* @param {PageItem|Array<PageItem>} item - an Illustrator PageItem or array of PageItems.
* @param {Boolean} [geometric] - if false, returns visible bounds.
* @param {Array} [bounds] - private parameter, used when recursing.
* @returns {Array} - the calculated bounds.
*/
function getItemBoundsIllustrator(item, geometric, bounds) {
var newBounds = [],
boundsKey = geometric ? 'geometricBounds' : 'visibleBounds';
if (undefined == item)
return;
if (
item.typename == 'GroupItem'
|| item.constructor.name == 'Array'
) {
var children = item.typename == 'GroupItem' ? item.pageItems : item,
contentBounds = [],
isClippingGroup = (item.hasOwnProperty('clipped') && item.clipped == true),
clipBounds;
for (var i = 0, child; i < children.length; i++) {
child = children[i];
if (
child.hasOwnProperty('clipping')
&& true === child.clipping
)
// the clipping item
clipBounds = child.geometricBounds;
else
contentBounds.push(getItemBoundsIllustrator(child, geometric, bounds));
}
newBounds = combineBounds(contentBounds);
if (isClippingGroup)
newBounds = intersectionOfBounds([clipBounds, newBounds]);
}
else if (
'TextFrame' === item.constructor.name
&& TextType.AREATEXT !== item.kind
) {
// get bounds of outlined text
var dup = item.duplicate().createOutline();
newBounds = dup[boundsKey];
dup.remove();
}
else if (item.hasOwnProperty(boundsKey)) {
newBounds = item[boundsKey];
}
// `bounds` will exist if this is a recursive execution
bounds = (undefined == bounds)
? bounds = newBounds
: bounds = combineBounds([newBounds, bounds]);
return bounds;
};
/**
* Returns the combined bounds of all bounds supplied.
* Works with Illustrator or Indesign bounds.
* @author m1b
* @version 2024-03-09
* @param {Array<bounds>} boundsArray - an array of bounds [L, T, R, B] or [T, L , B, R].
* @returns {bounds?} - the combined bounds.
*/
function combineBounds(boundsArray) {
var combinedBounds = boundsArray[0],
comparator;
if (/indesign/i.test(app.name))
comparator = [Math.min, Math.min, Math.max, Math.max];
else
comparator = [Math.min, Math.max, Math.max, Math.min];
// iterate through the rest of the bounds
for (var i = 1; i < boundsArray.length; i++) {
var bounds = boundsArray[i];
combinedBounds = [
comparator[0](combinedBounds[0], bounds[0]),
comparator[1](combinedBounds[1], bounds[1]),
comparator[2](combinedBounds[2], bounds[2]),
comparator[3](combinedBounds[3], bounds[3]),
];
}
return combinedBounds;
};
/**
* Returns the overlapping rectangle
* of two or more rectangles.
* NOTE: Returns undefined if ANY
* rectangles do not intersect.
* @author m1b
* @version 2024-09-05
* @param {Array<bounds>} arrayOfBounds - an array of bounds [L, T, R, B] or [T, L , B, R].
* @returns {bounds?} - intersecting bounds.
*/
function intersectionOfBounds(arrayOfBounds) {
var comparator;
if (/indesign/i.test(app.name))
comparator = [Math.max, Math.max, Math.min, Math.min];
else
comparator = [Math.max, Math.min, Math.min, Math.max];
// sort a copy of array
var bounds = arrayOfBounds
.slice(0)
.sort(function (a, b) { return b[0] - a[0] || a[1] - b[1] });
// start with first bounds
var intersection = bounds.shift(),
b;
// compare each bounds, getting smaller
while (b = bounds.shift()) {
// if doesn't intersect, bail out
if (!boundsDoIntersect(intersection, b))
return;
intersection = [
comparator[0](intersection[0], b[0]),
comparator[1](intersection[1], b[1]),
comparator[2](intersection[2], b[2]),
comparator[3](intersection[3], b[3]),
];
}
return intersection;
};
/**
* Returns true if the two bounds intersect.
* @author m1b
* @version 2024-03-10
* @param {Array} bounds1 - bounds array.
* @param {Array} bounds2 - bounds array.
* @param {Boolean} [TLBR] - whether bounds arrays are interpreted as [t, l, b, r] or [l, t, r, b] (default: based on app).
* @returns {Boolean}
*/
function boundsDoIntersect(bounds1, bounds2, TLBR) {
if (undefined == TLBR)
TLBR = (/indesign/i.test(app.name));
return !(
TLBR
// TLBR
? (
bounds2[0] > bounds1[2]
|| bounds2[1] > bounds1[3]
|| bounds2[2] < bounds1[0]
|| bounds2[3] < bounds1[1]
)
// LTRB
: (
bounds2[0] > bounds1[2]
|| bounds2[1] < bounds1[3]
|| bounds2[2] < bounds1[0]
|| bounds2[3] > bounds1[1]
)
);
};
... View more
‎Feb 23, 2025
08:51 PM
Hi @Utson_Avila2888 for your learning, I've made two changes to your code:
1. I've written a function—getItemBoundsIllustrator—that does a bit more heavy lifting when trying to get the actual bounds, and I've called it from your distributeObjects function, and
2. I've changed the way the items are positioned: rather than settings "position" property, I use the translate method—you can do it either way, but I find translate more understandable.
I tested with your sample file and it seemed to work correctly for me. Let me know if that gets you moving forward.
- Mark
(function () {
var doc = app.activeDocument;
var selx = doc.selection;
if (selx.length < 2) {
alert("Por favor, selecciona al menos 2 objetos.");
} else {
var userValues = showDialog();
if (userValues) {
distributeObjects(selx, userValues);
}
}
})();
function showDialog() {
var dialog = new Window('dialog', 'Distribuir Objetos Apilados');
dialog.add('statictext', undefined, 'Separación:');
var separationInput = dialog.add('edittext', undefined, '1');
separationInput.characters = 5;
var unitsGroup = dialog.add('group');
var cmButton = unitsGroup.add('radiobutton', undefined, 'cm');
var mmButton = unitsGroup.add('radiobutton', undefined, 'mm');
var pxButton = unitsGroup.add('radiobutton', undefined, 'px');
cmButton.value = true;
dialog.add('statictext', undefined, 'Modo de distribución:');
var modeGroup = dialog.add('group');
var gridButton = modeGroup.add('radiobutton', undefined, 'Grid');
var horizontalButton = modeGroup.add('radiobutton', undefined, 'Horizontal');
var verticalButton = modeGroup.add('radiobutton', undefined, 'Vertical');
verticalButton.value = true;
dialog.add('statictext', undefined, 'Columnas (solo para Grid):');
var columnsInput = dialog.add('edittext', undefined, '3');
columnsInput.characters = 5;
var buttons = dialog.add('group');
buttons.alignment = 'center';
var cancelButton = buttons.add('button', undefined, 'Cancelar', { name: 'cancel' });
var okButton = buttons.add('button', undefined, 'Aceptar', { name: 'ok' });
okButton.onClick = function () {
dialog.close(1);
};
cancelButton.onClick = function () {
dialog.close(0);
};
if (dialog.show() == 1) {
return {
separation: parseFloat(separationInput.text),
unit: cmButton.value ? 'cm' : (mmButton.value ? 'mm' : 'px'),
mode: gridButton.value ? 'grid' : (horizontalButton.value ? 'horizontal' : 'vertical'),
columns: parseInt(columnsInput.text)
};
}
return null;
}
function distributeObjects(sel, userValues) {
var separation = userValues.separation;
var unitMultiplier = (userValues.unit == 'cm') ? 28.3465 : (userValues.unit == 'mm' ? 2.83465 : 1);
separation *= unitMultiplier;
var mode = userValues.mode;
var gridCols = (mode == 'grid') ? userValues.columns : (mode == 'horizontal' ? sel.length : 1);
var currentX = 0, currentY = 0, maxRowH = 0;
if (mode == 'horizontal' || mode == 'grid') {
sel.sort(function (a, b) {
return getVisibleBounds(a)[0] - getVisibleBounds(b)[0];
});
} else if (mode == 'vertical') {
sel.sort(function (a, b) {
return getVisibleBounds(a)[3] - getVisibleBounds(b)[3];
});
}
for (var i = 0; i < sel.length; i++) {
var bounds = getItemBoundsIllustrator(sel[i]);
var objectWidth = bounds[2] - bounds[0];
var objectHeight = bounds[1] - bounds[3];
var dx = currentX - bounds[0];
var dy = currentY - bounds[1];
sel[i].translate(dx, dy, true, true, true, true);
if (mode == 'horizontal') {
currentX += (objectWidth + separation);
} else if (mode == 'vertical') {
currentY -= (objectHeight + separation);
} else if (mode == 'grid') {
currentX += (objectWidth + separation);
if ((i % gridCols) == (gridCols - 1)) {
currentX = 0;
currentY -= (maxRowH + separation);
maxRowH = 0;
}
maxRowH = Math.max(maxRowH, objectHeight);
}
}
}
function getVisibleBounds(item) {
if (item.typename === 'GroupItem' && item.clipped) {
for (var i = 0; i < item.pageItems.length; i++) {
if (item.pageItems[i].clipping) {
return item.pageItems[i].geometricBounds;
}
}
}
return item.geometricBounds;
}
/**
* Returns bounds of item(s).
* @author m1b
* @version 2025-02-25
* @param {PageItem|Array<PageItem>} item - an Illustrator PageItem or array of PageItems.
* @param {Boolean} [geometric] - if false, returns visible bounds.
* @param {Array} [bounds] - private parameter, used when recursing.
* @returns {Array} - the calculated bounds.
*/
function getItemBoundsIllustrator(item, geometric, bounds) {
var newBounds = [],
boundsKey = geometric ? 'geometricBounds' : 'visibleBounds';
if (undefined == item)
return;
if (
item.typename == 'GroupItem'
|| item.constructor.name == 'Array'
) {
var children = item.typename == 'GroupItem' ? item.pageItems : item,
contentBounds = [],
isClippingGroup = (item.hasOwnProperty('clipped') && item.clipped == true),
clipBounds;
for (var i = 0, child; i < children.length; i++) {
child = children[i];
if (
child.hasOwnProperty('clipping')
&& true === child.clipping
&& true !== child.stroked
&& true !== child.filled
)
// the clipping item
clipBounds = child.geometricBounds;
else
contentBounds.push(getItemBoundsIllustrator(child, geometric, bounds));
}
newBounds = combineBounds(contentBounds);
if (
isClippingGroup
&& clipBounds
)
newBounds = intersectionOfBounds([clipBounds, newBounds]);
}
else if (
'TextFrame' === item.constructor.name
&& TextType.AREATEXT !== item.kind
) {
// get bounds of outlined text
var dup = item.duplicate().createOutline();
newBounds = dup[boundsKey];
dup.remove();
}
else if (item.hasOwnProperty(boundsKey)) {
newBounds = item[boundsKey];
}
// `bounds` will exist if this is a recursive execution
bounds = (undefined == bounds)
? bounds = newBounds
: bounds = combineBounds([newBounds, bounds]);
return bounds;
};
/**
* Returns the combined bounds of all bounds supplied.
* Works with Illustrator or Indesign bounds.
* @author m1b
* @version 2024-03-09
* @param {Array<bounds>} boundsArray - an array of bounds [L, T, R, B] or [T, L , B, R].
* @returns {bounds?} - the combined bounds.
*/
function combineBounds(boundsArray) {
var combinedBounds = boundsArray[0],
comparator;
if (/indesign/i.test(app.name))
comparator = [Math.min, Math.min, Math.max, Math.max];
else
comparator = [Math.min, Math.max, Math.max, Math.min];
// iterate through the rest of the bounds
for (var i = 1; i < boundsArray.length; i++) {
var bounds = boundsArray[i];
combinedBounds = [
comparator[0](combinedBounds[0], bounds[0]),
comparator[1](combinedBounds[1], bounds[1]),
comparator[2](combinedBounds[2], bounds[2]),
comparator[3](combinedBounds[3], bounds[3]),
];
}
return combinedBounds;
};
/**
* Returns the overlapping rectangle
* of two or more rectangles.
* NOTE: Returns undefined if ANY
* rectangles do not intersect.
* @author m1b
* @version 2024-09-05
* @param {Array<bounds>} arrayOfBounds - an array of bounds [L, T, R, B] or [T, L , B, R].
* @returns {bounds?} - intersecting bounds.
*/
function intersectionOfBounds(arrayOfBounds) {
var comparator;
if (/indesign/i.test(app.name))
comparator = [Math.max, Math.max, Math.min, Math.min];
else
comparator = [Math.max, Math.min, Math.min, Math.max];
// sort a copy of array
var bounds = arrayOfBounds
.slice(0)
.sort(function (a, b) { return b[0] - a[0] || a[1] - b[1] });
// start with first bounds
var intersection = bounds.shift(),
b;
// compare each bounds, getting smaller
while (b = bounds.shift()) {
// if doesn't intersect, bail out
if (!boundsDoIntersect(intersection, b))
return;
intersection = [
comparator[0](intersection[0], b[0]),
comparator[1](intersection[1], b[1]),
comparator[2](intersection[2], b[2]),
comparator[3](intersection[3], b[3]),
];
}
return intersection;
};
/**
* Returns true if the two bounds intersect.
* @author m1b
* @version 2024-03-10
* @param {Array} bounds1 - bounds array.
* @param {Array} bounds2 - bounds array.
* @param {Boolean} [TLBR] - whether bounds arrays are interpreted as [t, l, b, r] or [l, t, r, b] (default: based on app).
* @returns {Boolean}
*/
function boundsDoIntersect(bounds1, bounds2, TLBR) {
if (undefined == TLBR)
TLBR = (/indesign/i.test(app.name));
return !(
TLBR
// TLBR
? (
bounds2[0] > bounds1[2]
|| bounds2[1] > bounds1[3]
|| bounds2[2] < bounds1[0]
|| bounds2[3] < bounds1[1]
)
// LTRB
: (
bounds2[0] > bounds1[2]
|| bounds2[1] < bounds1[3]
|| bounds2[2] < bounds1[0]
|| bounds2[3] > bounds1[1]
)
);
};
Edit 2025-02-25: added a check for a filled or stroked clipping mask.
... View more
‎Feb 22, 2025
08:34 PM
I have reported the bug. Please vote on it, if you can.
- Mark
... View more
‎Feb 22, 2025
08:27 PM
Hi all, I'm planning to post a bug report on uservoice, but I was hoping I could get a few people to test my demo document and see if they see the same bug.
To test:
1. open my attached .indd
2. place your insertion point cursor in the paragraph colored magenta.
3. type a character (what happens? for me the spanning breaks dramatically—see graphic below)
4. type another character (what happens? for me it goes back to normal
5. keep typing (for me it jumps back and forth)
I have noticed that if I change the magenta paragraph from using Adobe Paragraph Composer to Adobe Single-line Composer, the problem goes away. But it took me a while to troubleshoot it, so it would be nice to fix it.
Anyway, if you have time, please let me know your results, and which version of Indesign and OS you are running. I will link to this post in the bug report so Adobe can read your results too. Thanks.
- Mark
... View more
‎Feb 21, 2025
05:41 PM
3 Upvotes
Hi @Alexander Rott, here is an example of deriving the Lab values from RGB, CMYK, or Grayscale colors. You should be able to incorporate into your own script. This will be helpful: app.convertSampleColor(). Also here is another answer that uses this method.
- Mark
/**
* @file Show Lab Color Breakdown Demo.js
*
* Display the Lab color breakdown of the selected item's fillColor.
*
* @author m1b
* @version 2025-02-22
* @discussion https://community.adobe.com/t5/illustrator-discussions/using-javascript-to-get-lab-color-information/m-p/15169713
*/
(function () {
var doc = app.activeDocument,
item = doc.selection[0];
if (
undefined == item
|| !item.hasOwnProperty('fillColor')
|| 'NoColor' === item.fillColor.constructor.name
)
return alert('Please select an item with a fill color and try again.');
// we only what an array of the color channel values
var breakdown = getColorBreakdown(item.fillColor) || [];
// pick the colorspace based on the number of channels in the breakdown
var colorSpace = [
null,
ImageColorSpace.GrayScale,
null,
ImageColorSpace.RGB,
ImageColorSpace.CMYK,
][breakdown.length];
// this is what you are looking for!
var labValues = app.convertSampleColor(colorSpace, breakdown, ImageColorSpace.LAB, ColorConvertPurpose.defaultpurpose);
alert("L: " + labValues[0] + ", a: " + labValues[1] + ", b: " + labValues[2]);
})();
/**
* Returns an array of color channel values.
* @author m1b
* @version 2022-05-23
* @param {Swatch|SpotColor|Color} col - the source color.
* @param {Number} [tintFactor] - a number in range 0..1 (default: 1).
* @returns {Array<Number>}
*/
function getColorBreakdown(col, tintFactor) {
tintFactor = tintFactor || 1;
if (col.hasOwnProperty('color'))
col = col.color;
if ('SpotColor' === col.constructor.name)
col = col.spot.color;
if ('CMYKColor' === col.constructor.name)
return [col.cyan * tintFactor, col.magenta * tintFactor, col.yellow * tintFactor, col.black * tintFactor];
else if ('RGBColor' === col.constructor.name)
return [col.red * tintFactor, col.green * tintFactor, col.blue * tintFactor];
else if ('GrayColor' === col.constructor.name)
return [col.gray * tintFactor];
};
Edit 2025-02-22: added code to choose the correct source ImageColorSpace. Oops!
... View more
‎Feb 19, 2025
08:28 PM
Hi @Robert at ID-Tasker, yes it recurses. Here:
this.parent.create()
... View more
‎Feb 19, 2025
08:24 PM
Hi all, for what it's worth, I've created a bug report on uservoice. Please vote!
- Mark
... View more
‎Feb 19, 2025
03:32 PM
@Robert at ID-Tasker you are no village idiot!—you are an expert like each of us. I have made many more errors than you have on this one thread—some quite embarassing—so on that metric I would win the village idiot olympics here.
We could, as time permits, go back and look over our contributions on a thread such as this, and analyze the contribution we made. Did our comments push forward the discussion, or side-track it?
I find it uncomfortable work, as I see all my mistakes, but it can be edifying.
- Mark
... View more
‎Feb 19, 2025
06:10 AM
Hi @dublove, I am having trouble understanding this post. Would it be possible to post a BEFORE and AFTER document? Make it as simple as possible, but showing what you want.
- Mark
... View more
‎Feb 18, 2025
06:51 PM
Hi Robert, I try not to read between the lines, due to my often poor success rate.
> You're using JavaScript's RegEx on a text variable/string - not InDesign's GREP, that works on text objects.
Again, you are exactly correct!
But for your sake I will go into reading-between-the-lines territory and guess that you are concerned that because my example that uses a different flavour of grep (ExtendScript's RegExp vs Indesign's PCRE) my example will not be applicable. This is a legitimate concern, but rest assured, I knew what I was doing: the grep pattern I used in my example is 100% compatible with both flavours.
So why didn't I just use an actual Indesign grep example? I was lazy. Sorry.
But I will rectify my laziness now—
Here is the exact same example, converted into normal Indesign Find Change Grep context:
EDIT 2025-02-20: Do not bother reading this. Yes, this is a faithful conversion of my previous example, but it is a useless example—I could have used a normal capture group along with $1, $3 and $4, and it would have been fine. See my note on the earlier post. My apologies for wasting your time!
1. Using the non-capture group gives me what I want.
2. In this case using a normal capture group messes up the capture group indices and I get a mess.
Hope that helps.
- Mark
Edit: a couple of typos.
... View more
‎Feb 18, 2025
04:33 PM
Exactly @Robert at ID-Tasker! That line would be a poor choice to highlight.
- Mark
... View more
‎Feb 18, 2025
04:32 PM
Last comments/suggestions on your latest idml, @James Gifford—NitroPress: (apologies for wrong fonts in screen shots).
Paragraph Return and Carriage Return: I wondered it might be more readable to combine these, consistent with "End of Story".
1. If you did decide to remove the non-marking group, I would nominate the "negated character set" for its spot. They are really useful—let me know if you want examples.
2. the term "subexpression" in my opinion focuses on the wrong thing (I mean there are subexpressions elsewhere, eg. lookbehinds have subexpressions). I would suggest "Capture Group" which is a standard term.
3. Regarding "Reset Match": I would suggest the note "Discards any text already captured". Also it mustn't show a magenta box because there are no parameters for \K.
Your chart is a fantastic addition and I sincerely hope you aren't regretting sharing it here! 🙂
- Mark
... View more
‎Feb 18, 2025
03:38 PM
Edit 2025-02-20: Don't bother reading this example. Sorry to all, but something was nagging me and I realised that example I dredged up is a useless one. I mis-remembered the reason I had for using the non-capturing group: it was just for code-readability—I probably just liked the neatness of matching 1, 2, 3. I could have just used a normal capture group and accessed indexes 1 , 3, and 4 and it would have been fine I think. So please ignore this example—it is useless. Sorry!
@James Gifford—NitroPress
Funnily enough I had never considered that memory or speed were a reason to use non-marking groups (AKA non-capture groups), although both of those are good reasons.
I use them when I need to control the indexing of the captured text. My example is in a scripting context, and off the top of my head I don't remember ever using this in the Indesign UI but I don't see why it wouldn't apply when using group references in the changeTo field. [EDIT: yes it does apply perfectly—see my answer to Robert below for the same example converted into normal Indesign find/change grep.]
Here is a simple example:
function getJobNumber(doc) {
const matchJobNumber = /(^(?:([A-Z]{2})-?)?([-\d]+))_/;
var match = doc.name.match(matchJobNumber);
if (match && match.length > 1)
return {
fullCode: match[1],
countryCode: match[2],
numberCode: match[3],
};
};
The brief was that some document names started with job numbers in "UK-12345_" format, some had no hyphen "UK12345_" and some were just "12345_". I needed to collect (1) the full job number, (2) the countryCode by itself, and (3) the number.
(?:([A-Z]{2})-?)?
This part collects the country code. I needed to group it because this whole part is optional (the question mark at the end). But I never want to use its captured string because I only want the two letter country code without the possibly trailing hyphen.
Using a normal capture group (([A-Z]{2})-?)? would have misaligned the indices of the groups between the case where a country code existed and when it didn't. So using (?: ) keeps that optional grouping out of my results. The inner capture group ([A-Z]{2}) happily is still allocated its rightful index even if the outer non-capture group is not found (in which case the country code match[2] is an empty string).
Having said all that, I would judge that non-marking groups were well beyond the needs of the 99% users and if you needed space for something I would be tempted to leave it off your chart altogether.
- Mark
... View more
‎Feb 17, 2025
08:20 PM
Yes I thought so. I don't know how to do exactly what you want. There might be a clever way with Actions or graphic styles, but I don't know any. Sorry.
Good luck and let us know if you discover a good way!
- Mark
... View more
‎Feb 17, 2025
08:15 PM
@James Gifford—NitroPress
Peter wrote: > ... in a note you indicate that non-marking sub-expressions are obsolete. They aren't, they may not be used much, but they're certainly not obsolete.
Yes, definitely! Non-marking (AKA non-capturing) groups are very useful when needed.
James wrote: > Which, really, I think covers 99% of what InDesign GREP users would want to know in order to choose one over the other.
True, but it misses my point, which I can summarise in one sentence: Teaching \K as "Reset match" or "Discard capture" or "Clear capture" (or, for the mnemonic, "Klear capture"? or even—sigh—"Keep out" [Edit: or Peter's improvement: "keepbehind"?]) is no harder than teaching it as an "(Inclusive) Lookbehind*" and has the benefit that this description is real, not notional, and stands a chance to implant an accurate mental model in the student, that will withstand contact with the wider world.
Now I am imagining—for the sake of the exercise—a cohort of students leaving the academy and going out into the world to say things like "Oh, you'll need an Inclusive Lookbehind in that case." If that doesn't make the hair on your neck stand up, then, good for you—you are a normal healthly person! ‌🙂‌ - Mark
Edit: had to reformat because forum software was putting my reply into 5 columns! It's has clearly had enough of nitpicking!
... View more
‎Feb 17, 2025
06:41 PM
> I've come out of retirement for lesser things! @Peter Kahrel Haha, love it! I sometimes enjoy a bit of nitpicking when the mood takes me—and I am grateful for the indulgence here—but I promise to not make it a regular habit on other threads!
tldr;
I've realised that we simply don't agree on a point you made earlier, Peter, that
"Lookbehind is a functional notion, not a formal one."
I guess I would feel awkward referring to \K as a lookbehind in the same way a car mechanic might feel awkward in a discussion wherein "turbocharger" and "supercharger" where interchanged. These mechanisms might give very similar, high-level results, but are otherwise fundamentally different. And, importantly, those very slight high-level differences are 100% explained by the fundamental differences between them (eg, a turbocharger will have a small delay because it is driven by exhaust gases, while the supercharger is driven by the crankshaft and has no delay). The words "supercharger" and "turbocharger" are engineering terms, not day-to-day terms and in many contexts they could be used imprecisely with no problems. However, if I was teaching anybody in a related field—say, auto-electrician?—I would be more careful to use correct terminology.
This is why I, personally, would hesitate to describe \K as a lookbehind. But I can see why it is a perfectly reasonable thing to do in some circumstances. It would rarely be a big deal in the real world, and certainly isn't here on this thread. 🙂
And with that I will sign off, and thanks for indulging me. I had no idea my initial comment was going to provoke so much examination. It was fun. - Mark
___________________________ Further nitpicking and details, from your last reply. This is for the masochists only!
> because neither lookbehind captures what it's looking behind for
This claim is wrong. During the \K procedure, "apple" IS captured (or "matched", or "consumed"—let me know if the wording is the problem here) and when the \K activates, the captured contents is discarded—the bucket is emptied. During the positive lookbehind procedure "apple" is never captured at all. The grep engine is first looking to match the "1" of the regular expression. As it marches on it sees a "1" and then evaluates the "apple", probably backwards. Arrows show the difference. If you draw arrows showing the location of the "current character" under scrutiny by the state machine, in the \K example it marches left to right, with no backtracking, but in any lookbehind example it will see a match (but not yet capture) eg. the "1", then cast it's eye back to the left "e", "l", "p", "p", "a", evaluating the symbols in the lookbehind (?<=) and then actually capture the "1". It is a totally different process and here you can see why the latter is a lookbehind and the former is not.
> I don't think that 'discarded' is a useful term here
It was useful to me, writing the previous paragraph.
> When a search pattern is placed in parentheses the results are captured (and can be referred to using \1, \2, etc or $1, $2, etc).
You are forgetting the root capture group $0, which DID capture "apple" in the \K version (before it was discarded), but DIDN'T in the positive lookbehind version. But capture groups, per se, aren't relevent to my point.
> now do I begin to understand how you understand the difference: the \K lookbehind (in your example) looks for \D+ and when found, checks whether it's followed by \d+,
That is a strange way of describing the normal greap engine character-by-character matching behaviour which, yes, goes left-to-right matching as it goes. The \K just resets in the middle of this process. There is no lookbehind happening in the \K process—it is a simple forward-only process and will be very fast code. I'm sure that's why they implemented it.
A student in one school might ask: "I love this \K version of the positive lookbehind—it's so simple and fast. What is the negative lookbehind version?" At the same time a student in another school might never ponder what the opposite of "discard the captured content" is.
> Are you sure that that's how it works?
At the level of this discussion, yep!
> Coming back to your dialog, the teacher should have pointed out to the student that they could use ((?<=apple)|(?<=banana))\d+
Yes, that would be a great thing for the teacher to explain—no matter what anyone thinks about our nitpicking!
... View more
‎Feb 17, 2025
05:09 PM
@j.khakase Thanks for your sample files.
Unfortunately, the scripting API doesn't provide access to anything but the very most basic appearance, eg. a fill color and a stroke color. Because in your example file you use a more complex appearance (two fills and a live effect nested in a fill), a script won't really help here.
One solution would be to simplify the artwork—eg. split the artwork into a basic colored rectangle plus a point text frame. This would make a scripting approach feasible. See the attached demo file and try it with the script below. Note: this is just a demonstration—your exact needs will probably require something more specialized.
- Mark
Everytime you run script it will apply a random color from the same color group.
Can select multiple page items:
/**
* @file Change Fill Color By Color Group Random Swatch.js
*
* Experiment of applying swatch to the selected items,
* where the swatch is chosen at random from amongst the
* item's fill color's parent swatch group.
*
* @author m1b
* @discussion https://community.adobe.com/t5/illustrator-discussions/how-to-vary-hues-for-dynamic-shapes/m-p/15159237
*/
(function () {
var doc = app.activeDocument,
items = doc.selection;
if (0 === item.length)
return alert('Please select one or more page items and try again.');
for (var i = 0; i < items.length; i++)
applyRandomColorGroupColor(doc, items[i]);
})();
/**
* Apply a fill color to `item`, by choosing a random
* swatch color from `item`s current fill color's
* parent swatch group.
* @author m1b
* @version 2025-02-18
* @param {Document} doc - the item's Document.
* @param {PageItem} item - the item to color.
*/
function applyRandomColorGroupColor(doc, item) {
if (!item.hasOwnProperty('fillColor'))
return;
// get all the colors from the item's color's parent color group
var swatches = getColorGroupSwatchesForColor(doc, item.fillColor);
if (
!swatches
|| 0 === swatches.length
)
return alert('Could not find a color group for the selected item\'s color.')
// random swatch
item.fillColor = swatches[Math.floor(Math.random() * swatches.length)].color;
};
/**
* Returns all the swatches in the same color group as `targetColor`.
* @author m1b
* @version 2025-02-18
* @param {Document} doc - an Illustrator Document.
* @param {Color} targetColor - the target of the search.
* @returns {Swatches?}
*/
function getColorGroupSwatchesForColor(doc, targetColor) {
var swatchGroups = doc.swatchGroups;
for (var i = 0; i < swatchGroups.length; i++) {
var swatches = swatchGroups[i].getAllSwatches();
for (var j = 0; j < swatches.length; j++)
// compare stringified versions
if (stringify(swatches[j].color) === stringify(targetColor))
return swatches;
}
};
/**
* Simple stringify object, with
* special handling of Swatches.
* @author m1b
* @version 2023-04-26
* @param {Object} obj - the object to stringify.
* @returns {String}
*/
function stringify(obj) {
var str = obj.toString();
for (var key in obj) {
if (!obj.hasOwnProperty(key))
continue;
if (
key == 'spot'
|| key == 'color'
)
str += stringify(obj[key]);
else
str += obj[key];
}
return str;
};
... View more