DTPab

DTPにまつわるあれこれ

Illustratorのスクリプト勉強中④

ちょっと間が空いてしまいましたが、ちゃんと続いてます^^;

前回までのあらすじ

PageItem.visibleBoundsからオブジェクトの見かけ上の高さを割り出し、天地サイズを合わせ、横に並べるところまでできました。
選択しているオブジェクト群を、最前面のオブジェクトの天地サイズに揃えてリサイズしてから横に並べます。Y方向(垂直軸方向)の計算結果が負になってしまうことを避けるためにMath.absメソッドを利用して絶対値を取得し、PageItem.resizeメソッドを使ってオブジェクトを拡縮する方法を試しました。

前回のコード

var myDoc = app.activeDocument;
var mySels = myDoc.selection; //ここまでは前回と同じ

//0番目のオブジェクトの天のY座標
var targetY = mySels[0].visibleBounds[1];
//オブジェクトの高さを求める関数
var getHeight = function (obj){
    return Math.abs(obj.visibleBounds[3] - obj.visibleBounds[1]);
}
//基準とするオブジェクトの高さ
var tgHeight = getHeight(mySels[0]);

for (var i=1; i<mySels.length; i++){
    //変倍するオブジェクトの高さ
    var h = getHeight(mySels[i]);
    //比率計算(百分率)
    var p = (tgHeight / h) * 100;
    //リサイズ
    mySels[i].resize(p, p);
    //基準とするオブジェクトの天にあわせて整列
    mySels[i].position = [mySels[i].visibleBounds[0], targetY];
}

またこのコードに肉付けしていきます。

このスクリプトの最終型

ここまでで作りたいスクリプトが見えてきたので、最終的に作り上げたいスクリプトは以下のようなものです。

  • 選択したオブジェクトの高さを揃え、一定間隔(1mm)を空けて横に並べる*
  • 全体を横に並べた場合の最終的なサイズを指定できる*
  • どのオブジェクトを基準に高さを揃えるかを指定できる*
  • 並べる際の間隔も指定できるようにする**

*は最初から盛り込みたい必須機能。**は余力があれば盛り込む拡張機能です。

今日の目標

現状のスクリプトでは天地サイズをリサイズして横に並べますが、水平方向の移動をしていません。なので、今日の記事の目標は

  1. 1mm間隔で並べたときの最終的な横幅を入力して受け取る
  2. 水平方向に隙間なく並べる

というところまでやりたいと思います。

前回の補足

補足というか余談?です。
PageItem.resizeメソッドですが、引数を確認すると第七引数のscaleAboutでどこを基準にリサイズするか指定できるようですね。

f:id:uske_S:20180510144355p:plain

渡す値はTransformationオブジェクト。

f:id:uske_S:20180516105259p:plain

値のセットのしかたは画像右上の通り、Transformation.hogeとします。例えば左上を基準にリサイズするには、以下のようにします。

mySels[i].resize(p, p, true, true, true, true, 100, Transformation.TOPLEFT);

間のtrue100はデフォルト値を入れているだけです。このようにしてリサイズの基準点を変えられるようです。

値を入力させてそれを受け取る

さて本題です。まずは今日の目標の1、値を入力し、それを受け取ります。
もっとも簡便な方法としてはpromptメソッドですね。

var input = prompt("値を入力してください", "");
alert(input);//入力した文字列が表示される

ほかにもScriptUIを利用する方法もありますが、今回はこれで事足りるのでこれでいきます。
promptメソッドは入力した文字列を文字列型(String)オブジェクトとして値を受け渡すので、ちゃんと数値型に変換しておきます*1

文字列型から数値型への型変換

やりかたはいろいろありますが、僕が好きなのはparseIntメソッドとparseFloatメソッドです。前者は文字列を整数に丸めて数値型に変換し、後者は小数点を含めて浮動小数点(を含む数値)型に変換します。parseIntメソッドは第二引数に基数*2を指定するようにします*3
一方のparseFloatには基数は関係ないのでそんなもの不要です。

今回は小数点入力を許容したいので、parseFloatメソッドを採用します。

var input = prompt("値を入力してください", "");
if ( isNaN(parseFloat(input)) ) alert("半角数字と半角ピリオド以外は入力しないでください");
else input = parseFloat(input);
alert(input + "型:" + typeof input);

isNaNメソッドは、引数がNaN*4かどうかを調べるメソッドです。もし仮に入力された文字列に(parseIntメソッドやparseFloatメソッドで型変換しようとしても)数値にできないものが含まれているとき、結果がNaNになります。それを調べるためにisNaNメソッドで値を調べています。
入力した値を数値型に変換する処理はこんな感じでいきます。

水平方向に隙間なく並べる

