DTPab

印刷やデザイン、アドビ製アプリやスクリプトなど、雑多な技術ブログ

イラレで字形の一括置換をしたい

昨晩0時過ぎに帰宅してから書き始めたコードですが、いつの間にかマシンの前で寝てしまい、気づいたら4時でしたw

さて、本題です。
Illustratorの検索置換機能が貧弱すぎるので、何かやろうと思うと大抵スクリプトのお世話になります。
今回は漢字の字形をまとめて処理したいという要望をもらい、なんとかしようと思ったものです。

ただ、Illustratorで字形をまとめて置換できるようなスクリプトがないのですね。
かといって、InDesignのように字形だけを置換するような機能がIllustratorにはない…。これをどうやってスクリプトで解決するかが今回の問題でした。

まずはコードです。

/*
    字形を指定しても、文字が字形を持っていないと置換されません。
    Unicodeで指定した文字を、
    指定の字形(JIS78字形や等幅半角字形等)に一括で置換します。
    念のため、バックアップを保存するなどしてから実行してください。
*/
#target "illustrator"

//"置換したい文字のUnicode", "字形Enumの文字列(下記参照)",
//という形で記述する
var replaceGlyphs = [
    "9d0e", "JIS78FORM", //鴎
    "845b", "JIS90FORM", //葛
    ];
/*
    *字形Enum
    DEFAULTFORM //標準字形
    EXPERT //エキスパート字形
    FULLWIDTH //等幅全角字形
    HALFWIDTH //等幅半角字形
    JIS04FORM //JIS04字形
    JIS78FORM //JIS78字形
    JIS83FORM //JIS83字形
    JIS90FORM //JIS90字形
    QUARTERWIDTH //等幅四分字形
    THIRDWIDTH //等幅三分字形
    TRADITIONAL //旧字体
*/

(function main(){
    if (app.documents.length === 0){
        alert("ドキュメントが開かれていません");
        return;
        }
    var alertTxt = "【重要】下記を必ず守ってください\r◎バックアップを保存してから実行してください\r";
    alertTxt += "◎フォントをすべてアクティブにしてから実行してください\r\r";
    alertTxt += "スクリプトを実行してもよろしいですか?";
    if (!confirm (alertTxt, true)) return;
    var tgtTxt = app.activeDocument.textFrames;
    for (var i=0; i<tgtTxt.length; i++){
        var tgtContents = tgtTxt[i].contents;
        //replaceGlyphs
        var uniqueCount = 0;
        for (var k=0; k<replaceGlyphs.length; k+=2){
            var stChars = tgtTxt[i].story;
            var myIndex = stChars.textRange.contents.indexOf (String.fromCharCode(parseInt(replaceGlyphs[k], 16)), 0);
            while (myIndex !== -1){
                uniqueCount++;
                var myCAtt = stChars.textRange.characters[myIndex].characterAttributes;
                var newStyle = app.activeDocument.characterStyles.add(replaceGlyphs[k]+"_"+uniqueCount);
                if (myCAtt.strokeWeight == 1) myCAtt.strokeWeight = 0;//未設定だとなぜか1になるので、1の場合だけ0にする…なにこれ…
                for (key in myCAtt){
                    try { newStyle[key] = myCAtt[key] }
                    catch(e){
                        //以下は設定がないとエラーになるので、設定がなかった場合は明示的に指定する
                        if (key == "kerningMethod") newStyle.kerningMethod = AutoKernType.NOAUTOKERN;
                        else if (key == "alternateGlyphs") newStyle.alternateGlyphs = AlternateGlyphsForm.DEFAULTFORM;
                        }
                    }
                var tgtGlyph = replaceGlyphs[k+1];
                newStyle.alternateGlyphs = eval("AlternateGlyphsForm."+tgtGlyph);
                newStyle.applyTo(stChars.textRange.characters[myIndex], true);
                newStyle.remove();
                myIndex = stChars.textRange.contents.indexOf (String.fromCharCode(parseInt(replaceGlyphs[k], 16)), myIndex+1);
                }
            }
        }
    alert("終了しました")
    })();

今回はIllustrator用のスクリプトということで、InDesignと大きく違うのはexit()を使えないことです。そのため普通にコードを書くと{}の入れ子が増えがちになります。
そこでIllustratorでは匿名関数(即時関数?)で書くようにしています。そうするとexit()の代わりにreturnでスクリプトを抜け出せるからです。

さて、ここから各部のちょっとした説明をば。

//"置換したい文字のUnicode", "字形Enumの文字列(下記参照)",
//という形で記述する
var replaceGlyphs = [
    "9d0e", "JIS78FORM", //鴎
    "845b", "JIS90FORM", //葛
    ];

