Skip to main content
Known Participant
August 27, 2023
Question

Resize obj based on the size of the clipping mask contained in it to match the size of another obj

  • August 27, 2023
  • 2 replies
  • 801 views

Hello everyone,

i have a script with a function that resizes and positions one object (`odjFit`) to fit within another object (`odjTo`). It calculates a scaling factor based on the dimensions of the two objects, resizes `odjFit` accordingly, and aligns their centers.

 

function fitObj(odjFit, odjTo) {
	var w1 = odjFit.width, h1 = odjFit.height, w2 = odjTo.width, h2 = odjTo.height;
	var r = Math.min(w2/w1, h2/h1);
	odjFit.resize(r*100,r*100);
	var x1 = odjFit.left + odjFit.width/2, y1 = odjFit.top, x2 = odjTo.left + odjTo.width/2, y2 = odjTo.top;
	odjFit.translate(x2 - x1, y2 - y1);
};

 

the problem is that the object odjFit contains a clipping mask with objects way bigger than the mask itself so the function calculates w1and h1 considering these objects too. I want it to calculates w1 and h1 based only on the width and height of the object odjFit itself (which is the same as the dimensions of the clipping mask). Then the objects inside the clipping mask should be resized accordingly.

I hope i made myself clear.

Thanks for the help.

Cheers.

2 replies

Participant
December 13, 2024

it looks there is a simplier way to calculate this by using fit artboard to select art

//assuming the clipping group is selected
var artbds = app.activeDocument.artboards;
var activeBoardIndex = artbds.getActiveArtboardIndex();
var activeArtboard = app.activeDocument.artboards[activeBoardIndex];
var storedRect = activeArtboard.artboardRect;
app.activeDocument.fitArtboardToSelectedArt(activeBoardIndex);
var newRect = activeArtboard.artboardRect;
var w = newRect[2] - newRect[0];
var h = newRect[1] - newRect[3];
activeArtboard.artboardRect = storedRect;
alert("clipping width:" + w + "," + "clipping height:" + h);

 

m1b
Community Expert
Community Expert
February 28, 2025

That's a clever idea @John37830937iqgg, I'll give that a try and see if it covers the edge cases. It might be a winner!

- Mark

m1b
Community Expert
Community Expert
February 28, 2025

I tried it out @John37830937iqgg, but it seems to return the size of the clipping path, not the visual part of the clipping group. 😞

m1b
Community Expert
Community Expert
August 28, 2023

Hi @Delresto, I made a function "getItemBounds" that tries hard to get an item's bounds. See if it works in for you and let me know if you find bugs. - Mark

 

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

        // get bounds of outlined text
        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.
 * NOTE: Returns undefined if ANY
 * rectangles do not intersect.
 * @author m1b
 * @version 2022-07-24
 * @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]
        );

};

 

DelrestoAuthor
Known Participant
August 28, 2023

Hi Mark and thank for the help.

I merged your codes with mine in this way:

/**
 * Returns bounds of item(s).
 * Attempts to get correct bounds
 * of clipped groups.
 * @7111211 m1b
 * @version 2022-10-28
 * @9397041 {PageItem|Array<PageItem>} item - an Illustrator PageItem or array of PageItems.
 * @9397041 {Boolean} [geometric] - if false, returns visible bounds.
 * @9397041 {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') {

        // get bounds of outlined text
        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.
 * @7111211 m1b
 * @version 2022-07-24
 * @9397041 {Array} b1 - bounds array [l, t, r, b].
 * @9397041 {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.
 * NOTE: Returns undefined if ANY
 * rectangles do not intersect.
 * @7111211 m1b
 * @version 2022-07-24
 * @9397041 {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.
 * @7111211 m1b
 * @version 2022-10-04
 * @9397041 {Array} bounds1 - bounds array [l, t, r, b].
 * @9397041 {Array} bounds2 - bounds array [l, t, r, b].
 * @9397041 {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]
        );

};


function fitObj(odjFit, odjTo) {
   
    var odjFitBounds = getItemBounds(odjFit, true, []);

    var w1 = odjFitBounds[2] - odjFitBounds[0];
    var h1 = odjFitBounds[1] - odjFitBounds[3];
    var w2 = odjTo.width;
    var h2 = odjTo.height;

    var r = Math.min(w2 / w1, h2 / h1);

   
    odjFit.resize(r * 100, r * 100);

 
    for (var i = 0; i < odjFit.pageItems.length; i++) {
        var subItem = odjFit.pageItems[i];
        if (subItem.typename === "GroupItem" || subItem.typename === "CompoundPathItem") {
            fitObj(subItem, odjTo);
        } else {
            subItem.resize(r * 100, r * 100);
        }
    }
    

};

 

It gives me error at the line

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

It says "undefined is not an object".

 

Just to clarify, inside the group object "odjFit" there are different elements such as paths, groups, texts, clipping masks... all kind of objects actually. Could it be the reasons of the error?

 

Thanks!

m1b
Community Expert
Community Expert
August 29, 2023

This error is because you are asking for pageItems of odjFit, but odjFit doesn't have any. You can check for this error,  earlier in the code,  like this:

if (!odjFit.hasOwnProperty('pageItems')) {
    alert ('No page items.');
    return;
}

 - Mark