これはこれまで通りPageItem.positionでもいいのですが、TwitterPageItem.leftなるプロパティがあると教わったので*5、それを使ってみようと思います。
さて、ここで「selectionメンバーの順序の壁」が立ちふさがりました。1回目の記事で調べた通り、Illustratorはオブジェクトの重なり順でselectionのメンバー順を決定しています。オブジェクトを水平方向に並べるにあたり、僕は一番左のオブジェクトを基準にしたいのです。

座標値からソートする

しかたない。ソートしよう。そうしよう。選択しているオブジェクトを左にあるものから順に取得するために、オブジェクトの左端のX座標を比較してソートする方法を考えます。
というわけで少し前の記事を引っ張り出します。こういうとき、ブログに書き残しておいてよかったわーってなります。

temp.sort(function(a,b){return a[0]-b[0] || a[1]-b[1]});

二重配列によるソートでフレームを座標順に取得する - DTPab

とはいっても今回は天地方向は関係ないので(リサイズして揃えているため)、論理演算子前までを関数に定義して、sortメソッドに渡してみます。

$.writeln("ソート前");
for (var i=0; i<mySels.length; i++){
    $.writeln(mySels[i].visibleBounds[0]);
}

//ソート方法を定義するメソッド
var sortLeft = function (a, b){
    return a.visibleBounds[0] - b.visibleBounds[0];
}

$.writeln("ソート後");
mySels.sort(sortLeft);
for (var i=0; i<mySels.length; i++){
    $.writeln(mySels[i].visibleBounds[0]);
}

実行した結果はこちら。

f:id:uske_S:20180516105329p:plain

ソートできたっぽい!

左から順に隙間なく並べる

やっとPageItem.leftプロパティの出番ですかね。

//選択したオブジェクト群を左から隙間なく並べる
for (var i=1; i<mySels.length; i++){
    mySels[i].left = mySels[i-1].visibleBounds[2];
}

PageItem.leftプロパティに、前のオブジェクトの右端の座標値を渡します。

今日のコード

var myDoc = app.activeDocument;
var mySels = myDoc.selection;

//0番目のオブジェクトの天のY座標
var targetY = mySels[0].visibleBounds[1];
//オブジェクトの高さを求める関数
var getHeight = function (obj){
    return Math.abs(obj.visibleBounds[3] - obj.visibleBounds[1]);
}

//最終的な仕上がり幅の入力を求める
var input = prompt("値を入力してください", "");
if (input === null) alert("キャンセルしました");
else if ( isNaN(parseFloat(input)) ) alert("半角数字と半角ピリオド以外は入力しないでください");
else {
    input = parseFloat(input);

    //基準とするオブジェクトの高さ
    var tgHeight = getHeight(mySels[0]);

    for (var i=1; i<mySels.length; i++){
        //変倍するオブジェクトの高さ
        var h = getHeight(mySels[i]);
        //比率計算(百分率)
        var p = (tgHeight / h) * 100;
        //リサイズ
        mySels[i].resize(p, p);
        //基準とするオブジェクトの天にあわせて整列
        mySels[i].position = [mySels[i].visibleBounds[0], targetY];
    }

    //ソート方法を定義するメソッド
    var sortLeft = function (a, b){
        return a.visibleBounds[0] - b.visibleBounds[0];
    }

    //選択したオブジェクト群を左から順にソート
    mySels.sort(sortLeft);

    //選択したオブジェクト群を左から隙間なく並べる
    for (var i=1; i<mySels.length; i++){
        mySels[i].left = mySels[i-1].visibleBounds[2];
    }
}

promptメソッドの部分ですが、キャンセルボタンが押されるとnullが戻ります。なので、キャンセルが押された場合はスクリプトは何もせず終了します。
半角数字や半角ピリオド以外の文字が入力され、parseFloatメソッドがNaNを返した場合も終了します。
そのどちらでもなければ、それ以下のコードを実行する流れです。

といっても、今回のコードでは入力した値をまだ使っていません。次回はこの入力した数値を元に、改めてリサイズしていくことにします。

*1:ExtendScript、もっといえばJavaScriptは動的に変数の型を変換してくれるので型変換は必ずしも必要ではありません。ここで変換するのはあくまで僕の好みです。ここには数値が入っているよ、というのを後々のために明確にしておきたいのです。

*2:2進数や16進数というときの「2」や「16」のこと。普通の数字として扱う場合は10進数なので「10」。

*3:parseIntメソッドの第二引数は任意です(省くことができます)が、0から始まる文字列が8進数になったり、xから始まる文字列が16進数になったりするので、意識的に基数を指定するほうが意図しない不具合を起こさずに済みます。基数の指定は超推奨です。

*4:非数(Not-A-Number)を表すグローバルプロパティです。詳細は他に譲ります。

*5:InDesignにそんなプロパティはない!