忍者ブログ

ぼんぷろぐ

InDesignとかイラレとかのスクリプトよもやま話

新しいブログに引っ越しました。

こちらのブログはもう更新しませんが、コメント欄は生きてますので疑問、ご指摘などありましたらどうぞ。

[ExtendScript]
ExtendScriptで使ってはいけないグローバル変数名

JavaScriptしっかり書くならグローバル変数は極力なくして、どうしてもグローバル変数が必要なときはできるだけ独特な名前を、となるのでしょうけど、まぁInDesignやイラレでちょっとしたスクリプト書くときに、いちいちそんなこと気にしたくありません。適当な変数名で、グローバルスコープにベタ書きしてしまいがちです。

しかしそういうとき、適当につけた変数名が思わぬバグを引き起こすことがあります。たとえばファイルパスの変数名にpathとか、何かを反転する関数名にreflectとか、つい使ってしまいそうですけど、これらはバグの元です。(関数の中のローカル変数で使うのは問題ありません)

この手のバグ、知識として知っとかないと原因の特定がすごく難しいので、この際まとめておきます。



ExtendScriptはJavaScriptの亜種なので、JavaScriptでグローバル変数名に使っちゃダメなもの、たとえばeval、alert等の組み込み関数の名前や予約語等は、もちろんExtendScriptでもダメです。

ここでは「普通のJavaScriptならOKだけどExtendScriptではダメなもの」を紹介します。
ただし、ExtendScript全体で共通のものと、InDesign・イラレ独自のものだけです。フォトショやAEにも独自のものがあると思いますが詳しくないので触れません。

1.アプリケーションオブジェクト

  • app
のことですね。当たり前ですが一応挙げておきます。

2.ExtendScript独自の組み込み関数名

ExtendScript共通のものとしてはXML絡みの
  • isXMLName
  • setDefaultXMLNamespace
くらいしかないです。
アプリケーション独自のものには
  • exit(InDesignのみ)
  • resolve(InDesignのみ)
などがあります。

3.各種コンストラクタ名

ExtendScript共通のものはUnitValue、Reflection、BridgeTalkなど、
アプリケーション独自のものはDocument、PageItem、TextFrameなど膨大にありますが、
最初が大文字の変数名を避ければいいので、ここでは列挙しないことにします。

4.Object.prototypeのプロパティ名

Object.prototypeのプロパティというのは、すべてのオブジェクトが共通して持っているプロパティのことです。
すべてのオブジェクトが持つということは、グローバルオブジェクトも持っています。
そしてグローバル変数というのはグローバルオブジェクトのプロパティでもあります。
したがって、グローバル変数で「Object.prototypeのプロパティ名」を使おうとすると、グローバルオブジェクトがもともと持っているプロパティと競合してしまうので使ってはいけません。

ExtendScriptにあり、今のJavaScriptにはないものとして
  • reflect
  • watch
  • unwatch
が挙げられます。
(watch、unwatchは昔のJavaScriptにはあったらしい)

5.appのプロパティ名(イラレ、フォトショのみ)

イラレスクリプトでは、なぜかapp.が省略できます。
スクリプトの書き出しは、
var doc=app.activeDocument;

みたいなので始まることが多いですが、これを
var doc=activeDocument;

と書いても動くのです。
この謎仕様により、イラレスクリプトではappの持つプロパティ名をグローバル変数名に使えません。
appのプロパティ一覧(CS6)はこちらhttp://jongware.mit.edu/iljscs6html/iljscs6/pc_Application.html
うっかり使ってしまいそうなものとしては、
  • name
  • path
  • version
などがあります。

※Photoshopでも同じ問題があるようです。(お~まちさんに教えていただきました)

(ちなみに似たような謎仕様として、textRange.characterAttributes の characterAttributes も省略できます)

オワリ
PR

[ExtendScript]
比較演算がなんかおかしいExtendScript

ExtendScriptでは

false==null;

がtrueになるけど、変数を使って

x=false;
x==null;

とするとfalseになる(JavaScript的に正しいのはfalse)

っていう話をだいぶ前に書いたんですが(これの8番目)
他にもnullと==で比較した結果がおかしいものを見つけました。
0, "0", "" です。("0"はfalsyですらないのに…)





似たような現象はこんなのでも起きます。




数値と文字列の比較では文字列側をNumberに変換するので、"!"はNaNになって、「NaNは比較演算子で何と比較してもfalseを返す」という性質があるので false になるはずなのですが、変数に入れずに直接比較すると true になってしまいます。変数がある場合は正しいです。



でー、変数があるかないかでなんでこんな違いが発生するのかなんですけど、
JSXBIN化を使ってみるとヒントめいたものが見えてきます。

これは「null==false;」をJSXBIN化した文字列
@JSXBIN@ES@2.0@MyBbyBn0ABJAnAFct0DzABByB

そしてこれは「true;」をJSXBIN化した文字列です。
@JSXBIN@ES@2.0@MyBbyBn0ABJAnAFct0DzABByB

完全に一致です。
これは比較演算だけでなく、「1+1」と「2」とかでも同じになります。
つまりはJSXBIN化(コンパイル)した時点で既に演算されてるのです。

変数のある演算は、実際にプログラムが動いてその行に行きつくまで結果がわからないけど、1+1とか0==nullみたいなプリミティブ値のリテラル同士の演算は最初から結果がわかってるので、高速化のためにもコンパイル時に演算しておこう、ということなんだと思います。
で、その演算が間違ってると。




