• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers
Exit
0

Creating a rectangle around multiple objects with a script

Community Beginner ,
Oct 27, 2022 Oct 27, 2022

Copy link to clipboard

Copied

Hi folks,

 

I'm struggling a bit with a script. Here in the forum I saw a script which creates a rectangle around a single object with a certain offset and so on. This script doesn't work unfortunately, once you select multiple objects and want to create a rectangle along those.

 

My workflow by hand currently looks like this. I'm having those objects on the artboard, then I'm creating a rectangle exacly along the borders of all objects (basically recreating the bounding-box with an actual rectangle if you'd like to say so) and then I'm giving the newly created rectangle a black contour with 0.25pts and finally I'm ofsetting the path by 1mm and delete the inner rectangle.

 

So basically starting point on the left, and the end result to the right when I'm doing it by hand.

Script Demo.jpg

 

Pretty time consuming and tedious to do, especially because I find it really hard to place the initial rectangle on the first try, without the need to gently fondle it into the right place afterwards.

 

So heres the script I find which would possibly do the work for me, if it was for only one object:

 

function makeRect() {

if (selection.length == 0) {
    alert("No object …");
    return;
}else{
    if (selection.length > 1) {
        alert("Too many objects …");
        return;
    }else{
        var myDoc = app.activeDocument;
        var Sel = myDoc.selection
        var SelVB = Sel[0].visibleBounds;
        var dMarg = 28.3464567;
        var Links = SelVB[0];
        var Oben = SelVB[1];
        var SelBreite = (SelVB[2] - SelVB[0]);
        var SelHoehe = (SelVB[1] - SelVB[3]);
        var newCMYK = new CMYKColor();
        newCMYK.cyan = 100;
        newCMYK.magenta = 0;
        newCMYK.yellow = 0;
        newCMYK.black = 0;
        var myGroup =myDoc.groupItems.add();
        Sel[0].move(myGroup, ElementPlacement.PLACEATBEGINNING);
        var MargBox = myDoc.pathItems.rectangle(Oben+dMarg, Links-dMarg, SelBreite+2*dMarg, SelHoehe+2*dMarg);
        MargBox.stroked = true;
        MargBox.strokeWidth = 10;
        //MargBox.filled = false;
        MargBox.fillColor = newCMYK;
        MargBox.move(myGroup, ElementPlacement.PLACEATEND);
        }
    }
}

makeRect();

The script makes a rectangle, with stroke of 10, cyan fill and an offset of... 28,something - whatever that exactly means - is it px?

However, this doesn't work when it comes down to multiple objects that need to be rectangle-outlined. I mean there's obviously right at the beginning an alert if you select more than one object.

 

So, could someone help me out on this one? You'd really make my day.

 

Best regards,

Thomas

TOPICS
Scripting

Views

561

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines

correct answers 1 Correct answer

Community Expert , Oct 27, 2022 Oct 27, 2022

Hi @TW-22341, I spent a lot of time some while ago trying to work out a good way to get an item's bounds, even if it was clipped or whatever, and I made a function that has a good try at doing so, although it won't be perfect in strange case, eg. where effects like drop shadows are added.

I've thrown together a quick script that uses those functions I already wrote and I think it should do what you ask for. Let me know how it goes for you. You can adjust the "settings" object.

- Mark

 

Edit 1: i

...

Votes

Translate

Translate
Adobe
Community Expert ,
Oct 27, 2022 Oct 27, 2022

Copy link to clipboard

Copied

Not a script solution, but you can achieve what you've been doing by hand by grouping the objects, adding a stroke to the group and using Effect > Convert to Shape > Rectangle on the stroke:

DougARoberts_0-1666862526565.png

You could save that as a graphic style.

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Oct 27, 2022 Oct 27, 2022

Copy link to clipboard

Copied

Actually yes, if it was only for the optical effect. But I do need an actual path for anything that comes after this step 🙂

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Oct 27, 2022 Oct 27, 2022

Copy link to clipboard

Copied

Object > Expand Appearance

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
Oct 28, 2022 Oct 28, 2022

Copy link to clipboard

Copied

Perfectly match to my requirement, Thanks lot :), you make my day

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Oct 27, 2022 Oct 27, 2022

Copy link to clipboard

Copied

Hi @TW-22341, I spent a lot of time some while ago trying to work out a good way to get an item's bounds, even if it was clipped or whatever, and I made a function that has a good try at doing so, although it won't be perfect in strange case, eg. where effects like drop shadows are added.

