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

how to get physical width of character (extendscript)

Community Beginner ,
Sep 21, 2023 Sep 21, 2023

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
506
Translate
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

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

Translate
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

thank u!😉

Translate
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

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.pngexpand image

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

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

 

thanks for sharing!

Translate
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
LATEST

thank u,i will give it a try👀

Translate
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