Hi @psar12345, calculating the exact bounds of a page item is surprisingly involved. I wrote a function that makes a valiant attempt at doing it. You can see it working in the following demo script. The function `getItemBoundsIllustrator` returns [L, T, R, B]. Give it a try if you like.
- Mark
/**
* @file Get Item Bounds.js
*
* Demonstration of calculating the bounds
* of the selected item(s) and draws a
* box to show the bounds.
*
* @author m1b
* @version 2025-03-07
*/
(function () {
// enum to make the function call below more readable
var BoundsType = {
GEOMETRIC_BOUNDS: true,
VISIBLE_BOUNDS: false,
};
var doc = app.activeDocument;
if (0 === doc.selection.length)
return alert('Please select and item and try again.');
// calculate the bounds of the selection
var bounds = getItemBoundsIllustrator(doc.selection, BoundsType.VISIBLE_BOUNDS);
if (bounds)
// for demonstration purposes, draw a box to show bounds
var box = drawRectangle(doc, bounds, { opacity: 30, fillColor: makeColor([255, 50, 150]) });
})();
/**
* Draws a rectangle to the document.
* @param {Document|Layer|GroupItem} container - an Illustrator container.
* @param {Array<Number>} bounds - [T, L, B, R].
* @param {Object} props - properties to assign to the rectangle.
* @return {PathItem} - the rectangle.
*/
function drawRectangle(container, bounds, properties) {
properties = properties || {};
var rectangle = container.pathItems.rectangle(bounds[1], bounds[0], bounds[2] - bounds[0], -(bounds[3] - bounds[1])); // TLWH
// defaults
rectangle.filled = true;
rectangle.stroked = false;
// apply properties
for (var key in properties)
if (properties.hasOwnProperty(key))
rectangle[key] = properties[key];
return rectangle;
};
/**
* Return an RGB color.
* @param {Array<Number>} breakdown - the color breakdown.
* @returns {RGBColor}
*/
function makeColor(breakdown) {
var colr = new RGBColor();
colr.red = breakdown[0];
colr.green = breakdown[1];
colr.blue = breakdown[2];
return colr;
};
/**
* Returns bounds of item(s).
* @author m1b
* @version 2025-03-11
* @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 [L, T, R, B].
*/
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
)
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 2025-03-11
* @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];
if (!bounds)
continue;
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-03-11: added handling for empty clipping groups.