DTPab

DTPにまつわるあれこれ

アイテムの存在するページの確認

あるアイテムがドキュメントの何ページ目にあるのかというのは、スクリプトを介して意外と簡単に取得できます。ただ、それが意図した結果かどうかというのは別の話です。スクリプトではよくある話ですね。
というわけで、まずは下図をご覧ください。

f:id:uske_S:20170813131325p:plain

見開きいっぱいのオブジェクトがA〜D、そしてマゼンタ色の正方形が3つあります。A〜Dはわずかに(肉眼ではわからないレベルで)数値を変えてあります(後述)。マゼンタの正方形はページマージンがオブジェクトのど真ん中になるように配置しています。
判型はA4なので、座標値は左ページの左端を0としたとき、ノドの中央が210mm、右ページの右端が420mmになります。また、ページ下部の数字がページのノンブルです。
さて、InDesignはこれらのオブジェクトを何ページ目にあると判断するでしょうか。スクリプトではオブジェクトのparentPageプロパティを参照することで、そのページを取得することができます*1。それと併せて、細かな座標値の確認もしてみます。それぞれに対して下記のスクリプトを実行してみましょう。

var s = app.activeDocument.selection[0], result = [];
result.push("【ページ】"+s.parentPage.name);
result.push("【座標値】左端:"+decRound(s.geometricBounds[1]));
result.push("【座標値】中央:"+decRound((s.geometricBounds[3]-s.geometricBounds[1])/2));
result.push("【座標値】右端:"+decRound(s.geometricBounds[3]));
$.writeln(result.join("\n"));
//四捨五入関数
function decRound (x){
    var hoge = Math.pow(10,2);
    return Math.round(x*hoge)/hoge;
    }

結果は下記の通りです。
A(幅420mm、中央に配置)
【ページ】2
【座標値】左端:0
【座標値】中央:210
【座標値】右端:420
B(Aを右に0.01mm右に移動したもの)
【ページ】3
【座標値】左端:0.01
【座標値】中央:210.01
【座標値】右端:420.01
C(Aを0.1mm右方向に大きくしたもの)
【ページ】2
【座標値】左端:0
【座標値】中央:210.05
【座標値】右端:420.1
D(Aを中心基準で0.1mm小さくしたもの)
【ページ】3
【座標値】左端:0.05
【座標値】中央:210
【座標値】右端:419.95
マゼンタ左
【ページ】2
【座標値】左端:-7.5
【座標値】中央:0
【座標値】右端:7.5
マゼンタ中央
【ページ】3
【座標値】左端:202.5
【座標値】中央:210
【座標値】右端:217.5
マゼンタ右
【ページ】3
【座標値】左端:412.5
【座標値】中央:420
【座標値】右端:427.5

この結果から、下記のようなInDesignの仕様?を見て取れます。

  1. parentPageとして取得されるページは、オブジェクトの中心の座標(Bなど)
  2. 中心の座標がページの境目ピッタリだと、ノンブルの大きい方になる(マゼンタ中央など)
  3. ただしオブジェクトの端点がノンブルの小さいページのページマージンに触れる場合は、中央座標が次のページに存在しても、parentPageはノンブルの小さい方のページとなる(C)

1と2については直感的に分かりやすいのですが、3についてはこういう仕様だ、と考えて納得するしかないですね。
余談ですが、先程のデータのノンブルをいじって左右の見開きを入れ替えると、parentPageで取得されたページ側にオブジェクトが移動します。

f:id:uske_S:20170813131340p:plain

さて、ここからが本題です。
例えば、目次や索引、また相互参照のような機能をスクリプトで実装しようとした場合、このparentPageのこうした仕様を把握しておかないと、冒頭に書いたように「意図した結果」が得られないことになるわけです。
例えばオブジェクトの配置してあるページ数を抽出して索引を作ろう、と仮定してみます。
先程の見開きから、左右ページにまたいであるものについては「ページ数の小さい方」を基準にページを抽出してください、という仕様だったらどうでしょうか。一律にparentPageで取得してしまうと、BやD、マゼンタの中央のものは3ページとして抽出され、スクリプトの結果が意図したものになりません。
また、parentPageはそのオブジェクトが存在するページオブジェクトを取得しますが、ペーストボード上(製版線の外)のオブジェクトに対してはparentPageはページオブジェクトではなくnullを返します。したがって、仮にペーストボードのオブジェクトもどのスプレッドにあるのか調べたいという場合は、中綴製作所さんのこの記事などを参考にしてください*2
で、実際どうすれば「意図した結果」が得られるかですが、純粋に座標値から判断するしかありません。オブジェクトであればgeometricBoundsvisibleBounds、テキスト類であればhorizontalOffsetなどで座標を得る必要があります。

