適当に並べられたオブジェクトを、左上→右下順にソートする方法を考える。
InDesignの場合、オブジェクトには
visibleBounds
(見た目上、線幅等を含めたサイズ)と、
geometricBounds
(線幅等を含めないサイズ)の数値が、
配列で [<上のy値>,<左のx値>,<下のy値>,<右のx値>]
として入っている。
var sel = app.activeDocument.selection[0]; // 選択オブジェクトの1個目
$.writeln(sel.visibleBounds); // -> 40.5,34.2499999999008,59.5,52.7499999998016
$.writeln(sel.geometricBounds); // -> 41,34.7499999999008,59,52.2499999998016
今回は visibleBounds
を使用する。
シンプルにするために、y方向の並び順についてだけ考える。
各オブジェクトの上辺にシアンの接線を、下辺にマゼンタの接線を引く。
行になるのは、この範囲。 一番上にある上辺 から、 一番下にある下辺 まで。
さっきと同じように、上辺にシアンの接線を、下辺にマゼンタの接線を引く。
つまり、行になるのは 下辺の次に現れる上辺 と、次に上辺が現れる前の下辺 まで。
そう考えると、
- 配列に、上辺の値と下辺の値を、上辺か下辺かのフラグをつけて収集
- それらを位置でソートする
- 下辺の次に現れる上辺と、次に上辺が現れる前の下辺の位置を収集
で、各行の範囲が取れるはず。
スクリプトを書く。
(function () {
var ranges = getRowRanges(app.activeDocument.allPageItems);
// $.writeln(uneval(ranges));
}());
function getRowRanges(objs) {
// オブジェクトの配列から、行と列の範囲を返す
var yPositions = [], bounds, i, len;
for (i = 0, len = objs.length; i < len; i++) {
bounds = objs[i].visibleBounds;
// 上辺と下辺の位置を収集。0...上辺、1...下辺
yPositions.push([0, bounds[0]]); // 上辺
yPositions.push([1, bounds[2]]); // 下辺
}
return getRanges(yPositions);
}
function getRanges(positions) {
// 収集したポジションから、行の範囲を返す
var ranges = [],
tmpRange = [],
last, i;
//位置でソート
positions.sort(function (a, b) {
return a[1] - b[1];
});
//最初の上辺の値をセット
tmpRange[0] = positions[0][1];
for (i = 1, last = positions.length - 1; i < last; i++) {
// 今の値が下辺で、次が上辺だったら
if (positions[i][0] && !positions[i + 1][0]) {
// 下辺の値をセットしてpush
tmpRange[1] = positions[i][1];
ranges.push(tmpRange);
// 次の上辺の値をセット
tmpRange = [positions[i + 1][1]];
}
}
//最後の下辺の値をセットしてpush
tmpRange[1] = positions[last][1];
ranges.push(tmpRange);
return ranges;
}
これで、rangesにこんな感じのデータが返る。
[
[35.7499999997482,52.9999999994964],
[55.5,65.4999999997482],
[68.7499999997482,73.9999999997482],
[79.9999999997482,92.5],
[95.5,110],
[112.749999999748,134.249999999748]
]
列方向も同じ考えで良いので、行と列を一緒に処理するように変更。
(function () {
var ranges = getRowAndColRanges(app.activeDocument.allPageItems);
// $.writeln(uneval(ranges));
}());
function getRowAndColRanges(objs) {
// オブジェクトの配列から、行と列の範囲を返す
var yPositions = [], // 上辺と下辺
xPositions = [], // 左辺と右辺
bounds, i, len;
for (i = 0, len = objs.length; i < len; i++) {
bounds = objs[i].visibleBounds;
yPositions.push([0, bounds[0]]); // 上辺
xPositions.push([0, bounds[1]]); // 左辺
yPositions.push([1, bounds[2]]); // 下辺
xPositions.push([1, bounds[3]]); // 右辺
}
return {
"row": getRanges(yPositions),
"col": getRanges(xPositions)
};
}
function getRanges(positions) {
// ... 上記参照 ...
}
rangesに返るデータはこんな感じ。
{
"row":[
[35.7499999997482, 52.9999999994964],
[55.5, 65.4999999997482],
[68.7499999997482, 73.9999999997482],
[79.9999999997482, 92.5],
[95.5, 110],
[112.749999999748, 134.249999999748]
],
"col":[
[30.7499999999008, 43.9999999998512],
[44.9999999999008, 52.7499999998016],
[54.7499999999008, 56.7499999999008],
[63.5, 64.5],
[68.5, 69.5],
[75.4999999999008, 90.9999999999008],
[95.5, 106.749999999901],
[109.5, 116.999999999901]
]
}
あとは、重なり判定して位置を出し、ソートする。
(function () {
var selObjs = app.activeDocument.allPageItems;
var ranges = getRowAndColRanges(selObjs);
// オブジェクトをZ順にソート
var zSortFunc = function (a, b) {
var aData = getPosition(a, ranges),
bData = getPosition(b, ranges);
if (aData.row == bData.row) {
return aData.col - bData.col;
}
return aData.row - bData.row;
}
selObjs.sort(zSortFunc);
for (var i = 0, len = selObjs.length; i < len; i++) {
// フレームに番号を入れる
selObjs[i].contents = (i + 1).toString();
}
}());
function getPosition(obj, rowAndColRanges) {
// オブジェクトのZ順での位置
var bounds = obj.visibleBounds;
var data = {};
for (var r = 0, rLen = rowAndColRanges["row"].length; r < rLen; r++) {
var rowRange = rowAndColRanges["row"][r];
// オブジェクトが行に重なっていたら
if (bounds[0] < rowRange[1] && bounds[2] > rowRange[0]) {
data["row"] = r; break;
}
}
for (var c = 0, cLen = rowAndColRanges["col"].length; c < cLen; c++) {
var colRange = rowAndColRanges["col"][c];
// オブジェクトが列に重なっていたら
if (bounds[1] < colRange[1] && bounds[3] > colRange[0]) {
data["col"] = c; break;
}
}
return data;
}
function getRowAndColRanges(objs) {
// ... 上記参照 ...
}
function getRanges(positions) {
// ... 上記参照 ...
}
このスクリプトを実行すると、左上→右下順にオブジェクトに番号が入る。