Copy link to clipboard
Copied
There are horizontalOffset and endHorizontalOffset to get start and end position of character in the horizontal direction.
Also there are baseline to get position of the baseline.
Is there the same for the vertical direction to get the upper and lower position of particular character?
Hi again @livl17017666,
Well I wasn't satisfied, so I've written a new function getTextOutlineBounds(text) which does a much better job. First select some text and run script. So far the script works (on my system at least) even with text in anchored objects or tables, but I've no doubt it will fail in some cases. One limitation I have documented in script comments. Let me know if you find bugs and I will fix. Otherwise, I hope it is useful!
- Mark
/*
* Draw rectangle showing selected tex
...
Copy link to clipboard
Copied
Hi @livl17017666, you can get the bounds of a character's glyph's metrics (see example below of how to do that) but if you need the actual bounds of the character we'll need to do some hacks. Perhaps let me know if this is sufficient for your needs.
- Mark
function main() {
var doc = app.activeDocument;
// just grab some character from document
var ch = doc.textFrames[0].characters[5];
var bounds = [
/* top */ ch.baseline - ch.ascent,
/* left */ ch.horizontalOffset,
/* bottom */ ch.baseline + ch.descent,
/* right */ ch.endHorizontalOffset
];
// draw a rectangle just for showing
var rect = doc.rectangles.add({
geometricBounds: bounds,
fillColor: doc.swatches[4],
});
rect.transparencySettings.blendingSettings.opacity = 30;
};
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'Draw Rectangle Around Character');
Copy link to clipboard
Copied
Hi again @livl17017666,
Well I wasn't satisfied, so I've written a new function getTextOutlineBounds(text) which does a much better job. First select some text and run script. So far the script works (on my system at least) even with text in anchored objects or tables, but I've no doubt it will fail in some cases. One limitation I have documented in script comments. Let me know if you find bugs and I will fix. Otherwise, I hope it is useful!
- Mark
/*
* Draw rectangle showing selected text's bounds.
* @author m1b
* @discussion https://community.adobe.com/t5/indesign-discussions/how-to-get-bounds-of-particular-character/m-p/13713084
*/
function main() {
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.selection[0].parentTextFrames[0].parentPage.rectangles.add({
geometricBounds: bounds,
fillColor: doc.swatches[4],
});
rect.transparencySettings.blendingSettings.opacity = 30;
};
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'Draw Rectangle Around Selected Text');
/**
* Returns bounds of Text object [T, L, B, R].
* Known limitations:
* 1. Will return the wrong bounds in some cases
* where the text's paragraph is split over multiple
* textframes and uses a multi-line composer.
* (You can simulate this problem by copy-pasting
* the text frame - if the line breaks change and
* `text` has moved, this function will return
* the *moved* bounds. Script will warn when this
* is a possibility.
* @author m1b
* @version 2023-04-11
* @param {Text|Paragraph|Line|Word|Character} text - an Indesign text object.
* @returns {Array<Number>} - the bounds [T, L, B, R].
*/
function getTextOutlineBounds(text) {
app.scriptPreferences.measurementUnit = MeasurementUnits.POINTS;
var bounds;
// this property will be used to
// (a) force createOutlines to make separate polygons, and
// (b) identify the polygons after outlining
var marker = 'fillTint';
if (text.constructor.name == 'Array') {
// process elements of array
for (var i = 0; i < text.length; i++)
bounds = expandBoundsForIndesign(bounds, getTextOutlineBounds(text[i]));
return bounds;
}
else if (
!text.hasOwnProperty('parentTextFrames')
|| !text.isValid
|| !text.hasOwnProperty('characters')
|| !text.hasOwnProperty(marker)
)
return;
if (
text.paragraphs[0].parentTextFrames.length > 1
&& text.paragraphs[0].parentTextFrames[0] !== text.parentTextFrames[0]
&& String(text.parentStory.composer).search(/single-line/i) == -1
)
alert('Caution\nYou are using a multi-line composer. Function may return erroneous results.');
// store the original values before setting marker
var originalValues = [];
for (var i = 0; i < text.textStyleRanges.length; i++) {
originalValues[i] = {};
originalValues[i].value = text.textStyleRanges[i][marker];
originalValues[i].start = text.textStyleRanges[i].index - text.index;
originalValues[i].end = originalValues[i].start + text.textStyleRanges[i].length - 1;
}
var tf = text.parentTextFrames[0],
arbitraryValue = 61.803;
// mark the character so we can recognise it later
text[marker] = arbitraryValue;
// get the outermost textframe
while (tf.parent.constructor.name == 'Character')
tf = tf.parent.parentTextFrames[0];
// outline the whole text frame because outlining
// individual texts causes it to move left by the
// glyph's left sidebearing amount which I don't
// know how to determine
var dup = tf.duplicate();
outlines = dup.createOutlines();
// add outlines of anchored text frames
while (outlines[outlines.length - 1].textFrames.length > 0)
outlines.push(outlines[outlines.length - 1].textFrames[0].createOutlines()[0]);
// find the marked outlines
for (var i = 0; i < outlines.length; i++) {
var polygons;
if (outlines[i].constructor.name == 'Polygon')
// we get a Polygon if there is only one character in text frame
polygons = [outlines[i]];
else if (outlines[i].hasOwnProperty('polygons'))
// a Group with polygons
polygons = outlines[i].polygons;
else
continue;
for (var j = 0; j < polygons.length; j++)
if (polygons[j][marker] === arbitraryValue)
bounds = expandBoundsForIndesign(bounds, polygons[j].visibleBounds);
}
// revert the text after marking
for (var i = 0; i < originalValues.length; i++) {
var end = Math.min(text.length-1, originalValues[i].end);
text.characters.itemByRange(originalValues[i].start, end)[marker] = originalValues[i].value;
}
// delete all the outlines
for (var i = outlines.length - 1; i >= 0; i--)
outlines[i].remove();
if (dup.isValid)
// sometimes the duplicate is left behind
// eg. when there is a table in the text frame
dup.remove();
return bounds;
};
/**
* Returns bounds that encompass both bounds.
* @author m1b
* @version 2022-07-24
* @param {Array<Number>} b1 - bounds array [t, l, r, b].
* @param {Array<Number>} b2 - bounds array [t, l, r, b].
* @returns {Array<Number>}
*/
function expandBoundsForIndesign(b1, b2) {
if (b1 == undefined)
return b2;
if (b2 == undefined)
return b1;
var ex = [];
ex[0] = b1[0] < b2[0] ? b1[0] : b2[0];
ex[1] = b1[1] < b2[1] ? b1[1] : b2[1];
ex[2] = b1[2] > b2[2] ? b1[2] : b2[2];
ex[3] = b1[3] > b2[3] ? b1[3] : b2[3];
return ex;
};
Edit 2023-04-11: added code to ensure that fillTint is preserved in case selected text has multiple fillTints.
Copy link to clipboard
Copied
Hi @m1b,
Yes if there are no intrinsic Properties/Methods for exact meassure, it looks like that workaround is outline.
Thank you for you suggestion this is great work.
When I look this code maybe just if there are are variation of tint in selected text probably it will not back to original values.
But this could be solved with additional loops I guess.
Thank you for your effort!
If we can not do better than outlines I will mark this as Correct Answer.
Copy link to clipboard
Copied
Hi @livl17017666, good point about the tint variations. I have now fixed that in the code above.
As for a better solution: using the SDK, as Dirk mentioned, is definitely better. But that might be a major undertaking if you are not experienced with it.
- Mark
Copy link to clipboard
Copied
Hi @livl17017666 , I use this to get the metrics for a selected character. It gets ascent, descent, cap-height, and x-height, and uses a text frame’s first baseline offset rather than outlining to get the numbers. For more see this post from Uwe @Laubender
// the selected character
var s = app.activeDocument.selection[0];
// the fixed decimal place
var dn = 4;
if (s.constructor.name == "Character") {
var p = getFontMetric(s).pointSize;
var l = getFontMetric(s).leading;
var bl = getFontMetric(s).baseline.toFixed(dn);
var a = getFontMetric(s).ascent.toFixed(dn);
var d = getFontMetric(s).descent.toFixed(dn);
var ch = getFontMetric(s).capHeight.toFixed(dn);
var xh = getFontMetric(s).xHeight.toFixed(dn);
alert ("Selected Character Metrics\rPoint Size: " + p + "\rBaseline: " + bl +"\rLeading: " + l + "\rAscent: " + a + "\rDescent: " +d + "\rCapHeight: " +ch + "\rXHeight: " +xh )
} else {
alert("Please Select a Single Character")
}
/**
* Gets the selected character’s font metrics
* @ param the character selection
* @ return an object with pointSize, leading, baseline, ascent,descent, xHeight, and capHeight
*/
function getFontMetric(s){
app.scriptPreferences.measurementUnit = MeasurementUnits.POINTS;
var o = new Object;
o.pointSize = s.pointSize;
o.leading = s.leading;
o.baseline = s.baseline;
o.ascent = s.ascent;
o.descent = s.descent;
app.copy();
var tf = app.activeDocument.textFrames.add({geometricBounds: [0,0,500,500], textFramePreferences: {firstBaselineOffset: FirstBaseline.CAP_HEIGHT, insetSpacing: 0, minimumFirstBaselineOffset: 0, verticalJustification: VerticalJustification.TOP_ALIGN}});
app.select([tf.parentStory.insertionPoints[0]]);
app.paste();
tf.texts[0].alignToBaseline = false;
o.capHeight = tf.texts[0].insertionPoints[0].baseline;
tf.textFramePreferences.firstBaselineOffset = FirstBaseline.X_HEIGHT;
o.xHeight = tf.texts[0].insertionPoints[0].baseline;
tf.remove();
app.select(s)
app.scriptPreferences.userInteractionLevel = UserInteractionLevels.INTERACT_WITH_ALL;
return o
}
Copy link to clipboard
Copied
what a great script! do you have the same for illustrator?
Copy link to clipboard
Copied
For completeness sake as this thread is also tagged "SDK": the C++ SDK has plenty interfaces starting with "IWax" .