Hatena::ブログ(Diary)

chalcedonyの外部記憶装置・出張版 RSSフィード

2015-07-03

InDesignのパネルメニューをJavaScriptから実行するときはパネルがvisibleな必要がある

タイトルで用は済んでるのですが、忘れないようにもう少しメモしておきます。

以下、OSX10.9 + CS6を前提に書いたものです。



直接DOMをいじるよりInDesignのメニューコマンドを叩いてしまうほうが処理が楽なことって結構ありますが、パネルメニューの場合はそのパネルが表示状態になってないと動きません。

表示状態というのはパネルのメニューボタンが押せる状態のことで、他のパネルとくっつけてタブ表示された状態であっても、それが背面になっていたら動かないってことです。

なので、MenuActionオブジェクトをたどるだけじゃなくて、パネルの表示状態も変更する必要があります。

// サンプル
// 各スタイルで未使用の項目をすべて削除する
// 空のドキュメントだと[基本○○]とかを消そうとしてエラーになるので注意

(function(){

    // パネル名を処理したい順に並べておく
    var arr = ["オブジェクトスタイル", "表スタイル", "セルスタイル", "段落スタイル", "文字スタイル"];

    for(var i = 0, len = arr.length; i < len; i++){

      app.panels.itemByName(arr[i]).visible = true; // ★ここで表示状態にしてる

      var menu = app.menus.itemByName(arr[i] + "パネルメニュー");

      menu.menuItems.itemByName("未使用をすべて選択").associatedMenuAction.invoke();
      // 未使用のものがなかったらエラーになるのではじいておく
      try{
        menu.menuItems.itemByName("スタイルを削除...").associatedMenuAction.invoke();
      } catch(e){}

    }

}());

リハビリ的エントリでした。

2015-02-26

InDesignのDOMをprototypeで拡張するメモ2

昨日かいたやつがeveryItem()やitemByRange()で取得したオブジェクトに対応できてなかったことに気付きまして。

twitterで騒いだところ、判別しないで対応すればいいじゃないと教えていただきました。

https://twitter.com/peprintenpa/status/570915349459136512

というわけで、書き直し。

(function(){

  // Paragraph拡張
  // 引数に指定した数値ずつサイズを下げる。単位は無視して数値しか見ない
  // 成功するとtrueを返す
  Paragraph.prototype.downSize = function(num){
    var arr = this.getElements();
    
    for( var i=0, len = arr.length; i<len; i++ ){
      var tmp = arr[i];
      var origin = tmp.pointSize;
      var size;
      if( num.constructor.name != "Number" ){ throw "downSize Error: 引数が数値ではありません"; }

      size = origin - num;
      if( size <= 0 ) { throw "downSize Error: これ以上サイズを下げられません"; }

      tmp.pointSize = size;
    } // ループ終わり
 
    return true;
  }


  // 例:全ページの「target」って名前のテキストフレーム内の全段落のサイズを1下げる
  try {
      app.activeDocument.pages.everyItem().textFrames.item("target").paragraphs.everyItem().downSize(1);
  }
  catch(e) { }


}());

最近everyItem()の便利さにちょっと目覚めました。

2015-02-25

InDesignのDOMをprototypeで拡張するメモ

自分で書かないと忘れるのでメモしておきます。

たとえばParagraphに「指定した数値だけポイントサイズを下げる」ってメソッドを生やすとか。

(function(){

  // Paragraph拡張
  // 引数に指定した数値ずつサイズを下げる。単位は無視して数値しか見ない
  // 成功するとtrueを返す
  Paragraph.prototype.downSize = function(num){
    var origin = this.pointSize;
    var size;
    if( num.constructor.name != "Number" ){ throw "downSize Error: 引数が数値ではありません"; }

    size = origin - num;
    if( size <= 0 ) { throw "downSize Error: これ以上サイズを下げられません"; }

    this.pointSize = size;
    return true;
  }

  // 例:段落が一行におさまるまで0.5ずつ文字を小さくする
 // エラー投げられたら止まるようにしておく
  var p = app.selection[0].paragraphs[0];
  while(p.lines.length > 1){
    try {
      p.downSize(0.5);
    }
    catch(e) {
      alert(e);
      break;
    }
  }

}());

エラー処理はてきとーです。

Paragraph(インスタンス)のpointSizeの値しか見てないので、段落内で文字サイズが違う場合対応できない。実験だとpointSizeは最初の文字のサイズが返ってきてるみたい。

2014-08-06

InDesignの段落相互参照をJavaScriptで作る

はいこんばんは。

相互参照の作り方がけっこうめんどくさかったので自分用にメモ。ついでに実験。

10.9 + CS6でしか動かしてません。あと、テキストアンカーへの参照は別の作り方するはずです。調べてない。


