きみはねこみたいなにゃんにゃんなまほう

ねこもスクリプトをかくなり

Adobe ExtendScript Debuggerを使ってみる

アートボードを100枚以上含むファイルがあり、各アートボードのテキストボックスの内容をまとめてテキストファイルとして取得したくなったので、久しぶりにAdobe ExtendScriptスクリプトを書きました。やりたいことの要件は以下の通りです。

  • 各アートボードはそれぞれ1つのテキストボックスを持つ
    • もしくは対象の1つ以外のテキストボックスはレイヤーのロックなどで選択できないようにしておく
  • 各テキストボックスの内容が1行ごとに書かれたファイルを出力する

本物のデータは載せられないので、簡略化した例で示すと

f:id:lightbulbcat:20211226224228p:plain

のようなIllustratorのファイルに対して

テキストA
テキストB
テキストC
...

というデータが書き込まれたファイルを出力してくれるスクリプトが欲しい、ということになります。

開発環境はVS Codeを使い、途中でデバッグが辛くなったのでAdobe謹製のExtendScript Debuggerという拡張機能を入れました。 最終的に完成したスクリプトは以下のものです。

var CONFIG = {
    outFile: '~/Desktop/descs.txt',
}

function map(items, fn) {
    var res = []
    for (var i = 0; i < items.length; i++) res[i] = fn(items[i], i)
    return res
}

function filter(items, fn) {
    var res = []
    for (var i = 0; i < items.length; i++) {
        if (fn(items[i], i)) res.push(items[i])
    }
    return res
}

function log(input) {
    var now = new Date();
    var output = now.toTimeString() + ": " + input;
    $.writeln(output)
}

try {
    var texts = []
    log('Start script')
    for (var i = 0; i < app.activeDocument.artboards.length; i++) {
        app.activeDocument.selection = null;
        app.activeDocument.artboards.setActiveArtboardIndex(i);
        app.activeDocument.selectObjectsOnActiveArtboard();

        var textFrames = filter(app.activeDocument.selection, function(item) {
            return item instanceof TextFrame
        })

        texts.concat(map(textFrames, function(tf) {
            log('Artboard' + i + ': ' + tf.contents)
            return tf.contents
        }))
    }
    log('Detect ' + texts.length + ' textFrames total')

    texts = map(texts, function(text) {
        return text.replace(/\r\n|\r|\n/g, '\\n')
    })

    var output = texts.join('\n')
    var f = new File(CONFIG.outFile)
    f.encoding = 'UTF-8'
    f.lineFeed = 'Unix'
    f.open('w')
    f.write(output)
    f.close()

} catch (e) {
    alert('Error: ' + e)
}

始めはこのスクリプトデバッグを、ログメッセージをファイル出力して行なっていたのですが、 ファイルに何も書き込まれないという事象(原因は後述)に陥ったため、ExtendScript Debuggerの導入に踏み切りました。 ファイル出力を行う処理のデバッグ用のログをファイル出力で行なったため、結果として混乱が増大しました。 デバッグ専用の情報出力チャネルの存在は大切ですね。

ExtendScript Debuggerの使い方

使い方と言っても基本的には公式のドキュメントに従えば問題なく使えます。

ExtendScript Debugger - Visual Studio Marketplace

VS Codeデバッグ機能を使ったことがないと分かりづらいのは、デバッグ対象のプログラム (今回の場合はスクリプトを動作させるプログラムを指すのでAdobe Illustrator) を指定してやらないと Can’t start a session without an active target and engine. Select an active target and engine before trying again. と表示されて失敗するところです。意図するところは、どこでデバッグ対象のスクリプトを動かすか指定する必要があるということです。

f:id:lightbulbcat:20211226020254p:plain

これを解決するにはデバッグを実行する前に、下のステータスバーで対象プログラムを指定します。

f:id:lightbulbcat:20211226020229p:plain

Select the target application と表示されているところを選択すると選択モードになるので Adobe Illustrator を選択します。

f:id:lightbulbcat:20211226020234p:plain

デバッグの実行に成功すると $.write() で指定したメッセージがコンソールに流れます。

f:id:lightbulbcat:20211226020249p:plain

その他

毎回デバッグ対象のプログラムを指定したくない

${command:AskForScriptName} を固定文字列にすれば毎回ダイアログで入力せずにすみます。

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "extendscript-debug",
            "request": "launch",
            "name": "Ask for script name",
            // "program": "${workspaceFolder}/${command:AskForScriptName}",
            "program": "${workspaceFolder}/copy-textbox.jsx",
            "stopOnEntry": false
        }
    ]
}

それにしてもこの program っていう表現、デバッグ対象のスクリプトなのか、ランタイムを指すのか、何を指しているか曖昧で分かりづらいですね。

ファイル出力ではまったところ

はまったところは2点。

  • join('\n') で結合した文字列の改行文字が fileObj.write() 時に \r になる
    • f.lineFeed = 'Unix' で期待通りになった
  • 日本語が出力されない
    • f.encoding = 'UTF-8' で改善された。

ちなみに File クラスのリファレンスは Illustrator_Scripting_Guide_cc.pdf ではなく JavaScriptToolsGuide_CS5.pdf にあります。 共にAdobeサイト内の検索に引っかからないのもあり非常に探しづらい。

AdobeはもっとExtendScriptのリファレンスを整備してほしい。なんでPDFでしか提供されていないんだよ。 というか公式リファレンスくらいちゃんとアクセスできるところに置いてほしい。AdobeExtendScriptを捨てたいんだろうか。