配列のsortメソッドをなんとかする
えー、先日は川へ泳ぎに行ったわけですが、川底の大きな石に左足をぶつけました。事故の傷跡です。ちょっと痛かったです。それはおいておいて、sortメソッドの引数について少々難しく考えすぎている様に感じる日々を過ごしておりました。だって、あんまり使っている所見ないですから。便利なのにw(わたしも使ってませんが…)という事で、今回のscriptさんはJavascriptの配列のソート機能をうまく利用するというのが目的です。ただし、単純にsortメソッドを解説しても右の耳から左の耳へとダイレクトスルーという結果になりかねません。ですから、実用的な例に盛り込む形でお届けしておこうと思います。
まず、こちら。イラストレータなのですが、rectangle(四角)が30個並んでいますね。これは単純に四角ツールで描いたものをちまちまと適当にコピーして作っています。これをスクリプトで順番にテキストフレームに変換して連番をコンテンツとして書き込んでいきます。
はい、見事にバラバラですねぇw当然です。コピーする時に適当にシャッフルしましたから。イラレさんではオブジェクトの順番は純粋に生成された順番に一致します。座標でどうのとか見てくれたりは決してしない不親切な子なのです。こんな時はオブジェクトをソートする必要があるわけです。
まずはコードを
var tgt = objectSorter(app.selection);
app.paste();
var txf = app.selection[0];
var tx = txf.contents;
txf.remove();
var tmp = tx.split("\r");
var tx = [], txx = [];
for (var k=0,m;k<tmp.length;k++){
txx = tmp[k].split("\t");
for (m=0;m<txx.length;m++) tx.push(txx[m]);
}
var tf = [];
for (var i=0;i<tgt.length;i++) {
tf[i] = app.activeDocument.textFrames.areaText(tgt[i]);
tf[i].contents = tx[i];
}
function objectSorter(tgs) { //tgs: objects
var ar = [];
for (var i=0;i<tgs.length;i++) ar.push(tgs[i]);
ar.sort(function(a,b) {
if (a.position[1]>b.position[1]) {
return -1;
}
else if (a.position[1]<b.position[1]) {
return 1;
}
else {
if (a.position[0]>b.position[0]) {
return 1;
}
else if (a.position[0]==b.position[0]) {
return 0;
}
else {
return -1
}
}
});
return ar
}
動作です。30個の四角を選択しておきます。そして、エクセルなどからタブと改行で区切られたテキストをコピーしてきます。その状態でスクリプトを実行するとオブジェクトがソートされテキストが意図通り左上から右下に向けて流されるというわけです。
コードではオブジェクトのソートから処理していますが、先にその他の部分を説明しておきます。
まず、クリップボードのテキストをシリアライズする処理から。
app.paste();
var txf = app.selection[0];
var tx = txf.contents;
txf.remove();
var tmp = tx.split("\r");
var tx = [], txx = [];
for (var k=0,m;k<tmp.length;k++){
txx = tmp[k].split("\t");
for (m=0;m<txx.length;m++) tx.push(txx[m]);
}
この部分ですね。AIでは直接クリップボードのデータをマニピュレートできる機能が実装されていません。ので、クリップボードのテキストをスクリプトに認識する手順として初めにペーストを行う必要があります。そして、ペーストを行うと選択された状態になるためにapp.selection[0].contentsを取得するとクリップボードに収納されていたデータを得ることができます。そして、必要なくなったこのテキストフレームはremoveメソッドで削除します。
さて、得られたテキストですが、タブと改行コード(今回はキャリッジリターン)で区切られています。これを一つの配列に収めてしまう作業が必要になります。乱暴に説明すると先ず改行で分割して、それぞれをタブで分割した配列を全部順番にくっつける。となります。splitメソッドで分割してループでpushメソッドを利用して一つの配列に統合する動作をさせています。変な分岐はありませんからコードを順番に追いかけてみてください。まあ、先にCR→Tabへ検索置換かけてもいいのですがね…(実は、そうするとコード構成がストレートになる)
ちなみにテキストはこんな感じです。
続いてはpathItemをareaTtextへ変換する処理です。
var tf = [];
for (var i=0;i<tgt.length;i++) {
tf[i] = app.activeDocument.textFrames.areaText(tgt[i]);
tf[i].contents = tx[i];
}
textFramesに対するareaTextというメソッドがあります。引数にpathItemを与えると、そのpathItemをエリアテキストに変換してれるものです。
実際のコードではループを利用して選択された四角全てをエリアテキストに変換し、先ほどのテキストデータを順番に書き込むといった処理を行います。
さて、最後にソートファンクションを
function objectSorter(tgs) { //tgs: objects
var ar = [];
for (var i=0;i<tgs.length;i++) ar.push(tgs[i]);
ar.sort(function(a,b) {
if (a.position[1]>b.position[1]) {
return -1;
}
else if (a.position[1]<b.position[1]) {
return 1;
}
else {
if (a.position[0]>b.position[0]) {
return 1;
}
else if (a.position[0]==b.position[0]) {
return 0;
}
else {
return -1
}
}
});
return ar
}
こちらがソートを受け持つ関数です。
まずsortメソッドの解説からやっつけちゃいます。今回の対象はpathItemsというオブジェクトです。こいつらの位置座標を対象にソートを行いたわけです。もちろん単純にsortメソッドを利用してもソートは行われません。ではどうするのかというとsortメソッドにカスタム関数を引数として与えて、その関数に定義された条件でソートをやってもらいます。
では、その関数ですが、どのように書くかというと…
fuction(a,b) {
if (a>b) return -1;
if (a==b) return 0;
if (a<b) return 1;
}
このサンプルではaがbより大きい場合-1を返し、aがbより小さい場合1を返します。また、a-bの場合は0を返すという具合になっています。この様にaをbより前に配置したい場合は負の数を、bをaより前に配置したい場合は正の数を返す事でソートを行います。勿論同じという場合も考慮する必要がありますから、その場合0を返すようにします。
これがカスタム関数の基本です。ではオブジェクトをソートするには…
function(a,b) {
if (a.position[1]>b.position[1]) {
return -1;
}
else if (a.position[1]<b.position[1]) {
return 1;
}
else {
if (a.position[0]>b.position[0]) {
return 1;
}
else if (a.position[0]==b.position[0]) {
return 0;
}
else {
return -1
}
}
}
ここで、引数a,bはpathItemsが入ることになります。これらをpositionプロパティを見て優劣をつけていきます。ご覧のように単純です。左上に位置するものほど前に来るように、まず見るのは縦です。もし、縦位置が同一であれば横位置を比較して優劣をつけます。処理的には頭から順番に追えば理解できると思いますので一度ステップインでチェックしてみるといいでしょう。
before – afterは冒頭のようになります。
今回はpositionプロパティでしたが、areaプロパティを利用すると大きさ順にソートできたりします。