この部分で、検索する漢字とそれに充てる字形を設定します。
コードのコメントのとおりですが、Illustratorの字形パレットに表示されるUnicode(16進数で表記)と、以下の一覧から置換したい字形の設定を、カンマで区切ってダブルクォーテーション(")で挟んで羅列します。

  • DEFAULTFORM:標準字形
  • EXPERT:エキスパート字形
  • FULLWIDTH:等幅全角字形
  • HALFWIDTH:等幅半角字形
  • JIS04FORM:JIS04字形
  • JIS78FORM:JIS78字形
  • JIS83FORM:JIS83字形
  • JIS90FORM:JIS90字形
  • QUARTERWIDTH:等幅四分字形
  • THIRDWIDTH:等幅三分字形
  • TRADITIONAL:旧字体

Unicodeだけだとどの漢字を置換する設定なのかあとで分からなくなるので、サンプルコードのようにコメントでその漢字を入力しておくとよいと思います。

置換対象の設定を済ませたら、まずはドキュメントが開かれているか、注意事項の確認ののち、いざ関数の実行になります。

var stChars = tgtTxt[i].story;
var myIndex = stChars.textRange.contents.indexOf (String.fromCharCode(parseInt(replaceGlyphs[k], 16)), 0);

この部分で、テキストオブジェクトの中から対象となる漢字を検索しています。設定したUnicodeを16進数の数値として型変換し(parseIntに基数16を渡す)、その数値を基にfromCharCodeで文字を特定します。formCharCodeメソッドは常に、String.fromCharCodeという形で利用します。
こうして16進数の数値から取得した文字に対して、今度はindexOfメソッドを使うことでストーリー内を検索することに代えています。第二引数に0を渡しているのは、ストーリーの0番目の文字から検索を開始、という意味です。indexOfメソッドは、対象の文字が見つかれば見つかった場所=indexを数値として返してきますが、見つからなかった場合は-1が返ります。
このことを利用して、次のwhile文のトリガーとしています。

今回苦労した(というか工夫した)のがここから先の部分です。目指したのは、以下のような流れです。

  1. 字形を変更したい文字の現在の文字装飾情報を基に、空欄のない文字スタイルを作る
  2. 作った文字スタイルの字形設定を任意のものに変更する
  3. その文字スタイルを字形を変更したい文字に充てる
  4. その文字スタイルを捨てる

これで字形だけを変換することができないかと思ったわけです。
さて、今度はwhile文の中です。

var myCAtt = stChars.textRange.characters[myIndex].characterAttributes;
var newStyle = app.activeDocument.characterStyles.add(replaceGlyphs[k]+"_"+uniqueCount);
if (myCAtt.strokeWeight == 1) myCAtt.strokeWeight = 0;//未設定だとなぜか1になるので、1の場合だけ0にする…なにこれ…
for (key in myCAtt){
    try { newStyle[key] = myCAtt[key] }
    catch(e){
        //以下は設定がないとエラーになるので、設定がなかった場合は明示的に指定する
        if (key == "kerningMethod") newStyle.kerningMethod = AutoKernType.NOAUTOKERN;
        else if (key == "alternateGlyphs") newStyle.alternateGlyphs = AlternateGlyphsForm.DEFAULTFORM;
        }
    }
var tgtGlyph = replaceGlyphs[k+1];
newStyle.alternateGlyphs = eval("AlternateGlyphsForm."+tgtGlyph);

この部分で文字スタイルを定義しています。前記のリストでは「1.」にあたる部分です。
コメントで「なにこれ」とかありますがw、文字スタイルを生成する際に、元とする文字の線幅が未設定だと、なぜか「1」が設定されてしまうのです。これを回避するために、線幅が「1」だった場合は「0」に戻しています。普通は文字に線幅付けないよね? という前提のもとに立っていますが、ここは注意してください。
このあとfor…in文で、文字の装飾情報を一つずつ取り出して文字スタイルに登録していきます。ただし、ここもまたコメントにあるように、カーニングと字形については、文字スタイルの元とした文字に設定がないとエラーで止まってしまいます。そのため未設定だった場合は明示的に用意してあげなければなりません。
最後に、文字スタイルの字形情報を、最初に設定した字形に変更します(evalメソッドを利用しています)。

この後は、文字にその文字スタイルを充て(applyToメソッド)、文字スタイルを削除し(removeメソッド)、myIndex変数を更新します。この変数の更新はwhile文のトリガー(myIndexが-1でループから抜ける)の確認です。

以上、思ったより長くなってしまいましたが、字形を一括で置換するスクリプトでした。