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){}

    }

}());

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

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()の便利さにちょっと目覚めました。

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は最初の文字のサイズが返ってきてるみたい。

Adobe ExtendScriptのプリプロセッサディレクティブ

ExtendScriptって名前、検索しづらいにもほどがあると思います。どうもこんばんは。

スクリプトをいくつかのファイルに分割したり、単機能のライブラリとして読み込んだりしたいとき、#includeディレクティブ(指示文)を記述するとインクルードすることができます。
他にも#targetとか#targetengineだとかあって、使うたびに調べてて面倒になったのでまとめておこうと思いました。

元ネタは「JavaScriptToolsGuideCC.pdf」。Mac 10.9 + ExtendScript Toolkit CCでの実験結果を含みます。

書き方

#include "file1.jsxinc;folder/file2.jsxinc"

Cとかに似てるそうですね、知らんけど。

ポイントは以下の通り。

  • 先頭に#、続けてディレクティブ名、スペース、引数。
  • 末尾にセミコロンはつけない。
  • 引数に複数の値を指定するときはセミコロンで区切る。
  • 引数の引用符は必須ではないけど、英数字以外を含む場合や、複数指定する場合は必要。'でも"でもOK。

各ディレクティブ説明

指定できるディレクティブは以下の6つ。

  • #include
  • #includepath
  • #script
  • #strict
  • #target
  • #targetengine

順番にいきましょう。

#include
#include "../folder/file1.jsxinc"

他のスクリプトファイルをインクルード(読み込んで実行)します。相対パス絶対パスどちらも使えるっぽいです。

ちなみに.jsxincというのは普通の.jsxファイルの拡張子を変えただけのファイルです。インクルード専用のファイルだということを明示するために付ける拡張子。InDesignスクリプトパネルなどから直接実行できなくなります。

ディレクティブは複数書いても大丈夫みたいですね。その場合は上から順に読み込まれます。

#includepath
#includepath "folder1;../folder2"

インクルードするファイル(#includeで記述)のパスを指定します。相対パス絶対パスどちらも使えます。引数は複数指定可能。

これがなかなかややこしいというか、下手に使うと失敗しそうです。例を挙げると、

#includepath "../lib/folder1;../lib/folder2"
#includepath "../lib"
#include "multi.jsxinc"

こんなふうに記述した場合、インクルードされるファイルは以下の4つ。(数字)は読み込み順です。

  • ../lib/folder1/multi.jsxinc (3)
  • ../lib/folder2/multi.jsxinc (2)
  • ../lib/multi.jsxinc (1)
  • ./multi.jsxinc (4)

最後のは、実行中のスクリプトと同じ階層にあるファイルです。
確かに#include自体相対パスとみなせるので理解はできるんだけど……ちょっと怖いのは私だけですか。主に順番とか。

面倒なので実験はしてないけど、これで#includeが複数あるともっとややこしくなりそうです。
ファイル名をユニークにすること、グローバル変数を最低限にすることが大事っぽい。

#script
#script "Script Name"

スクリプトの名前を指定します。
「The name value is displayed in the Toolkit Editor tab.」って書いてあるんだけど、どこで使われるんだかよくわからない。知ってる人いたら教えてください。

#strict
#strict on

エラーチェックをstrict(厳密)モードにします。
オブジェクトのプロパティがreadonly(読み込み専用)に設定されているとき、通常はスクリプトで上書きしようとしても無視されてそのまま進むだけなんだけど、strictモードがonだとエラーで止まるようになるみたいです。

#target
#target "indesign"

スクリプトが動作するアプリケーションを指定します。
ここで指定しておくと、.jsxファイルをダブルクリックしたとき、アプリケーションを起動して実行するかどうかのダイアログが出ます。指定しない場合はESTKで開くだけで、実行はされません。

#targetengine
#targetengine "session"

スクリプトが動作するJavaScriptエンジンを指定します。
通常、スクリプトで使用したグローバル変数は実行後に破棄されるんだけど、ここでmain以外を指定しておくと、スクリプトが動作したアプリケーションが起動している間はずっと変数が保持され、同じエンジンを指定したスクリプトから参照できるようになります。
ScriptUIを使ってウィンドウやらパレットやらを作りたいときには指定必須です。

おしまい

正直#includepathのとこが書きたかっただけです。

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は使い回せる
}

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


実際のところ、私が使ったのは逆パターン(選択したテキストから参照先の見出しを探す)のほうだったりします。
そもそも相互参照じたい使ってる人少なそうだけどねー。

PhotoshopドキュメントをJavaScriptで別名保存する

たまにはアプリケーションに特化したことを書こうのコーナーです(今考えた)。

いわゆる「別名保存」はDocment.saveAs()メソッドを使うんだけど、オプション類をいちいち調べるのが嫌になったのでまとめます。
なお、以下はPhotoshop CS6 Javascript Scripting Reference(リンク先PDF)を信用して書いたものです。ESTK付属のオブジェクトモデルビューアよりずっと使いやすいのでおすすめ。

Document.saveAs() 書式

