見出し画像

【Illustrator】PhotoshopのダイナミックテキストをIllustratorで実現する

2025年6月リリースのPhotoshop 26.8にダイナミックテキスト機能を追加されました。Adobe Expressの人気機能が移植されたものです。

でも、これってIllustratorにこそ、欲しいですよね!ということで作ってみました。

制限事項

  • 横書きのみ

  • 欧文で語尾にスペースがあるとき、スペースを計算してしまいます。

  • カーニングや文字ツメなどの情報がクリアされてしまいます。これは保持したいところ…

コード

コピーしてテキストエディタにペーストして保存。拡張子を.jsxに変更すればスクリプトファイルとして実行できます。

#target illustrator
app.preferences.setBooleanPreference('ShowExternalJSXWarning', false);

// エリア内文字をポイント文字に変換し、変換後のTextFrameを取得
function convertAreaTextToPointText(areaTextFrame, originalContents, originalPosition) {
    areaTextFrame.convertAreaObjectToPointObject();

    var allTextFrames = app.activeDocument.textFrames;
    for (var i = 0; i < allTextFrames.length; i++) {
        var tf = allTextFrames[i];
        var dx = Math.abs(tf.position[0] - originalPosition[0]);
        var dy = Math.abs(tf.position[1] - originalPosition[1]);

        if (dx < 1 && dy < 1) {
            return tf;
        }
    }
    return null;
}

// エリア内文字を行単位に分割し、ポイント文字のTextFrameとして再配置
function splitTextFrameIntoLines(textFrame) {
    var lines = textFrame.contents.split('\r');
    var originalPosition = textFrame.position;
    var currentY = originalPosition[1];
    var textSize = textFrame.textRange.characterAttributes.size;
    var textFont = textFrame.textRange.characterAttributes.textFont;
    var textColor = textFrame.textRange.characterAttributes.fillColor;
    var hScale = textFrame.textRange.characterAttributes.horizontalScale;

    var splitLines = [];

    for (var i = 0; i < lines.length; i++) {
        var newLine = app.activeDocument.textFrames.pointText([originalPosition[0], currentY]);
        newLine.contents = lines[i];
        newLine.textRange.characterAttributes.size = textSize;
        newLine.textRange.characterAttributes.textFont = textFont;
        newLine.textRange.characterAttributes.fillColor = textColor;

        var actualWidth = newLine.width;
        var scaleX = (textFrame.width / actualWidth);
        if (scaleX > 0) {
            newLine.textRange.characterAttributes.size = textSize * scaleX;
            newLine.textRange.characterAttributes.horizontalScale = 100;
            newLine.textRange.characterAttributes.verticalScale = 100;
        }

        currentY -= newLine.height;
        splitLines.push(newLine);
    }
    var mergedFrame = mergeTextFramesVertically(splitLines);
    if (mergedFrame) {
        // 行間を自動に設定し、autoLeadingAmount を 100 に設定
        mergedFrame.textRange.characterAttributes.autoLeading = true;

        var paragraphs = mergedFrame.paragraphs;
        for (var i = 0; i < paragraphs.length; i++) {
            paragraphs[i].paragraphAttributes.autoLeadingAmount = 110;
        }

        redraw();
        textFrame.remove();
        mergedFrame.convertPointObjectToAreaObject();
    }
}

// 垂直比率を水平比率に合わせて統一
function matchVerticalScaleToHorizontal(item) {
    var currentHorizontalScale = item.textRange.characterAttributes.horizontalScale;
    item.textRange.characterAttributes.verticalScale = currentHorizontalScale;
}

// テキストフレームのみをリストに追加
function collectTextFrame(item, list) {
    if (item.typename === 'TextFrame') {
        list.push(item);
    }
}

// 複数のテキストフレームを縦方向に連結して再構成
function mergeTextFramesVertically(frames) {
    if (frames.length < 2) {
        return;
    }

    var sortedFrames = sortTextFramesByPosition(frames);
    var splitFrames = [];
    for (var i = 0; i < sortedFrames.length; i++) {
        var lines = sortedFrames[i].contents.split('\r');
        for (var j = 0; j < lines.length; j++) {
            if (lines[j] !== "") {
                var tf = sortedFrames[i].duplicate();
                tf.contents = lines[j];
                tf.top -= j * 2000; // 行順を維持するために位置調整
                splitFrames.push(tf);
            }
        }
        sortedFrames[i].remove();
    }
    sortedFrames = sortTextFramesByPosition(splitFrames);

    var baseFrame = sortedFrames[0];
    for (var k = 1; k < sortedFrames.length; k++) {
        baseFrame.paragraphs.add('\n');
        var paragraphs = sortedFrames[k].paragraphs;
        for (var p = 0; p < paragraphs.length; p++) {
            paragraphs[p].duplicate(baseFrame);
        }
        sortedFrames[k].remove();
    }
    return baseFrame;
}

// テキストフレームを位置情報(上→下、左→右)でソート
function sortTextFramesByPosition(frameList) {
    try {
        var copyList = [];
        var i;
        for (i = 0; i < frameList.length; i++) {
            copyList.push(frameList[i]);
        }

        copyList.sort(function(a, b) {
            if (a.position[1] > b.position[1]) {
                return -1;
            }
            if (a.position[1] < b.position[1]) {
                return 1;
            }
            if (a.position[1] === b.position[1]) {
                if (a.position[0] < b.position[0]) {
                    return -1;
                }
                if (a.position[0] > b.position[0]) {
                    return 1;
                }
                return 0;
            }
        });
        return copyList;
    } catch (e) {
        alert("ソート中にエラーが発生しました: " + e.message);
        return frameList;
    }
}

function main() {
    // 選択確認
    if (app.documents.length === 0 || app.activeDocument.selection.length === 0) {
        alert("エリア内文字を選択してください。");
        return;
    }

    var areaTextFrame = app.activeDocument.selection[0];

    if (areaTextFrame.typename !== "TextFrame" || areaTextFrame.kind !== TextType.AREATEXT) {
        alert("エリア内文字を選択してください。");
        return;
    }

    var areaWidth = areaTextFrame.width;

    // 分割
    splitTextFrameIntoLines(areaTextFrame);

    // 連結
    var selectionItems = app.activeDocument.selection;
    if (selectionItems.length >= 2) {
        var textFrames = [];
        for (var i = 0; i < selectionItems.length; i++) {
            collectTextFrame(selectionItems[i], textFrames);
        }
        if (textFrames.length >= 2) {
            mergeTextFramesVertically(textFrames);
        }
    }
}

main();

備考

思いっきり車輪の大発明ですが、今回のスクリプトはエリア内文字の横幅を見ます。

ダウンロード

ここから先は

0字 / 1ファイル

¥ 100

Amazon Payで支払うと最大2%還元のチャンス! 9/30まで

定期マガジンを購読されるとサンプルファイルをダウンロードいただけます。 https://note.com/dtp_tranist/m/mebd7eab21ea5