必要なものは

  • 参照先ドキュメント (A)
    • 参照先マーカー挿入箇所(InsertionPoint) (A-1)
    • 段落参照先オブジェクト(ParagraphDestination) (A-2)
  • 参照元(ソース)ドキュメント (B)
    • 相互参照形式(CrossReferenceFormat) (B-1)
    • 相互参照挿入箇所(Text系オブジェクト) (B-2)
    • 相互参照ソースオブジェクト(CrossReferenceSource) (B-3)

var destinationDoc = app.documents.item("destination.indd"); // A
var sourceDoc = app.documents.item("source.indd");           // B

var destinationPoint = destinationDoc.(DOMツリー).insertionPoints[0];         // A-1
var destination = destinationDoc.paragraphDestinations.add(destinationPoint); // A-2
var format = sourceDoc.crossReferenceFormats.item("formatName");              // B-1
var sourseText = sourceDoc.(DOMツリー).texts[0];                              // B-2
var source = sourceDoc.crossReferenceSources.add(sourseText, format);         // B-3

// 相互参照挿入
sourceDoc.hyperlinks.add(source, destination);

で、たとえばこんなのを作れるねっていう思いつき。変数名は上のと一緒にしてます。

// 選択中のテキストと同じ文言をドキュメント内から検索し、まとめて段落相互参照を設定します。
// 辞書系ドキュメントの見出し語を選択して実行→本文中の同じ文言が全部相互参照になるとか。
// 単なるテキスト検索なので精度を上げるには工夫が必要。あくまで例です。

var destinationDoc = app.activeDocument;
var sourceDoc = app.documents.item("source.indd"); // 相互参照を挿入したいほうのドキュメント

var destinationPoint = app.selection[0].insertionPoints[0];
var destination = destinationDoc.paragraphDestinations.add(destinationPoint);
var format = sourceDoc.crossReferenceFormats.item("見出し参照"); // 相互参照形式は事前に作っておく

// テキスト検索
app.findTextPreferences = NothingEnum.nothing;
app.changeTextPreferences = NothingEnum.nothing;
app.findTextPreferences.findWhat = app.selection[0].contents;
app.findTextPreferences.appliedParagraphStyle = "本文"; // 念のため段落スタイル指定

var foundTexts = sourceDoc.findText(); // 検索実行
for (var i = 0, len = foundTexts.length; i < len; i++) {
  var sourseText = foundTexts[i];
  var source = sourceDoc.crossReferenceSources.add(sourseText, format);

  sourceDoc.hyperlinks.add(source, destination); // destinationは使い回せる
}

使いどころは限定されると思う。


実際のところ、私が使ったのは逆パターン(選択したテキストから参照先の見出しを探す)のほうだったりします。

そもそも相互参照じたい使ってる人少なそうだけどねー。

2013-07-03

選択した文字列を索引項目に登録するJavaScript(読み仮名自動入力、CS3〜)

はいこんばんは。

InDesignで索引項目を追加するときはソートのために読み仮名を入力しなくてはならないのですが、手打ちするのがあまりにもめんどくさいので自動的に取得するスクリプトを作りました。

読み仮名の取得にはみんな大好きYahoo!のテキスト解析WebAPIを利用しています。

これを

f:id:chalcedony_htn:20130703213940p:image

こうします

f:id:chalcedony_htn:20130703213941p:image


あくまで自分用に作ったものなので、

  • 参照形式はデフォルトの「現在のページ」のみ
  • 項目のレベルは設定できない(すべてレベル1になる)

という仕様になっています。

あと、APIのアプリケーションIDは消してありますので、もし使用する場合は自分のアプリケーションID(ランダムな英数字になってます)を取得して、「◆◆◆ココにアプリケーションIDを書く◆◆◆」のところに入れてください(1箇所だけです)。YahooのIDを作れば誰でも取得できます。

ちょっと試してみたいという人にはだいぶ不親切ですがご了承ください><


このスクリプトを書くにあたって、2つの記事を大いに参考にさせていただいています。


動作確認は、WinXP + CS4、WinXP + CS5.5、Win7 + CS5.5、MacOSX 10.7.5 + CS3、MacOSX 10.5.8 + CS3、MacOSX 10.5.8 + CS4で行いました。Macでの動作確認にご協力いただいたお二方、ありがとうございました! 遅れてすいません!


■使用前の確認事項

  • なにが起きても泣かないようにデータのバックアップを取りながら使用してください(最重要)。
  • インターネットへアクセスできる環境が必要です。
  • Yahoo!の「日本語形態素解析API」を使用しています。サービスが終了した場合、スクリプトも使用できなくなります。
  • 選択中の文字列をインターネット経由でYahoo!のサービスへ送信します。機密情報などが漏洩した場合でも責任は持ちません。
  • アクセスログなどを見れば、どんな文字列を送信したか誰でもわかります。
  • 記号類は読み仮名から削除されます。