Document.saveAs(saveIn[, options][, asCopy][, extensionType]);
引数 説明
saveIn File 必須。保存先になるFileオブジェクト。ここで拡張子つけても無視してpsd形式にされる。他の形式にしたい場合はオプションで指定する必要がある。
options varies
(いろいろ)
保存オプション。ファイル形式、および保存時のオプションを指定する。ファイル形式ごとにオブジェクト(クラス)が異なる。詳しくはあとで。
asCopy boolean 複製として保存するかどうか。オプション。
extensionType Extension 拡張子の書式。オプション。

Extension.LOWERCASE 小文字
Extension.NONE 拡張子なし
Extension.UPPERCASE 大文字

保存先Fileオブジェクト

var fileobj = new File("ファイルパス");

とかして作ればいいんじゃないですかね(投げやり)。

保存オプション

オプションは保存形式ごとに別々のクラスが用意されている。ふつうにGUIで保存するときもそうだけど、形式によってオプションがまるで違うからだと思う。
例えばjpg形式なら、

var saveOptions = new JPEGSaveOptions;
saveOptions.embedColorProfile = true;
saveOptions.formatOptions = FormatOptions.STANDARDBASELINE;
saveOptions.quality = 12;
// 他、各プロパティを設定

app.activeDocument.saveAs(fileobj, saveOptions, false, Extension.LOWERCASE)

って感じでオブジェクトを作ってから、saveAs()実行時に引数として渡す。

とりあえず順番にいきましょう。これ使うの?ってのもあるけど。

続きを読む

parseIntとparseFloatの覚え書き

前フリが思いつかない件について。
この記事の主旨は一言で言うと「parseIntには第2引数で基数を指定しよう」です。

基本

parseIntとparseFloatは、どちらも引数として渡された文字列をパースして数値に変換するグローバル関数。
parseIntは整数、parseFloatは小数と整数の両方(浮動小数点数)を扱える。

parseInt("100")    // 100
parseInt("3.14")   // 3

parseFloat("100")  // 100
parseFloat("3.14") // 3.14

パース方法とNaN

parseIntとparseFloatは、どちらも引数の文字列を先頭から1文字ずつ確認し、数値と解釈できない文字が見つかった時点で、その直前までの部分を数値として返す。
一文字も数値にできなかった場合はNaN(数値ではないことを表す数値型の値)を返す。

わかりにくいので例を。

parseInt("100pt")     // 100
parseInt("-3.14pt")   // -3
parseInt("314e-2")    // 314
parseInt("hoge123")   // NaN

parseFloat("100pt")   // 100
parseFloat("-3.14pt") // -3.14
parseFloat("314e-2")  // 3.14
parseFloat("hoge123") // NaN

parseFloatが数値と解釈できるのは、正負符号(+/-)、0〜9の数字、小数点、指数(e)。
parseIntはそこから小数点を除いたもの……ではなくて、実はもうちょっと複雑。

parseIntの第2引数(基数)

parseIntの引数として、パースしたい文字列のほかに、「何進数として解釈するか」という基数を渡すことができる。
基数は文字列でなくて数値で渡すことに注意。

parseInt("15", 10) // 15
parseInt("15", 8)  // 13
parseInt("15", 16) // 21

同じ"15"という文字列を、10進数、8進数、16進数でパースした値が返ってくる。

基数の指定によって、数として解釈できる文字は変わってくる。ちょっと複雑と言ったのはこのこと。

parseInt("19e", 10) // 19("e"はパース不可)
parseInt("19e", 8)  // 1 ("9e"はパース不可)
parseInt("19e", 16) // 414

基数を省略した場合、通常は10進数でパースされる。のだけど、省略するとけっこう危険だったりする。

基数を省略した場合

もしくは、基数に0を指定した場合、自動的に以下のように解釈されることが多い。

  • "0x"または"0X"で始まる文字列が渡された場合、基数は16(16進数)と解釈される。
  • "0"で始まる文字列が渡された場合、基数は8(8進数)と解釈される。
  • その他の文字で始まる文字列が渡された場合、基数は10(10進数)と解釈される。
parseInt("0x15") // 21(16進数として解釈)
parseInt("015")  // 13(8進数として解釈)※実装による
parseInt("15")   // 15(10進数として解釈)


16進数はともかく、この8進数がなかなか厄介で……

parseInt("015")  // 13(8進数)※実装による
parseInt("019")  // 1 (8進数、"9"はパース不可)※実装による
parseInt("08")   // 8 (8進数だとNaNになるので10進数で解釈)※実装による


あるでしょう、ノンブルとかで先頭をゼロで埋めてるやつ。
それをうっかりparseInt(textframe.contents)なんてやってしまうと、ページによって変な数値が返ってくる可能性があるということ。

余談

先頭の文字列が"0x"なら16進数、というのは、JavaScript(というか、ECMAScript)の仕様として規定されている。しかし実は先頭が"0"の場合は、「8進数にしてもいいよ(どっちかいうと10進数でやってね)」ということになっていた。
実装では8進数を採用したものが多いけど、確実ではないらしい。つまり実行する環境によって結果がまちまちということ。さっきから※実装によるって書いてるのはそういうことです。

いちおう現在の最新の仕様では、"0x"と"0X"を16進数とするのを除けばデフォルトは10進数、と定められている。けど実装のほうは今のところほとんど追いついてないらしい。AdobeのESTKでも8進数になるしね。

というわけで、この記事の主旨


parseIntには第2引数で基数を指定しよう


大事なことなので2回いいました。ちなみにparseFloatは常に10進数で、基数指定はできません。