InDesignで、オブジェクトを左上→右下の順にソートして、番号付けする。

![7156B44923668FB6C7DA2A5E6F9352A9.png](https://qiita-image-store.s3.amazonaws.com/0/3956/89b90daa-c410-2764-f618-5168c332d5e2.png)

適当に並べられたオブジェクトを、左上→右下順にソートする方法を考える。
![ss 2018-07-08 8.51.17.png](https://qiita-image-store.s3.amazonaws.com/0/3956/5bdd43b7-b6d7-2817-689f-5c08147cbec2.png)

InDesignの場合、オブジェクトには
`visibleBounds`（見た目上、線幅等を含めたサイズ）と、
`geometricBounds`（線幅等を含めないサイズ）の数値が、
配列で `[<上のy値>,<左のx値>,<下のy値>,<右のx値>]` として入っている。

```javascript
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方向の並び順についてだけ考える。
![6F57CC8294AFE0B6DE6696E76305CB8D.png](https://qiita-image-store.s3.amazonaws.com/0/3956/0729c304-91eb-99d7-b9af-7d04066c9ea8.png)

各オブジェクトの上辺にシアンの接線を、下辺にマゼンタの接線を引く。
![0571A3E25000CD45A1531785386A67D3.png](https://qiita-image-store.s3.amazonaws.com/0/3956/090b65f3-0923-329a-a21a-31f801523f54.png)

行になるのは、この範囲。 **一番上にある上辺** から、 **一番下にある下辺** まで。
![0318E64C91E2B64AA1B9DF4DE7CC9F0A.png](https://qiita-image-store.s3.amazonaws.com/0/3956/31b25098-a98a-b75b-e92d-0f00d1e26d01.png)

次に、こういう並びだった場合を考える。
![95FA7452064FB6BFBA4801C8185C37D9.png](https://qiita-image-store.s3.amazonaws.com/0/3956/ad35db99-1a83-7ec2-7306-309f0a74213d.png)

さっきと同じように、上辺にシアンの接線を、下辺にマゼンタの接線を引く。
![A10A8FABCF4D93B616C537CB70A8A73B.png](https://qiita-image-store.s3.amazonaws.com/0/3956/e7d5f0f3-ce11-bdf3-36a0-19d17fa375c0.png)

行になるのは、この範囲。今度は2行になる。
![62CF365314F27DC880F09412C07EA286.png](https://qiita-image-store.s3.amazonaws.com/0/3956/3a46ab89-3b6c-292d-01ec-ee70841ce744.png)

つまり、行になるのは **下辺の次に現れる上辺** と、**次に上辺が現れる前の下辺** まで。
そう考えると、

1. 配列に、上辺の値と下辺の値を、上辺か下辺かのフラグをつけて収集
2. それらを位置でソートする
3. 下辺の次に現れる上辺と、次に上辺が現れる前の下辺の位置を収集

で、各行の範囲が取れるはず。
スクリプトを書く。

```javascript
(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にこんな感じのデータが返る。

```json
[
  [35.7499999997482,52.9999999994964],
  [55.5,65.4999999997482],
  [68.7499999997482,73.9999999997482],
  [79.9999999997482,92.5],
  [95.5,110],
  [112.749999999748,134.249999999748]
]
```

列方向も同じ考えで良いので、行と列を一緒に処理するように変更。

```javascript
(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に返るデータはこんな感じ。

```json
{
  "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]
  ]
}
```

塗り分けると、こんな感じになる。
![D3B92022EA8ABF7D4DFF122EF7A10F02.png](https://qiita-image-store.s3.amazonaws.com/0/3956/9b0446b0-14c2-fb3d-b8e8-534825e5df1b.png)

あとは、重なり判定して位置を出し、ソートする。

```javascript
(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) {
    // ... 上記参照 ...
}
```

このスクリプトを実行すると、左上→右下順にオブジェクトに番号が入る。

![5A152D72EA4E114D61AB157D1C0055F9.png](https://qiita-image-store.s3.amazonaws.com/0/3956/724cfbdd-4f63-f42f-57d0-297e8537239e.png)