I've thrown together a quick script that uses those functions I already wrote and I think it should do what you ask for. Let me know how it goes for you. You can adjust the "settings" object.

- Mark

 

Edit 1: improved handling of text.

Edit 2: simplified code, changed getItemBounds to handle multiple items as parameter, moved rect behind objects as per OP's code.

 

 

 

(function () {

    var mm = 2.834645;

    // the colors of the rectangle
    var black = new GrayColor(),
        cyan = new CMYKColor();

    black.gray = 100
    cyan.cyan = 100;
    cyan.magenta = 0;
    cyan.yellow = 0;
    cyan.black = 0;;

    // user settings:
    var settings = {

        margin: 1 * mm,
        strokeColor: black,
        strokeWidth: 1

    };

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

    if (items.length == 0) {
        alert('Please make a selection and try again.');
        return;
    }

    var lastItem = items[items.length - 1],
        bounds = getItemBounds(items);

    if (bounds == undefined)
        return;

    // add margin
    bounds[0] -= settings.margin;
    bounds[1] += settings.margin;
    bounds[2] += settings.margin;
    bounds[3] -= settings.margin;

    // draw the rectangle
    var rect = doc.pathItems.rectangle(bounds[1], bounds[0], bounds[2] - bounds[0], bounds[1] - bounds[3]);
    rect.filled = true;
    rect.fillColor = cyan;
    rect.stroked = true;
    rect.strokeWidth = settings.strokeWidth;
    rect.strokeColor = settings.strokeColor;

    // put the rectangle behind all the items
    rect.move(lastItem, ElementPlacement.PLACEAFTER);

    // leave just the rectangle selected
    app.selection = null;
    rect.selected = true;

    // finished


    /**
     * Returns bounds of item.
     * @author m1b
     * @version 2022-10-28
     * Attempts to get correct bounds
     * of clipped groups.
     * @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 getItemBounds(item, geometric, bounds) {

        var newBounds = [];

        if (
            item.typename == 'GroupItem'
            || item.constructor.name == 'Array'
        ) {

            var children = item.typename == 'GroupItem' ? item.pageItems : item,
                contentBounds = [];

            if (
                item.hasOwnProperty('clipped')
                && item.clipped == true
            ) {

                // item is clipping group
                var clipBounds;
                for (var i = 0; i < children.length; i++) {

                    var child = children[i];
                    if (
                        child.hasOwnProperty('clipping')
                        && child.clipping == true
                    )
                        // the clipping item
                        clipBounds = child.geometricBounds;

                    else
                        // a clipped content item
                        var b = expandBounds(getItemBounds(child, geometric, bounds), contentBounds);

                }
                newBounds = intersectionOfBounds([clipBounds, contentBounds]);

            }

            else {

                // item is a normal group
                for (var i = 0; i < children.length; i++) {
                    var child = children[i];
                    var b = expandBounds(getItemBounds(child, geometric, bounds), contentBounds);
                }

                newBounds = contentBounds;

            }

        }

        else if (item.typename == 'TextFrame') {

            // item is a text frame
            var dup = item.duplicate().createOutline();
            newBounds = geometric ? dup.geometricBounds : dup.visibleBounds;
            dup.remove();

        }

        else {

            // item is not clipping group
            newBounds = geometric ? item.geometricBounds : item.visibleBounds;

        }

        bounds = (bounds == undefined)
            ? bounds = newBounds
            : bounds = expandBounds(newBounds, bounds);

        return bounds;

    };


    /**
     * Returns bounds that encompass both bounds.
     * @author m1b
     * @version 2022-07-24
     **
     * @param {Array} b1 - bounds array [l, t, r, b].
     * @param {Array} b2 - bounds array [l, t, r, b].
     * @returns {Array} - the encompassing bounds.
     */
    function expandBounds(b1, b2) {

        var expanded = b2;

        for (var i = 0; i < 4; i++) {
            if (b1[i] != undefined && b2[i] == undefined) expanded[i] = b1[i];
            if (b1[i] == undefined && b2[i] != undefined) expanded[i] = b2[i];
            if (b1[i] == undefined && b2[i] == undefined) return;
        }

        if (b1[0] < b2[0]) expanded[0] = b1[0];
        if (b1[1] > b2[1]) expanded[1] = b1[1];
        if (b1[2] > b2[2]) expanded[2] = b1[2];
        if (b1[3] < b2[3]) expanded[3] = b1[3];

        return expanded;

    };


    /**
     * Returns the overlapping rectangle
     * of two or more rectangles.
     * @author m1b
     * @version 2022-07-24
     * NOTE: Returns undefined if ANY
     * rectangles do not intersect.
     * @param {Array} arrayOfBounds - an array of bounds arrays [l, t, r, b].
     * @returns {Array} - bounds array [l, t, r, b] of overlap.
     */
    function intersectionOfBounds(arrayOfBounds) {

        // 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;

            var l = Math.max(intersection[0], b[0]),
                t = Math.min(intersection[1], b[1]),
                r = Math.min(intersection[2], b[2]),
                b = Math.max(intersection[3], b[3]);

            intersection = [l, t, r, b];

        }

        return intersection;

    };




    /**
     * Returns true if the two bounds intersect.
     * @author m1b
     * @version 2022-10-04
     * @param {Array} bounds1 - bounds array [l, t, r, b].
     * @param {Array} bounds2 - bounds array [l, t, r, b].
     * @param {Boolean} TLBR - whether bounds arrays are interpreted as [t, l, b, r].
     * @returns {Boolean}
     */
    function boundsDoIntersect(bounds1, bounds2, TLBR) {

        if (TLBR === true)
            // TLBR
            return !(
                bounds2[0] > bounds1[2]
                || bounds2[1] > bounds1[3]
                || bounds2[2] < bounds1[0]
                || bounds2[3] < bounds1[1]
            );

        else
            // LTRB
            return !(
                bounds2[0] > bounds1[2]
                || bounds2[1] < bounds1[3]
                || bounds2[2] < bounds1[0]
                || bounds2[3] > bounds1[1]
            );

    };


})();

 

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Oct 27, 2022 Oct 27, 2022

