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

[ExtendScript] Get bounds of selected text

Community Expert ,
Jun 01, 2023 Jun 01, 2023

Copy link to clipboard

Copied

Hi all, I've been asked (by @danezeq) in the Indesign forum to convert a script I wrote for returning the bounds of selected text to an Illustrator version.

 

Examples:

Screenshot 2023-06-02 at 11.14.09.png

The rectangles are just for demonstration purposes—you can do whatever you like with the bounds.

 

Here is the script. It will need some testing, so let me know if you find bugs.

- Mark

 

/**
 * Draw rectangle showing selected text's bounds.
 * @author m1b
 * @discussion https://community.adobe.com/t5/illustrator-discussions/how-to-draw-a-graphic-on-hidden-character-in-illustrator/m-p/13833618
 */
(function () {

    var doc = app.activeDocument,
        text = doc.selection,
        bounds = getTextOutlineBounds(text);

    if (bounds == undefined) {
        alert('Could not calculate bounds. Please select some text and try again.');
        return;
    }

    // draw a rectangle just for showing the bounds
    var rect = doc.pathItems.rectangle(bounds[1], bounds[0], bounds[2] - bounds[0], -(bounds[3] - bounds[1])); // TLWH
    rect.fillColor = doc.swatches[4].color;
    rect.opacity = 30;

})();


/**
 * Returns bounds of Text object [L, T, R, B].
 * @author m1b
 * @version 2023-06-02
 * @param {Text|Paragraph|Line|Word|Character} text - an Indesign text object.
 * @returns {Array<Number>} - the bounds [T, L, B, R].
 */
