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:
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;
};
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,
...
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.
Copy link to clipboard
Copied
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.
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;
};
Copy link to clipboard
Copied
Great! Thank you both, folks.
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
Copy link to clipboard
Copied
@m1b, I decided to read the original post. So it’s all about my precious font height options 🙂 I could have guessed.