Copy link to clipboard

Copied

Thank you, works like a charm up to almost 99%. I found a strange behaviour I didn't expect and can't find out where the script is getting distracted. As long as its basic Shapes it's working as intended. But as soon as there is text along a path it get's distracted and doesn't offset by 1mm as programmed. The example to the right shows it. As you can see in comparison to the actual bounding-box it even offset the rectangle unevenly. Buy overlooking the script I can't see where this might occur. It just seems to be text on the circle there. If I delete that and just put text inside the circle the offset is correctly executed. You got any idea why it is behaving like this?

circle-script-problem.jpg

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Oct 27, 2022 Oct 27, 2022

Copy link to clipboard

Copied

Okay the problem is that the bounds of textFrames include some "invisible" space, eg. between the cap height and the top of the em square. Same at the bottom, between the descender and the bottom of the em square. I've added a potential fix: to duplicate and outline and get the bounds of that. See what you think. I've updated the script above. - Mark

Screenshot 2022-10-28 at 07.48.12.png

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Oct 28, 2022 Oct 28, 2022

Copy link to clipboard

Copied

This version seems to work flawlessly - very impressive. Ghank you for your help. I just adjusted the settings a bit, since I need a white stroke with 0.25 and no fill. Heres my result:

 

 

(function () {

    var mm = 2.834645;

    // the colors of the rectangle
    var black = new GrayColor(),
        cyan = new CMYKColor();
		white = new CMYKColor();

	black.gray = 100
	white.cyan = 0;
	white.magenta = 0;
	white.yellow = 0;
	white.black = 0;
    cyan.cyan = 100;
    cyan.magenta = 0;
    cyan.yellow = 0;
    cyan.black = 0;;

    // user settings:
    var settings = {

        margin: 1 * mm,
        strokeColor: white,
        strokeWidth: 0.25

    };

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

    if (items.length == 0) {
        alert('Please make a selection and try again.');
        return;
    }

    var lastItem = items[items.length - 1],
        bounds = getItemBounds(items);

    if (bounds == undefined)
        return;

    // add margin
    bounds[0] -= settings.margin;
    bounds[1] += settings.margin;
    bounds[2] += settings.margin;
    bounds[3] -= settings.margin;

    // draw the rectangle
    var rect = doc.pathItems.rectangle(bounds[1], bounds[0], bounds[2] - bounds[0], bounds[1] - bounds[3]);
    rect.filled = false;
    //rect.fillColor = cyan;
    rect.stroked = true;
    rect.strokeWidth = settings.strokeWidth;
    rect.strokeColor = settings.strokeColor;

    // put the rectangle behind all the items
    rect.move(lastItem, ElementPlacement.PLACEAFTER);

    // leave just the rectangle selected
    app.selection = null;
    rect.selected = true;

    // finished


    /**
     * Returns bounds of item.
     * @author m1b
     * @version 2022-10-28
     * Attempts to get correct bounds
     * of clipped groups.
     * @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 getItemBounds(item, geometric, bounds) {

        var newBounds = [];

        if (
            item.typename == 'GroupItem'
            || item.constructor.name == 'Array'
        ) {

            var children = item.typename == 'GroupItem' ? item.pageItems : item,
                contentBounds = [];

            if (
                item.hasOwnProperty('clipped')
                && item.clipped == true
            ) {

                // item is clipping group
                var clipBounds;
                for (var i = 0; i < children.length; i++) {

                    var child = children[i];
                    if (
                        child.hasOwnProperty('clipping')
                        && child.clipping == true
                    )
                        // the clipping item
                        clipBounds = child.geometricBounds;

                    else
                        // a clipped content item
                        var b = expandBounds(getItemBounds(child, geometric, bounds), contentBounds);

                }
                newBounds = intersectionOfBounds([clipBounds, contentBounds]);

            }

            else {

                // item is a normal group
                for (var i = 0; i < children.length; i++) {
                    var child = children[i];
                    var b = expandBounds(getItemBounds(child, geometric, bounds), contentBounds);
                }

                newBounds = contentBounds;

            }

        }

        else if (item.typename == 'TextFrame') {

            // item is a text frame
            var dup = item.duplicate().createOutline();
            newBounds = geometric ? dup.geometricBounds : dup.visibleBounds;
            dup.remove();

        }

        else {

            // item is not clipping group
            newBounds = geometric ? item.geometricBounds : item.visibleBounds;

        }

        bounds = (bounds == undefined)
            ? bounds = newBounds
            : bounds = expandBounds(newBounds, bounds);

        return bounds;

    };


    /**
     * Returns bounds that encompass both bounds.
     * @author m1b
     * @version 2022-07-24
     **
     * @Param {Array} b1 - bounds array [l, t, r, b].
     * @Param {Array} b2 - bounds array [l, t, r, b].
     * @Returns {Array} - the encompassing bounds.
     */
    function expandBounds(b1, b2) {

        var expanded = b2;

        for (var i = 0; i < 4; i++) {
            if (b1[i] != undefined && b2[i] == undefined) expanded[i] = b1[i];
            if (b1[i] == undefined && b2[i] != undefined) expanded[i] = b2[i];
            if (b1[i] == undefined && b2[i] == undefined) return;
        }

        if (b1[0] < b2[0]) expanded[0] = b1[0];
        if (b1[1] > b2[1]) expanded[1] = b1[1];
        if (b1[2] > b2[2]) expanded[2] = b1[2];
        if (b1[3] < b2[3]) expanded[3] = b1[3];

        return expanded;

    };


    /**
     * Returns the overlapping rectangle
     * of two or more rectangles.
     * @author m1b
     * @version 2022-07-24
     * NOTE: Returns undefined if ANY
     * rectangles do not intersect.
     * @Param {Array} arrayOfBounds - an array of bounds arrays [l, t, r, b].
     * @Returns {Array} - bounds array [l, t, r, b] of overlap.
     */
    function intersectionOfBounds(arrayOfBounds) {

        // 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;

            var l = Math.max(intersection[0], b[0]),
                t = Math.min(intersection[1], b[1]),
                r = Math.min(intersection[2], b[2]),
                b = Math.max(intersection[3], b[3]);

            intersection = [l, t, r, b];

        }

        return intersection;

    };

    /**
     * Returns true if the two bounds intersect.
     * @author m1b
     * @version 2022-10-04
     * @Param {Array} bounds1 - bounds array [l, t, r, b].
     * @Param {Array} bounds2 - bounds array [l, t, r, b].
     * @Param {Boolean} TLBR - whether bounds arrays are interpreted as [t, l, b, r].
     * @Returns {Boolean}
     */
    function boundsDoIntersect(bounds1, bounds2, TLBR) {

        if (TLBR === true)
            // TLBR
            return !(
                bounds2[0] > bounds1[2]
                || bounds2[1] > bounds1[3]
                || bounds2[2] < bounds1[0]
                || bounds2[3] < bounds1[1]
            );

        else
            // LTRB
            return !(
                bounds2[0] > bounds1[2]
                || bounds2[1] < bounds1[3]
                || bounds2[2] < bounds1[0]
                || bounds2[3] > bounds1[1]
            );

    };


})();

 

 