ただ、このparentPageに頼らない方法は、かなり骨が折れます。単純にparentPageで目的が達成できればそれに越したことはありません。スクリプトの仕様を十分に検討し、どうしてもこの方法でないとまずい、という場合にのみ採用されると良いと思います。
実際にこの座標値基準でのページ取得ですが、だいたい下記のような事項について処理の検討が必要になります。

  • 単位設定の「定規の単位>開始位置」がスプレッドかそれ以外か(これが「ページ」や「ノド元」だと途端に作業負担が増えるので「スプレッド」を推奨)
  • 右開きか左開きか
  • オブジェクトがドキュメントの何スプレッド目にあるか
  • スプレッドに存在するページの数を確認(3ページ以上で構成された見開きも存在する可能性を加味する)
  • ページそれぞれのサイズ(幅)を確認(ページごとにサイズが違う可能性を加味する)
  • 対象となるオブジェクトの左側の座標、右側の座標などから何を基準にページとするか決める

思いつく範囲で列記してみました。まだ足りないような気もしますが、概ねこんなところでしょう…。
長くなってしまったので記事はこの辺で終えたいと思いますが、parentPageがどうこうというより、このInDesignの仕様(見開き上に存在するオブジェクトの帰属ページに関する仕様)は知っておくべきかなと思います。
最後に、索引抽出スクリプト、というところでは以下の照山さんの記事に面白いスクリプトがあります。興味のある方は中を覗いてみてください。

mottainaidtp.seesaa.net

以上、アイテムの存在するページの確認の仕方についての一考察でした。

*1:CS5以降のプロパティ

*2:オブジェクトのparentPageがnullを返すのかページオブジェクトを返すのかの基準は、オブジェクトの大半がペーストボードにあったとしても、ページの仕上がりに触れているどうかが基準です(オブジェクトの中央座標が基準ではありません)

parentPageのparent

ここんとこレジュメのスクリプトばっかりで遊んでないので、ちょっぴりハマったことをメモ。
ドキュメント中のテキストフレーム内の文字を選択した状態で、そのテキストフレームと同じページに新たにオブジェクトを作りたかった。

var myDoc = app.activeDocument;
var mySel = myDoc.selection[0];
var myIndex = mySel.parentTextFrames[0].parentPage.index;
myDoc.pages[myIndex].textFrames.add();

見開きドキュメントの3ページ目以降で実行すると、1ページ目か2ページ目にできてしまって、肝心のオブジェクトを選択しているページにフレームを作ってくれません。おっかしーなー?と思ったわけです。
調べればなんてことなくて、parentPageの親、つまりparentPage.parentはスプレッドオブジェクトなんですね。だからparentPage.indexを取得すると、そのスプレッドの何ページ目かを取得するだけで、ドキュメント全体の何ページ目かは取得できないのでした。
で、よくよく考えたら別にparentPageでページオブジェクトを取得できたんなら、わざわざindexを取得しないでもそのままページオブジェクトとして使えばいいよね??という、割とどうでもいいことで悩まされていたのでした…。

var myDoc = app.activeDocument;
var mySel = myDoc.selection[0];
var myPage = mySel.parentTextFrames[0].parentPage;
myPage.textFrames.add();

これで選択しているオブジェクトと同じページにテキストフレームが作れました。

オブジェクトのparent大事。

もくもく会#2レジュメ 解答編5「doScriptによる実行」

元の設問はこちら

今回はdoScriptを使った処理です。doScript自体はCS3から実装されていますが、いろいろ含めてCS4からが本格仕様です。ちなみにIllustratorのそれとは違い、InDesigndoScriptは文字通りスクリプトを(言語を指定して)実行できます。
この機能、とっても便利なので覚えておくといいです。UndoModesの設定次第では、Undoの履歴を残さないので処理させることができます。そうすると圧倒的に処理が早くなるのと、スクリプトを実行した後でも一度のUndoでスクリプト実行前に戻ることができます。
6.スクリプトによる修正を一度のUndoで戻れるようにdoScriptで実行する

app.doScript (main, ScriptLanguage.JAVASCRIPT, [], UndoModes.FAST_ENTIRE_SCRIPT);

function main (){
    var myDoc = app.activeDocument;
    var myTxf = myDoc.textFrames;
    var tgtBlack = myDoc.swatches.item('Black');
    for (var i=0; i<myTxf.length; i++){
        var myTab = myTxf[i].tables;
        for (var k=0; k<myTab.length; k++){
            var myCel = myTab[k].cells;
            for (var m=0; m<myCel.length; m++){
                if (!myCel[m].diagonalLineStrokeOverprint &&
                    myCel[m].diagonalLineStrokeColor == tgtBlack){
                    myCel[m].diagonalLineStrokeOverprint = true;
                    }
                if (!myCel[m].diagonalLineStrokeGapOverprint &&
                    myCel[m].diagonalLineStrokeGapColor == tgtBlack){
                    myCel[m].diagonalLineStrokeGapOverprint = true;
                    }
                }
            }
        }
    }

doScriptは、引数に「関数、または文字列」「スクリプト言語」「第一引数の関数に渡す引数(配列)」「UndoModes」という順序で指定します。今回は引数のない関数(main)を指定しているので、doScriptの第三引数は空の配列([ ])です。
もし引数がうまく渡せない場合は、第一引数に関数を文字列型として記述するという方法もあります。仮引数hogeを指定するなら

app.doScript ("main(hoge)", ScriptLanguage.JAVASCRIPT, [], UndoModes.FAST_ENTIRE_SCRIPT);

こんな感じです。まぁこの辺はフレキシブルでいいと思います。
ScriptLanguageの指定、UndoModesの指定は、お〜まちさんのディザInDesignドキュメントオブジェクトモデル図*1を見ていただくとよいです。特にScriptLanguageは、OSによってドキュメントオブジェクトビューワーに表示される言語が決まってしまいます。つまりMacではAppleScriptJavaScriptは見つかりますが、VisualBasicは参照できません。WindowsではAppleScriptの指定の仕方が参照できません。 ちなみに、前々回の記事(機能3)で斜線のオーバープリント処理を関数化しました。その場合はこんな感じになります。

app.doScript (main, ScriptLanguage.JAVASCRIPT, [], UndoModes.FAST_ENTIRE_SCRIPT);

function main (){
    var allDocs = app.documents;
    for (var x=0; x<allDocs.length; x++){
        //var myDoc = app.activeDocument;
        var myDoc = allDocs[x];
        var myTxf = myDoc.textFrames;
        var tgtBlack = myDoc.swatches.item('Black');
        for (var i=0; i<myTxf.length; i++){
            var myTab = myTxf[i].tables;
            for (var k=0; k<myTab.length; k++){
                var myCel = myTab[k].cells;
                for (var m=0; m<myCel.length; m++){
                    refineDiagonalLineOP (myCel[m], tgtBlack);
                    }
                }
            }
        }
    }

function refineDiagonalLineOP (tgtCell, tgtSwatch){
    if (!tgtCell.diagonalLineStrokeOverprint &&
        tgtCell.diagonalLineStrokeColor == tgtSwatch){
        tgtCell.diagonalLineStrokeOverprint = true;
        }
    if (!tgtCell.diagonalLineStrokeGapOverprint &&
        tgtCell.diagonalLineStrokeGapColor == tgtSwatch){
        tgtCell.diagonalLineStrokeGapOverprint = true;
        }
    }

関数の中で関数を呼び出すため、スコープ*2に注意してください。今回のケースでいうと、refineDiagonalLineOP関数からtgtBlack変数を参照できないため、第二引数にして渡しています。
以上がdoScriptを使った記述です。処理の高速化とUndoのコントロールができるので、使い勝手のいいスクリプトになります。ぜひ自分で作ったほかのスクリプトでも試してみてください。

*1:ページ内を「doscript」で検索すれば見つかります。まだ英語表記のままですが、そんなに難しくないでしょう…^^;

*2:JavaScriptの変数のスコープについては、MDNなどをご参照ください。なかなか簡単には説明しにくいので、もし分かりにくければ「関数の中から変数が見に行けない状態」という大雑把な感じで理解しておいてください。いずれ関数をたくさん書くようになると、スコープが煩わしいものからありがたいものになるはずですw