ここまでは「値を直接演算するとおかしいけど、変数があれば正しい」というもので、実際のスクリプトで出てくるのはほぼ変数ありの演算でしょうから、まあ罠ではあるけどそんなに深刻なバグではありません。

次のは変数があろうとなかろうと、常におかしいやつです。

undefined < 任意の数

は、JavaScript的にはfalseになるのが正しいですが、ExtendScriptではtrueになります。

たとえばこんなコードでうっかり引数を入れ忘れて実行したらtrueが返ってきてしまいます。

function f(x){
    return x<0;
}
f();

ちなみに任意の数ってのは0とか100だけでなく、Infinityや-Infinityも含まれます。
「undefinedはマイナス無限大よりも小さい」ということになってしまうのです。



オワリ

[ExtendScript]
BridgeTalkとエンジンの話

BridgeTalkではbodyに実行したいスクリプトのコードを文字列で入れるのですが、これがあまり長くなると読みにくいし、できれば関数呼び出し1つだけですっきりすませたいって思うかもしれません。


たとえば、イラレでこんなスクリプトを実行してみましょう。
(※イラレではScriptUIの面倒な仕様により、イラレからイラレ(自分自身)へBridgeTalkを送る必要がある場合があります)

function f(s){
    alert(s);
}
var bt=new BridgeTalk;
bt.body="f("+"\"ふぐ\""+");"
bt.target="illustrator";
bt.send();

「ふぐ」がアラートされたら成功ですが、これメニューの『その他のスクリプト』から実行するとうまくいきません。
ESTKやSPAi、Sppyなどから実行するとちゃんと動きます。
そして1度ESTKやSPAiなどで実行した後なら、『その他のスクリプト』から実行から実行しても動くようになります。

この現象は実行エンジンに関係しています。

エンジンについては前にまとめたんですけど、
えー、なんか読み返してみるとすごい分かりづらいですが、
イラレで『その他のスクリプト』から実行すると、#targetengineが無い場合、transientというエンジンで実行されます。
一方BridgeTalkで受け取ったコードはmainエンジンで実行されるため、その齟齬によりうまく動かないわけです。

これをどうにかするには
①mainエンジンで関数fを定義する
②BridgeTalkで受け取ったコードをtransientエンジンで動かす。
の2つが考えられます。

しかし②はダメなんです。なぜならtransientエンジンは変数が使い捨てで、実行後に関数fが保持されないからです。

なので①を採用します。1行目でtargetengineを指定するだけです。

#targetengine main
function f(s){
    alert(s);
}
var bt=new BridgeTalk;
bt.body="f("+"\"ふぐ\""+");"
bt.target="illustrator";
bt.send();




では同じことをInDesignでやってみましょう。

#targetengine main
function f(s){
    alert(s);
}
var bt=new BridgeTalk;
bt.body="f("+"\"ふぐ\""+");"
bt.target="indesign";
bt.send();

これ、動きません。
なぜかというと、InDesignでは逆にmainが変数使い捨てのエンジンだからです。
InDesignで関数fを保持するためにはmain以外のエンジンを指定します。

上に書いた①②で言えば、今度は①がダメなので②を採用することになります。

//@targetengine hoge
function f(s){
    alert(s);
}
var bt=new BridgeTalk;
bt.body="f("+"\"ふぐ\""+");"
bt.target="indesign#hoge";
bt.send();

targetのところでアプリ名に続いて#エンジン名を指定することで、そのエンジン上でBridgeTalkを動かすことができます。(InDesign以外でこれがまともに使えるアプリがあるのか分かりませんが…)


オワリ

[ExtendScript]
#includeはどこに書いてもよく、コードの断片でもいい

ExtendScriptの#includeって、案外自由度が高くて、

①行頭でさえあればどこに書いてもよく、書いた場所にインクルードしたファイルの内容が展開される。

②インクルードする側、される側のコードがそれぞれJavascriptのコードとして成立していなくてもいい(インクルード後に成立していれば)

という特徴があります。




①は割と重要です。ひょっとしたら#includeは冒頭に書かなきゃいけないと思ってる方もいるのではと思いますが、たとえば

(function (){
    #include "nanka.jsx"
    …
})()

のように書けば、nanka.jsxを関数内のローカルスコープで読み込むことができます。




②はどういうことかと言うと、たとえば、

alert(1

という")"が抜けたコードがあって、

)

という1文字だけのテキストファイルkakkotoji.txtがあったとして、

alert(1
#include "kakkotoji.txt"

を実行すれば、

alert(1
)

と同じになって1がアラートされるのです(kakkotoji.txtは実行するjsxファイルと同じフォルダに入れます)。
だからどうしたという感じですが…

唯一役に立ちそうな使い道として思いついたのが、jsonファイルの読み込みです。
ExtendScriptのオブジェクトとしてそのまま読めるようなjsonファイルであれば、

var hoge=
#include "hoge.json"

とするだけで、変数hogeにhoge.jsonをパースしたオブジェクトが入ります。

他になんか面白い使い道思いついた方は教えてくださーい

プロフィール

kawamoto_α
(あるふぁ(仮))


InDesignで新聞組版のようなことをしています。

ツイッタ

※ブラウザによっては当ブログからDLしたzipファイルが拡張子なしになることがあるようですが、.zipを補って開いてください。



イラレ用トーンカーブスクリプト(¥1500)



クロソイド式角丸長方形スクリプト(¥500)
Illustrator用
InDesign用



イラレスクリプトをキーボードショートカットで実行するやつ(Win用)