So I basically added white as CMYK color, couldn't find an easier way, but thats ok I guess, set the stroke Color to the newly defined white, changed rect.filled to false and out-commented the rect.fillColor. The result is now just as I wanted it. If you got any further improvements or if I did something wrong with my changes, feel free to correct me. Unfortunately I'm only able to read and understand code, but writing it myself is a thing I haven't managed to truly learn for years... But right now I'm pretty happy that you helped me out on this one, so I don't have to do this dull task over and over again by hand 🙂 You really saved my day, thank you very, very much!

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Oct 28, 2022 Oct 28, 2022

Copy link to clipboard

Copied

You're welcome! The changes you made look good to me.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
Oct 28, 2022 Oct 28, 2022

Copy link to clipboard

Copied

I am looking similer script, I want to add rectangle around each object group indivisually at once, based on defined margin through the prompt dialog. is that possible?

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
Oct 28, 2022 Oct 28, 2022

Copy link to clipboard

Copied

Thank you Doug A Roberts

I got what I was looking

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Oct 28, 2022 Oct 28, 2022

Copy link to clipboard

Copied

Here's a modified version that asks for margin value and puts rectangle behind each item. - Mark

/**
 * Draws bounding rectangle behind each item.
 * @discussion https://community.adobe.com/t5/illustrator-discussions/creating-a-rectangle-around-multiple-objects-with-a-script/m-p/13300468
 */