■使用中の注意事項

  • APIが期待通りの読み仮名を返してくるとは限らないので、必ず1件ずつ確認しながら登録してください。
  • 登録直後は索引項目のページ数が表示されません。索引パネルのメニューから[プレビューを更新]を実行すると直ります。
  • 索引登録時、ドキュメントの最初のページ付近にダミーのテキストフレームが作られます。通常は自動的に削除されますが、スクリプトが途中で止まった場合などに残る可能性があります。ひととおり登録したら、フレームが残っていないか確認してください。あったら消してね。
  • なにが起きても泣かないようにデータのバックアップを取りながら使用してください(念押し)。

// 索引登録支援ツール(InDesign CS3〜)
// Yahoo!のテキスト解析APIを利用して索引の読み仮名入力を自動化します。
// http://developer.yahoo.co.jp/webapi/jlp/
//
// 以下の記事をパク^H^H大いに参考にさせていただいています。
// kmutoさん
// via http://d.kmuto.jp/20120912.html
// CLさん
// via http://d.hatena.ne.jp/C_L/20081012/indesign_socket_http
//
// v0.9 2013/04/03
// v1.0 2013/06/03  デフォルトスタイルの初期化処理追加、自分で使用開始
// v1.1 2013/06/17  初期化処理をやめてダミー文字のサイズだけ指定する形に変更
// 
// NYSL http://www.kmonos.net/nysl/
// ==============================================================================


main();


// メインの処理
function main(){

  //Yahoo!APIのアプリケーションID
  var myAppID = "◆◆◆ココにアプリケーションIDを書く◆◆◆";

  // 選択状態チェック(テキストオブジェクトを選択してる状態のみ動作)
  if(app.selection.length == 0 || !app.selection[0].constructor.name.match(/^(Text|Word|Character|Paragraph|Line|TextColumn|TextStyleRange)$/)){
    alert("索引登録可能なテキストを選択してください。");
    return false;
  }

  var actDoc = app.activeDocument;
  var idx = (actDoc.indexes.length > 0) ? actDoc.indexes[0] : actDoc.indexes.add();

  var targ = app.selection[0]; // 選択中のテキスト
  var title = targ.contents;   // 索引項目になる文言

  // APIリクエスト
  var yapiObj = new YAPIReading();
  yapiObj.appid = myAppID;
  var reading = yapiObj.getReading(title); // 読みがなになる文言

  // 表示ダイアログ準備
  // ダイアログで読みがなを修正可能
  var dlg = createDialog();
  dlg.tf.text = title;   // 索引項目欄に入力
  dlg.rf.text = reading; // 読みがな欄に入力
  // 読みがなが空文字(APIがエラー返してきてる)だったらメッセージを上書きする
  if(reading == ""){ dlg.info.text = "読みがなの自動取得に失敗しました。直接入力してください。"; }

  // ダイアログ表示から登録実行
  // 登録ボタンを押すと、その時点の読みがな欄のテキストを読みがなとして登録
  if(dlg.show() == 1){
    title = dlg.tf.text;
    reading = dlg.rf.text;

    try{
      embedIndex(idx, targ, title, reading); // 登録実行
    } catch(e) {
      alert("登録に失敗しました。\n索引項目または読みがなに使えない文字がないか確認してください。");
      arguments.callee();
    }

    return true;
  }

  return false; // 登録しなかったらfalse返すことにしておく(なんとなく)
}





// ***************************************************************************
//
// 以下、関数・オブジェクト定義など
//
// ***************************************************************************


// 索引を追加する関数 ********************************************************
// kmutoさんのアイディア(マーカーのコピペ)を拝借
// via http://d.kmuto.jp/20120912.html
// ***************************************************************************
function embedIndex(index, target, title, reading) {

  // ダミーのテキストフレームを作って★マークとか入れておく
  var dummyFrame = index.parent.pages[0].textFrames.add();
  dummyFrame.geometricBounds = [0, 0, 50, 50]; // サイズは適当
  dummyFrame.insertionPoints[0].pointSize = 1; // 文字あふれ対策にサイズを小さくしておく
  dummyFrame.contents = "★";

  // 項目追加
  // ダミーの★マークのところに索引マーカーを入れる
  index.topics.add(title, reading).pageReferences.add(dummyFrame.characters[0]);

  // マーカー文字をカット&ペーストして正しい位置に移動
  dummyFrame.characters[0].select();
  app.cut();
  target.insertionPoints[0].select();
  app.pasteWithoutFormatting(); // フォーマットなしでペースト

  // ダミーのフレームを始末
  dummyFrame.remove();
}



// ダイアログオブジェクトを作って返す関数 ************************************
// あとで部品にアクセスしやすいようにショートカット作ってある
// dlg.tf   : 項目入力欄
// dlg.rf   : 読みがな入力欄
// dlg.info : 情報欄
// ***************************************************************************
function createDialog(){
  var dlg = new Window("dialog", "索引登録");
  dlg.orientation = "row";
  dlg.alignChildren = "top";

  var inputG = dlg.add("group");
  inputG.orientation = "column";
  inputG.alignChildren = "left";
  var infoLabel = inputG.add("statictext", undefined, "読みがなを修正してください。キャンセルすると登録を中止します。");

  var inputTitle = inputG.add("group");
  inputTitle.orientation = "row";
  var titleLabel = inputTitle.add("statictext", undefined, "索引項目:");
  var titleField = inputTitle.add ("statictext", undefined, undefined);
  titleLabel.characters = 9;
  titleField.characters = 35;

  var inputReading = inputG.add("group");
  inputReading.orientation = "row";
  var readingLabel = inputReading.add("statictext", undefined, "読みがな:");
  var readingField = inputReading.add ("edittext", undefined, undefined);
  readingLabel.characters = 9;
  readingField.characters = 35;

  var buttonG = dlg.add("group");
  buttonG.orientation = "column";
  buttonG.add("button", undefined, "登録", {name: "ok"});
  buttonG.add("button", undefined, "キャンセル", {name: "cancel"});

  // ショートカット定義
  dlg.tf = titleField;
  dlg.rf = readingField;
  dlg.info = infoLabel;

  return dlg;
}



// 読みがな取得用クラス定義 **************************************************
// アプリケーションIDはインスタンス側で設定する
//
// var hoge = new YAPIReading();      // インスタンス作成
// hoge.appid = "★★アプリケーションID★★"; // 自分のアプリケーションIDを指定する
// var title = "僕の妹は漢字が読める";     // 読みたい文言
// var reading = hoge.getReading(title);     // 解析結果を取得
//
// ってする
// 記号類はfilterで除去。これも変更可能
// 読みがな部分の抽出はテキストそのまま正規表現でぶっこぬき。XML解析なにそれおいしいの
// ***************************************************************************
function YAPIReading(){
  this.appid    = "";
  this.filter   = "1|2|3|4|5|6|7|8|9|10|11|12";
  this.response = "reading";
  this.results  = "ma";

  this.rex = /<reading>(.*?)<\/reading>/g; // <reading>要素を見つける正規表現(gオプション付き)

  this.getReading = function(sentence){
    var reading = "";

    var requestURI = 'http://jlp.yahooapis.jp/MAService/V1/parse?'
                   + 'appid=' + this.appid
                   + '&ma_filter=' + this.filter
                   + '&response='  + this.response
                   + '&results='   + this.results
                   + '&sentence='  + encodeURI(sentence);

    var lwp = new Lwp();
    var result = lwp.get(requestURI);

    // gオプション付きRegExpオブジェクトのexecループ
    // <reading>要素を見つけるたびに中身のテキストを足していく
    var m;
    while (m = this.rex.exec(result)) {
      reading += m[1];
    }
    return reading; // リクエストに失敗した場合はreadingタグがないので空文字になってるはず
  }
  return this;
}


// HTTPアクセス(GET)用クラス定義 *******************************************
// CLさんのモジュールを微改造
// via http://d.hatena.ne.jp/C_L/20081012/indesign_socket_http
// prototypeをやめてUser-Agentをそれっぽくしただけ
// ***************************************************************************
function Lwp() {
  this.userAgent = "InDesign/" + app.version + " (InDesign " + app.version + "; " + $.os + "; ja)";
  this.uri = function(uri) {
    var rex = new RegExp('http://([^:/]+)(?::(\d+))?(.+)');
    // via http://pc11.2ch.net/test/read.cgi/php/1015692614/57
    var urlObj =[];
    if ( uri.match(rex) ) {
      urlObj.host = RegExp.$1;
      urlObj.port = RegExp.$2 ?RegExp.$2 :80;
      urlObj.path = RegExp.$3;
    }
    return urlObj;
  }
  this.get = function (uri) {
    var conn = new Socket;
    var urlObj = this.uri(uri);
    if ( conn.open(urlObj.host + ':' + urlObj.port, 'UTF-8') ) {
      conn.write ("GET " + urlObj.path + " HTTP/1.0\n" + "Host: " + urlObj.host + "\n" + "User-Agent: " + this.userAgent + "\n\n");
        var reply = conn.read(999999);
        conn.close();
      return reply.substring(reply.indexOf("\n\n") + 2);
    }
  }
  return this;
};