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

how to get physical width of character (extendscript)

Community Beginner ,
Sep 21, 2023 Sep 21, 2023

Copy link to clipboard

Copied

In a text node, different encodings of characters correspond to different widths(pt) of individual characters, how to get the width of a specified individual character when using extendscript script?😭

TOPICS
Scripting

Views

445
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
Adobe
Community Expert ,
Sep 21, 2023 Sep 21, 2023

Copy link to clipboard

Copied

duplicate your text frame, remove all characters except the one you need to measure, create outlines, get width

Votes

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 Beginner ,
Sep 24, 2023 Sep 24, 2023

Copy link to clipboard

Copied

thank u!😉

Votes

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 ,
Sep 22, 2023 Sep 22, 2023

Copy link to clipboard

Copied

Hi @wy3245464968i2, it isn't easy, as Carlos intimated. Here is a script that demonstrates a function I made to do what you ask. It returns the "fontBounds" which is the size of the glyphs bounding box(es) and the geometric bounds (calculated exactly as @CarlosCanto suggested). Note that the function is quite naive regarding the text inputs—it will certainly fail in many cases. For example, if the text is rotated, then the bounds should be correct, but the fontBounds will be wrong—it will need extra logic to calculate correctly in that case. Anyway, give it a try and see if it helps.

- Mark

Screenshot 2023-09-23 at 13.05.14.png

/**
 * Example usage of getTextDimensions function to show
 * font bounds and geometry bounds of selected text.
 * @author m1b
 * @discussion https://community.adobe.com/t5/illustrator-discussions/how-to-get-physical-width-of-character-extendscript/m-p/14103798
 */
(function () {

    var doc = app.activeDocument,
        text = doc.selection,
        dimensions = getTextDimensions(text);

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

    // JUST FOR EXAMPLE PURPOSES:
    drawRectangle(doc, dimensions.fontBounds, makeColor([255, 0, 0]));
    drawRectangle(doc, dimensions.bounds, makeColor([0, 0, 255]));

})();


/**
 * Returns dimension information about the given `text`.
 * @author m1b
 * @version 2023-06-02
 * @param {TextRange|TextFrame} text - an Illustrator text object.
 * @returns {ArrayObject} - { fontBounds: bounds: left: top: width: height: }
 */
function getTextDimensions(text) {

    var bounds;

    if (
        text.constructor.name == 'Array'
        && text.length > 0
    )
        return getTextDimensions(text[0]);

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

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

    var result = {
        fontBounds: undefined,
        bounds: undefined,
        left: undefined,
        top: undefined,
        width: undefined,
        height: undefined,
    };

    var doc = getDocument(text);

    // first get the dimensions by duplicate
    // the text in its own frame at [0, 0]
    var t = doc.activeLayer.textFrames.add(ElementPlacement.PLACEATEND);
    t.position = [0, 0];
    text.duplicate(t);
    var fontBounds = t.geometricBounds;
    var outline = t.createOutline();
    var innerBounds = outline.geometricBounds;
    outline.remove();

    result.width = innerBounds[2] - innerBounds[0];
    result.height = -(innerBounds[3] - innerBounds[1]);

    // next get the dimensions in correct position
    var tf = text.parent.textFrames[0];

    // we'll paint the text with this
    // so we can differentiate it
    var markerColor = getMarkerColor();

    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
    dup = dup.createOutline();
    var outlines = dup.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);

    dup.remove();

    // now correct for the difference between [0,0]
    fontBounds[0] += bounds[0] - innerBounds[0];
    fontBounds[1] += bounds[1] - innerBounds[1];
    fontBounds[2] += bounds[2] - innerBounds[2];
    fontBounds[3] += bounds[3] - innerBounds[3];

    // update result
    result.bounds = bounds;
    result.fontBounds = fontBounds;
    result.left = result.bounds[0];
    result.top = result.bounds[1];

    return result;

};


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

};


/**
 * Attempt to return an unusual color for identification purposes.
 * @returns {RGBColor|CMYKColor}
 */
function getMarkerColor() {

    var c,
        v = 61.8;

    if (app.activeDocument.documentColorSpace == DocumentColorSpace.RGB) {
        c = new RGBColor();
        c.red = c.green = c.blue = v;
    }

    else {
        c = new CMYKColor();
        c.cyan = c.magenta = c.yellow = c.black = v;
    }

    return c;

};


/**
 * Returns a page item's document.
 * @author m1b
 * @version 2023-09-13
 * @param {PageItem} item - an Illustrator page item.
 * @returns {Document}
 */
function getDocument(item) {

    var parent = item;

    while (
        parent.constructor.name !== 'Document'
        && parent.hasOwnProperty('parent')
    )
        parent = parent.parent;

    if (parent.constructor.name == 'Document')
        return parent;

};


/**
 * Return an RGB color.
 * @param {Array<Number>} breakdown - the color breakdown.
 * @returns {RGBColor}
 */
function makeColor(breakdown) {

    var colr = new RGBColor();
    colr.red = breakdown[0];
    colr.green = breakdown[1];
    colr.blue = breakdown[2];

    return colr;

};


/**
 * desc
 * @date 2023-09-23
 * @param {Document} doc - an Illustrator document.
 * @param {Array<Number>} bounds - [T, L, B, R]
 * @param {RGBColor|CMYKColor|GrayColor} color - the color
 * @return {PathItem}
 */
function drawRectangle(doc, bounds, color) {
    var rect = doc.pathItems.rectangle(bounds[1], bounds[0], bounds[2] - bounds[0], -(bounds[3] - bounds[1])); // TLWH
    rect.filled = true;
    rect.stroked = false;
    rect.fillColor = color;
    rect.opacity = 30;
    return rect;
};

Votes

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 ,
Sep 22, 2023 Sep 22, 2023

Copy link to clipboard

Copied

yeah covering all the scenarios is certainly not easy @m1b , your script does a lot more than I thought was needed.

 

thanks for sharing!

Votes

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 Beginner ,
Sep 24, 2023 Sep 24, 2023

Copy link to clipboard

Copied

LATEST

thank u,i will give it a try👀

Votes

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