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

How to get bounds of particular character?

Participant ,
Apr 08, 2023 Apr 08, 2023

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?

 

 

 

TOPICS
Scripting , SDK

Views

708

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 , Apr 08, 2023 Apr 08, 2023

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

Screenshot 2023-04-09 at 13.37.51.png

 

 

/*
 * Draw rectangle showing selected tex
...

Votes

Translate

Translate
Community Expert ,
Apr 08, 2023 Apr 08, 2023

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');

 

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 ,
Apr 08, 2023 Apr 08, 2023

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

Screenshot 2023-04-09 at 13.37.51.png

 

 

/*
 * 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.

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
Participant ,
Apr 10, 2023 Apr 10, 2023

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.

   

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 ,
Apr 10, 2023 Apr 10, 2023

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

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 ,
Apr 11, 2023 Apr 11, 2023

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 

 

https://community.adobe.com/t5/indesign-discussions/how-to-determine-a-text-size-by-actual-print-mea...

 

 

Screen Shot.png

 

 

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

 

 

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
Participant ,
May 15, 2023 May 15, 2023

Copy link to clipboard

Copied

LATEST

what a great script! do you have the same for illustrator?

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
Guide ,
Apr 09, 2023 Apr 09, 2023

Copy link to clipboard

Copied

For completeness sake as this thread is also tagged "SDK": the C++ SDK has plenty interfaces starting with "IWax" .

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