(function () {

    // the colors of the rectangle
    var black = new GrayColor(),
        cyan = new CMYKColor();

    black.gray = 100
    cyan.cyan = 100;
    cyan.magenta = 0;
    cyan.yellow = 0;
    cyan.black = 0;

    var defaultMargin = '1 mm',
        margin = prompt('Draw Rectangles Around Items\nEnter Margin:', defaultMargin);

    if (margin == undefined)
        return;

    // user settings:
    var settings = {

        margin: stringToPoints(margin) || stringToPoints(defaultMargin),
        strokeColor: black,
        strokeWidth: 1

    };

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

    if (items.length == 0) {
        alert('Please make a selection and try again.');
        return;
    }

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

        var bounds = getItemBounds(items[i]);

        if (bounds == undefined)
            return;

        // add margin
        bounds[0] -= settings.margin;
        bounds[1] += settings.margin;
        bounds[2] += settings.margin;
        bounds[3] -= settings.margin;

        // draw the rectangle
        var rect = doc.pathItems.rectangle(bounds[1], bounds[0], bounds[2] - bounds[0], bounds[1] - bounds[3]);
        rect.filled = true;
        rect.fillColor = cyan;
        rect.stroked = true;
        rect.strokeWidth = settings.strokeWidth;
        rect.strokeColor = settings.strokeColor;

        // put the rectangle behind all the items
        rect.move(items[i], ElementPlacement.PLACEAFTER);
    }

    // leave just the rectangle selected
    app.selection = null;
    rect.selected = true;

    // finished

    /**
     * Converts a string measurement value,
     * to a value in points, for example:
     *   '1.5 mm'  --> 4.2519675.
     * @author m1b
     * @version 2022-10-29
     * @Param {String} str - the string to convert, eg. '1mm'.
     * @Returns {Number} - the value in points.
     */
    function stringToPoints(str) {

        var n;

        if (/\s*[\d\.]+\s*(mm|millimeters)/.test(str))
            n = parseFloat(str) * 2.834645;
        else if (/\s*[\d\.]+\s*(cm|centimeters)/.test(str))
            n = parseFloat(str) * mm * 28.34645;
        else if (/\s*[\d\.]+\s*("|in)/.test(str))
            n = parseFloat(str) * 72;
        else
            n = parseFloat(str);
        if (n === n)
            return n;

    };


    /**
     * Returns bounds of item(s).
     * @author m1b
     * @version 2022-10-28
     * Attempts to get correct bounds
     * of clipped groups.
     * @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 getItemBounds(item, geometric, bounds) {

        var newBounds = [];

        if (
            item.typename == 'GroupItem'
            || item.constructor.name == 'Array'
        ) {

            var children = item.typename == 'GroupItem' ? item.pageItems : item,
                contentBounds = [];

            if (
                item.hasOwnProperty('clipped')
                && item.clipped == true
            ) {

                // item is clipping group
                var clipBounds;
                for (var i = 0; i < children.length; i++) {

                    var child = children[i];
                    if (
                        child.hasOwnProperty('clipping')
                        && child.clipping == true
                    )
                        // the clipping item
                        clipBounds = child.geometricBounds;

                    else
                        // a clipped content item
                        var b = expandBounds(getItemBounds(child, geometric, bounds), contentBounds);

                }
                newBounds = intersectionOfBounds([clipBounds, contentBounds]);

            }

            else {

                // item is a normal group
                for (var i = 0; i < children.length; i++) {
                    var child = children[i];
                    var b = expandBounds(getItemBounds(child, geometric, bounds), contentBounds);
                }

                newBounds = contentBounds;

            }

        }

        else if (item.typename == 'TextFrame') {

            // item is a text frame
            var dup = item.duplicate().createOutline();
            newBounds = geometric ? dup.geometricBounds : dup.visibleBounds;
            dup.remove();

        }

        else {

            // item is not clipping group
            newBounds = geometric ? item.geometricBounds : item.visibleBounds;

        }

        bounds = (bounds == undefined)
            ? bounds = newBounds
            : bounds = expandBounds(newBounds, bounds);

        return bounds;

    };


    /**
     * Returns bounds that encompass both bounds.
     * @author m1b
     * @version 2022-07-24
     **
     * @Param {Array} b1 - bounds array [l, t, r, b].
     * @Param {Array} b2 - bounds array [l, t, r, b].
     * @Returns {Array} - the encompassing bounds.
     */
    function expandBounds(b1, b2) {

        var expanded = b2;

        for (var i = 0; i < 4; i++) {
            if (b1[i] != undefined && b2[i] == undefined) expanded[i] = b1[i];
            if (b1[i] == undefined && b2[i] != undefined) expanded[i] = b2[i];
            if (b1[i] == undefined && b2[i] == undefined) return;
        }

        if (b1[0] < b2[0]) expanded[0] = b1[0];
        if (b1[1] > b2[1]) expanded[1] = b1[1];
        if (b1[2] > b2[2]) expanded[2] = b1[2];
        if (b1[3] < b2[3]) expanded[3] = b1[3];

        return expanded;

    };


    /**
     * Returns the overlapping rectangle
     * of two or more rectangles.
     * @author m1b
     * @version 2022-07-24
     * NOTE: Returns undefined if ANY
     * rectangles do not intersect.
     * @Param {Array} arrayOfBounds - an array of bounds arrays [l, t, r, b].
     * @Returns {Array} - bounds array [l, t, r, b] of overlap.
     */
    function intersectionOfBounds(arrayOfBounds) {

        // 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;

            var l = Math.max(intersection[0], b[0]),
                t = Math.min(intersection[1], b[1]),
                r = Math.min(intersection[2], b[2]),
                b = Math.max(intersection[3], b[3]);

            intersection = [l, t, r, b];

        }

        return intersection;

    };




    /**
     * Returns true if the two bounds intersect.
     * @author m1b
     * @version 2022-10-04
     * @Param {Array} bounds1 - bounds array [l, t, r, b].
     * @Param {Array} bounds2 - bounds array [l, t, r, b].
     * @Param {Boolean} TLBR - whether bounds arrays are interpreted as [t, l, b, r].
     * @Returns {Boolean}
     */
    function boundsDoIntersect(bounds1, bounds2, TLBR) {

        if (TLBR === true)
            // TLBR
            return !(
                bounds2[0] > bounds1[2]
                || bounds2[1] > bounds1[3]
                || bounds2[2] < bounds1[0]
                || bounds2[3] < bounds1[1]
            );

        else
            // LTRB
            return !(
                bounds2[0] > bounds1[2]
                || bounds2[1] < bounds1[3]
                || bounds2[2] < bounds1[0]
                || bounds2[3] > bounds1[1]
            );

    };


})();

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Oct 29, 2022 Oct 29, 2022

Copy link to clipboard

Copied

LATEST

Another scripting approach with a pretty versatile dialog would be this one, provided by Sergey Anosov. It still works in recent Illustrator versions.

 

Make Rectangle

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines