DTPab

DTPにまつわるあれこれ

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

ちょっと躓いたので備忘録も兼ねて残しておきます。ちょっとだけツイートした二重配列のソートについてです。
二重配列と配列に対するsortメソッドについては既に知っているよ、という方は本題まで読み飛ばしてください。以下、本題までおさらいを兼ねて文字にしてます。

二重配列とは

そもそも二重配列(二次元配列ともいう)とは、下記のような配列を指します。

var arr = [ [0,1], [1,3], [2,4] ];

要するに配列の要素に配列が入っている状態です。取り出すときはこんなふうにします。

alert (arr[2][1]); //結果は4

sortメソッド

配列を並び替えるメソッド(関数)で、もともとの本家JavaScriptが持っているものです。詳細はMDN等を参照ください。
一応少しだけ触れておくと、例えばこんなふうに使います。

//数値を比較する場合
var arr = [ 4, 1, 2, 6, 8, 11, 9 ];
arr.sort (function (a,b){return a-b});
//結果は1,2,4,6,8,9,11
//※文字列を比較してソートする場合はまた別の関数を用意する必要がある

sortメソッドに、2つの引数を持つ関数を渡すのが一般的です。
関数function (a,b)の結果が正となるか、負となるか、0なのか、で配列の並び順をコントロールします。なのでここに記述する関数がsortメソッドの肝です。

論理演算子||

後で出てくる論理演算子OR(||)について、先にちょっと説明しておきます。これは論理演算子の一つで、俗に「論理OR」などと呼ばれます。if文などでお馴染みのやつですね。
この論理演算子は、AもしくはB、というときの「もしくは」と近い結果を返すことは、if文を使ったことがあればなんとなくわかると思います。

var A = 10, B = 5;
alert(A > B || A < B);//結果はtrue

この例では「AがBより大きい場合」もしくは「AがBより小さい場合」と、A=Bでない限りtureが返ることになります(AとBを同値にすると、結果はfalseです)。
ではこういうケースではなんと表現すればいいでしょうか。

var A = 10, B = 5, C = 2;
alert (A-B || B-C);

この場合の論理演算子ORの式の評価のしかたを言葉で説明しようとすると、先ほどのように「もしくは」とはちょっと言いにくいですね。
この論理演算子ORは、やっぱり「演算子」なのです。四則演算の+-のように頭から順に処理を行うと考えてみてください。
まずA-Bの式の評価を行います。仮にA>Bだった場合、式の結果は正の値を取ります。A=Bであれば値は0ですし、A<Bであれば負になります。その時の論理ORは何を返すでしょうか。こんな実験をしてみるとわかりやすいかもしれません。

alert (-1 || 1); //-1
alert (0 || 1); //1
alert (2 || 1); //2
alert (-5 || -1); //-5

この結果を見て納得できれば論理ORの扱いはバッチリです。そうでなければ、Falsyたちと仲良くなる必要があります。
Falsyとは、Booleanコンテクスト(要するに真偽値に型変換する場合)にfalseと判定される値のことです。具体的には、false0""(空の文字列)、nullundefinedおよびNaNです。これら以外はすべてtrueと判定されます(それらをTruthyと呼びます)。
したがって、先ほどの実験では0のみがFalsyです。論理演算子で評価するとfalseと判定され、次の値を評価することになり、結果は1が返るわけです。

本題

さて、ここまで説明してきましたが、結局何をやりたかったかというと、選択したテキストフレームを座標順に取得したかったのでした。
f:id:uske_S:20170825223912p:plain
例えばこんな感じの順番でテキストフレームを取得したいけど、InDesignallPageItemsはレイヤー的に上のものから取得するようで、下記のようなスクリプトでは末尾から取得するみたいです。

var s = app.activeDocument.pages[0].allPageItems;
var temp = [];

for (var i=0; i<s.length; i++){
    temp.push(
        [s[i].geometricBounds[0], 
        s[i].geometricBounds[1], 
        s[i].contents]);
    $.writeln(s[i].contents);
    }

結果はこんな感じ。
f:id:uske_S:20170825223924p:plain

二重配列をソートする関数

定規の基準を左上とした場合(左→右、上→下に座標の値が大きくなる)のソート関数を加えたものが下記になります。

var s = app.activeDocument.selection;
var temp = [];

for (var i=0; i<s.length; i++){
    temp.push([
        s[i].geometricBounds[0], 
        s[i].geometricBounds[1], 
        s[i].contents]);
    }

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

sortメソッドの関数にあるreturn文の式は、テキストフレームのY座標の天を比較し(a[0]-b[0])、同値であればX座標を比較します(a[1]-b[1])。条件を追加する場合は、論理ORで式をつなぐだけです。実質的に3次以上の多次元配列もこの形式で対応することができます。

以上、二重配列のソートを使った、フレームの座標順取得でした。