function getTextOutlineBounds(text) {

    var bounds;

    if (text.constructor.name == 'Array') {

        // process each element of array
        for (var i = 0; i < text.length; i++)
            bounds = expandBoundsForIllustrator(bounds, getTextOutlineBounds(text[i]));

        return bounds;

    }

    else if (text.constructor.name == 'TextFrame') {
        // process the text of the text frame
        return getTextOutlineBounds(text.textRange);
    }

    else if (
        !text.hasOwnProperty('characters')
        || text.characters.length == 0
        || !text.hasOwnProperty('fillColor')
    )
        // can't work with this
        return;

    var tf = text.parent.textFrames[0],
        arbitraryValue = 61.803;

    // we'll paint the text with this
    // color so that when it's outlined,
    // we can differentiate it
    var markerColor = new CMYKColor();
    markerColor.cyan = arbitraryValue;
    markerColor.magenta = arbitraryValue;
    markerColor.yellow = arbitraryValue;
    markerColor.black = arbitraryValue;

    var dup = tf.duplicate(),
        start = text.start,
        end = text.end;

    // mark the selected characters
    for (var i = start; i < end; i++)
        dup.characters[i].fillColor = markerColor;

    // a stringified version for matching purposes
    var matchMarker = stringify(dup.characters[start].fillColor);

    // the outlines are always compoundPathItems
    var outlines = dup.createOutline().compoundPathItems;

    // find the marked outlines
    for (var i = 0; i < outlines.length; i++)
        if (stringify(outlines[i].pathItems[0].fillColor) == matchMarker)
            bounds = expandBoundsForIllustrator(bounds, outlines[i].visibleBounds);

    // delete temporary items
    for (var i = outlines.length - 1; i >= 0; i--)
        outlines[i].remove();

    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 expandBoundsForIllustrator(b1, b2) {

    if (!b1 && !b2)
        return;

    if (!b1 && b2)
        return b2.slice();

    if (!b2 && b1)
        return b1.slice();

    var expanded = b2.slice();

    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;

};


/**
 * Stringify tailored for the purpose
 * of identifying identical 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;

};

 

TOPICS
Scripting , Type

Views

507

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

Enthusiast , Jun 04, 2023 Jun 04, 2023

I added to Mark's code 1) the exact color for the rectangle instead of the unpredictable swatch index, 2) get selected text layer to create the rectangle, and 3) the isSelectRect boolean key to select the rectangle after the script runs.

 

 

 

 

/**
 * Draw rectangle showing selected text's bounds.
 *  m1b
 * @discussion https://community.adobe.com/t5/illustrator-discussions/how-to-draw-a-graphic-on-hidden-character-in-illustrator/m-p/13833618
 */
(function () {

    var isSelectRect = true,
        
...

Votes

Translate

Translate
Adobe
Enthusiast ,
Jun 04, 2023 Jun 04, 2023

Copy link to clipboard

Copied

Hi Mark, interesting technical script. I did some quick tests. No bugs found, the script draws rectangles on the selected text.

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 ,
Jun 04, 2023 Jun 04, 2023

Copy link to clipboard

Copied

@m1b -

Hi Mark,

Nice script..  👍 

No bugs found.

Best regards

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
Engaged ,
Jun 04, 2023 Jun 04, 2023

Copy link to clipboard

Copied

Hey, this can shave off some time in some cases!

Testing... Gives error when the top layer is locked and the selected art is on a layer below.

A choise of a fill to apply was strange in my case — the standard b/w gradient.

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
Enthusiast ,
Jun 04, 2023 Jun 04, 2023

Copy link to clipboard

Copied

I added to Mark's code 1) the exact color for the rectangle instead of the unpredictable swatch index, 2) get selected text layer to create the rectangle, and 3) the isSelectRect boolean key to select the rectangle after the script runs.

 

 

 

 

/**
 * Draw rectangle showing selected text's bounds.
 *  m1b
 * @discussion https://community.adobe.com/t5/illustrator-discussions/how-to-draw-a-graphic-on-hidden-character-in-illustrator/m-p/13833618
 */
(function () {

    var isSelectRect = true,
        doc = app.activeDocument,
        text = doc.selection,
        bounds = getTextOutlineBounds(text);

    if (bounds == undefined) {
        alert('Could not calculate bounds. Please select some text and try again.');
        return;
    }

    var rectColor = new CMYKColor();
    rectColor.cyan   = 0;
    rectColor.magenta = 100;
    rectColor.yellow = 100;
    rectColor.black  = 0;
    var lay = (text.typename == 'TextRange') ? text.parent.textFrames[0].layer : text[0].layer;

    // draw a rectangle just for showing the bounds
    var rect = lay.pathItems.rectangle(bounds[1], bounds[0], bounds[2] - bounds[0], -(bounds[3] - bounds[1])); // TLWH
    rect.fillColor = rectColor;
    rect.opacity = 30;
    // reset text selection requires CS6 or CC
    if (parseInt(app.version) >= 16 && isSelectRect) {
      app.executeMenuCommand('deselectall');
      rect.selected = true;
    }
})();


/**
 * Returns bounds of Text object [L, T, R, B].
 *  m1b
 *  2023-06-02
 *  {Text|Paragraph|Line|Word|Character} text - an Indesign text object.
 *  {Array<Number>} - the bounds [T, L, B, R].
 */
function getTextOutlineBounds(text) {

    var bounds;

    if (text.constructor.name == 'Array') {

        // process each element of array
        for (var i = 0; i < text.length; i++)
            bounds = expandBoundsForIllustrator(bounds, getTextOutlineBounds(text[i]));

        return bounds;

    }

    else if (text.constructor.name == 'TextFrame') {
        // process the text of the text frame
        return getTextOutlineBounds(text.textRange);
    }

    else if (
        !text.hasOwnProperty('characters')
        || text.characters.length == 0
        || !text.hasOwnProperty('fillColor')
    )
        // can't work with this
        return;

    var tf = text.parent.textFrames[0],
        arbitraryValue = 61.803;

    // we'll paint the text with this
    // color so that when it's outlined,
    // we can differentiate it
    var markerColor = new CMYKColor();
    markerColor.cyan = arbitraryValue;
    markerColor.magenta = arbitraryValue;
    markerColor.yellow = arbitraryValue;
    markerColor.black = arbitraryValue;

    var dup = tf.duplicate(),
        start = text.start,
        end = text.end;

    // mark the selected characters
    for (var i = start; i < end; i++)
        dup.characters[i].fillColor = markerColor;

    // a stringified version for matching purposes
    var matchMarker = stringify(dup.characters[start].fillColor);

    // the outlines are always compoundPathItems
    var outlines = dup.createOutline().compoundPathItems;

    // find the marked outlines
    for (var i = 0; i < outlines.length; i++)
        if (stringify(outlines[i].pathItems[0].fillColor) == matchMarker)
            bounds = expandBoundsForIllustrator(bounds, outlines[i].visibleBounds);

    // delete temporary items
    for (var i = outlines.length - 1; i >= 0; i--)
        outlines[i].remove();

    return bounds;

};


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

    if (!b1 && !b2)
        return;

    if (!b1 && b2)
        return b2.slice();

    if (!b2 && b1)
        return b1.slice();

    var expanded = b2.slice();

    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;

};


/**
 * Stringify tailored for the purpose
 * of identifying identical Swatches.
 *  m1b
 *  2023-04-26
 *  {Object} obj - the object to stringify.
 *  {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;

};

 

 

 

 

 

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
Engaged ,
Jun 04, 2023 Jun 04, 2023

Copy link to clipboard

Copied

Great! Thank you both, folks.

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 ,
Jun 04, 2023 Jun 04, 2023

Copy link to clipboard

Copied

Thank you all for testing and for your improvements @Sergey Osokin. @Egor Chistyakov good pickup about the locked layer problem and the swatch.

 

I didn't focus on drawing the rectangle—you can put it in any swatch you want, or just get rid of that part—the function getTextOutLineBounds is the real script. Drawing the rectangle was just a quick example—in most cases you wouldn't want that, but it was handy to show that the function returned a sensible result.

 

Thanks again, and @Charu Rajput, for giving it a run. 🙂

- Mark

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
Engaged ,
Jun 05, 2023 Jun 05, 2023

Copy link to clipboard

Copied

LATEST

@m1b, I decided to read the original post. So it’s all about my precious font height options 🙂 I could have guessed.

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