2010年12月6日月曜日

JavaScriptで、タグの外側だけを置換する。

例えば、
<p>あいうえおAAAかきくけこbbbさしすせそCCC<br/>たたたDDDちちちeeeつつつFFFてててててgとととと</p>
というhtmlがあった場合に、これの英字の部分だけを太字にしようとしたとする。
だけれど素直に
var html="<p>あいうえおAAAかきくけこbbbさしすせそCCC<br/>たたたDDDちちちeeeつつつFFFてててててgとととと</p>";
html=html.replace(/(\w+)/g,"<b>$1</b>");
などとやると、
<<b>p</b>>あいうえお<b>AAA</b>かきくけこ<b>bbb</b>さしすせそ<b>CCC</b><<b>br</b>/>たたた<b>DDD</b>ちちち<b>eee</b>つつつ<b>FFF</b>ててててて<b>g</b>とととと</<b>p</b>>
のようにタグ内の文字まで置換されてしまうわけで……。

そこで、タグの外を置換する正規表現を調べると、やたらと難しい正規表現を書かなきゃいけない。自分は正規表現はわりと得意な方だと思ってるけれど、それでもかなり理解に苦しむ。書いてみても動かない。
だいたい、タグ自体にマッチさせる正規表現は、ちょっと勉強すれば誰でもわかる。<[^>]*?>だ。

ピンと来た!
var html="<p>あいうえおAAAかきくけこbbbさしすせそCCC<br/>たたたDDDちちちeeeつつつFFFてててててgとととと</p>";

html=html.replace(/<[^>]*?>|(\w+)/g,
function(s0,s1){
if(s1===undefined) return s0;
return '<b>'+s1+'</b>';
});
<p>あいうえお<b>AAA</b>かきくけこ<b>bbb</b>さしすせそ<b>CCC</b><br/>たたた<b>DDD</b>ちちち<b>eee</b>つつつ<b>FFF</b>ててててて<b>g</b>とととと</p>

これでいい。正規表現内では先にタグをひっかけ、キャプチャがなければ全体をそのまま返す。
タグになった部分の文字は見なくなるから、あとはキャプチャ文字にbタグをかけて返せばいい。
これかなり応用がききそう!


ついでに、Groovyならばこう。
String html="<p>あいうえおAAAかきくけこbbbさしすせそCCC<br/>たたたDDDちちちeeeつつつFFFてててててgとととと</p>";

html=html.replaceAll(/<[^>]*?>|(\w+)/){s0,s1->
if(s1==null) return s0
return "<b>$s1</b>";
}

println html


2010.12.8追記:
タグ外だけ置換を連続でやるならつまり、こういうこと。
var html="<p style=\"color:red;\">あいうえおpAAAかきくけこ<br/>bbbさしすせそCCC</p>\
<p>たたたDDDちちちpeeeつつつF<br/>FFてててててgとととと</p>";

function replaceOutsideTags(str,reg,rep){
return str.replace(/<[^>]*?>|([^<]+)/g,
function(s0,s1){return (s1===undefined)?s0:s1.replace(reg,rep);
});
};

html = replaceOutsideTags(html,/(\w+)/g,"<b>$1</b>");
html = replaceOutsideTags(html,/p/g,"ぴぃ");
html = replaceOutsideTags(html,/b/g,"火");
html = replaceOutsideTags(html,/ち/g,"血");
$.writeln(html);

2010年11月21日日曜日

異体字セレクタ検索を作った。


異体字セレクタを検索できるガジェットを作ってみました。
←左側のメニューの一番上に置いておきます。

IDVは先日2010-11-14に更新されましたが、このガジェットは2007-12-14版をもとに作成されています。
また字形は、MacOS X 付属の「小塚明朝 Pr6N M」を使用しています。

読み込みがあきらかに遅くなった気がするので、いずれ外すか、どこか別のところに移動するかも。
メイキングはそのうち書く予定…。

2010年9月3日金曜日

InDesign CS5 の pageItems の並びが CS4 以前までと違っている件

昨日、今まで作ったスクリプトのCS5での動作検証をしていて、あることに気づいた。
下のようなInDesignドキュメントを用意する。



レイヤーは3つ、下から順番にテキストフレームに数字を入れ、ところどころグループしてある。
このドキュメントに対し、InDesignCS4で以下のスクリプトを実行する。
var doc = app.activeDocument;
for(var l=0,lLength=doc.layers.length;l<lLength;l++){
var layer = doc.layers[l];
$.writeln('----↓Layer '+layer.name);
var items = layer.pageItems.everyItem().getElements();
(function(items){
for(var i=0,iLength=items.length;i<iLength;i++){
var item=items[i];
if(item.constructor.name==='Group'){
$.writeln('--↓Group '+item.id);
arguments.callee(item.pageItems.everyItem().getElements());
$.writeln('--↑Group '+item.id);
}else{
$.writeln(item.contents);
}
};
})(items);
$.writeln('----↑Layer '+layer.name);
};

すると、結果はこう出る。
----↓Layer レイヤー 3
12
11
10
----↑Layer レイヤー 3
----↓Layer レイヤー 2
--↓Group 986
9
--↓Group 985
8
7
--↑Group 985
--↑Group 986
----↑Layer レイヤー 2
----↓Layer レイヤー 1
--↓Group 987
6
5
--↑Group 987
4
--↓Group 976
3
2
--↑Group 976
1
----↑Layer レイヤー 1

これが今までの動き。pageItemsは、レイヤーごとに上から順に、グループ以下を含まずに「PageItem」オブジェクトを返す。
それらはTextFrameやGroupではなく、一枚皮をかぶった状態なので、everyItem().getElements()で、PageItemの皮を剝いた配列を拾っていた。これは今まで自分が結構使っている手だった。

さて、このスクリプトをCS5で走らせてみると、こうなった。
----↓Layer レイヤー 3
12
11
10
----↑Layer レイヤー 3
----↓Layer レイヤー 2
--↓Group 986
9
--↓Group 985
8
7
--↑Group 985
--↑Group 986
----↑Layer レイヤー 2
----↓Layer レイヤー 1
4
1
--↓Group 987
6
5
--↑Group 987
--↓Group 976
3
2
--↑Group 976
----↑Layer レイヤー 1

並び順が変わってしまっている!わかりにくいので、レイヤーを結合して、もう一度実行してみる。
----↓Layer レイヤー 1
12
11
10
4
1
--↓Group 986
9
--↓Group 985
8
7
--↑Group 985
--↑Group 986
--↓Group 987
6
5
--↑Group 987
--↓Group 976
3
2
--↑Group 976
----↑Layer レイヤー 1

再帰するのをやめて、contentsじゃなくそのまま出してみる。
var doc = app.activeDocument;
for(var l=0,lLength=doc.layers.length;l<lLength;l++){
var layer = doc.layers[l];
var items = layer.pageItems.everyItem().getElements();
for(var i=0,iLength=items.length;i<iLength;i++){
$.writeln(items[i]);
};
};

結果は?こうなった。
[object TextFrame]
[object TextFrame]
[object TextFrame]
[object TextFrame]
[object TextFrame]
[object Group]
[object Group]
[object Group]

要するに、CS5のpageItemsは、上から順にアイテムを並べるのではなく、「テキストフレームのまとまり(textFrames)」、「グループのまとまり(groups)」といったまとまりごとに並べて来るようだ。
ちなみに順番は、
Oval→Rectangle→GraphicLine→Polygon→TextFrame→Group
の順に来るみたい。

これは……地味に困る。前後関係を取りたいスクリプトがいくつかあるんだ。
pageItemsでは前後関係が正しく来ないものの、allPageItemsでは正しい前後関係で取れる(ただしグループ以下のアイテムもまとめて来てしまう)のを確認したので、以下のようなfunctionを書いてpageItems.everyItem().getElements()と置き換えることにした。
var getChildItems=function(item){
var allItems = item.allPageItems;
var max = allItems.length, i = 0, children = [];
while(i < max){
var child = allItems[i];
children.push(child);
i++;
if(child.constructor.name==='Group'){
i += child.allPageItems.length;
}
};
return children;
};
とりあえずこれで何とか……。まいったよもう。

2010年8月17日火曜日

デジタル・パブリッシング・フェア2010に行った話。


ずいぶん書くのが遅れてしまいましたが、先月7月9日(土)、東京ビッグサイトまで行ったのです。
自分はPage2008の時以来なので、2年半ぶりの上京でした。会社の皆とレンタカーを乗り合わせて、社長の運転で行ったのでした。
朝8時に待ち合わせて出発。昼は富士サービスエリアで桜えびかきあげ丼。美味しかった。

ビッグサイトに着いたのは昼1時過ぎ。前の駐車場は空いてないので奥の駐車場に車を止めて、長い通路を歩いて会場へ。
ビッグサイトの例の逆ピラミッドの下をくぐれなかったのは残念でした。

今回のデジパブは、教育ITソリューションフェアと、東京国際ブックフェアとの合同開催で、教育IT系のエリアから順番に見て行きました。
気になったものを箇条書きに。

  • 問題集データベースから、word組み、InDesign組版する会社があった。
    そんなに上手くいくのかな?というのが印象。

  • iPadを教育に使う試み。iPadの習字ソフト、面白い!

  • プロジェクタに専用の指し棒でついた部分をカメラで探知し、クリックしたり○で囲んだりする電子黒板。面白い!

  • モリサワMcBook。
    MCB2はもともとテキスト流し込みで全部作るから、電子書籍と相性がいいのはわかるけど、独自形式なのはどうかなぁ…という感じ。
    現状で縦組ができる強みはあると思う。

  • CMS入校から自動組、電子書籍にというソリューション。
    人が並んでいてちゃんと見られなかったのが残念。

  • アンテナハウスのブースで「ページ組版のためのCSS指南」を購入してもらう。(がんばって変換書く!)
    ePubの仕様策定も応援してます。

  • 大日本スクリーンの人とのフォントの話。
    ヒラギノは中国語フォントがあり、日本語とデザインを合わせられる。SnowLeopardには既に乗ってる。
    ライセンスについては
    「フォントライセンスはApple内のフォントはAppleの規定に従っている」
    との事。
    ※あとで調べてみると、商業利用(PDF埋め込み等)もフリーという話。MacOSX Server使えば帳票出し用フォントにヒラギノが使えるな……。

  • フレキシブルに巻いて使えるキーボード。面白い。


その他、いろいろ見ましたが、そこまで目新しいものは見当たらなかった印象でした。
電子書籍系はまだ入り口で、紙系DTPは行き止まりという感じ。
教育ITの方がアイデア豊富で面白かったです。

たぶん「教育」は目的だから、ITに刺激されていろんなアイデアも出るのでしょうが、「出版」は手段なので……
パブリッシングはデジタルに置いてかれてしまうんだろうか?


そして一通り見てから、ブックフェアを見学。
蝶の図鑑を探していたのだけれど、安売り本にはめぼしいものがなく、専門書はあまりニッチなのは置いていない印象で、何も買わずにあれよあれよという間に時間。
帰りは中央道経由で、渋滞に巻き込まれながら帰ったのでした。

夜は諏訪湖サービスエリアでサーモンユッケ丼でした。
美味しかった。

文字から文字コードを得る、文字コードを文字に戻す(Groovy版)

カネムーメモ: 文字から文字コードを得る、文字コードを文字に戻す
のGroovy版。Javaなんだけど。
もっと便利なメソッドもありそうと思うがよくわからない。

2010年7月30日金曜日

そのループ、InDesignのPageItemsに使っちゃだめだよ!

for (var i = 0, elem; elem = elems[i]; i++) { doSomething(elem) }って書き方見やすくていいな - JavaScriptとかPerlとかPHPとかさくらとか勉強する

上記のページで、こういう書き方が紹介されていた。
var paragraphs = document.getElementsByTagName('p');
for (var i = 0, paragraph; paragraph = paragraphs[i]; i++) {
doSomething(paragraph);
}

へーへーへー、さっそくやってみよう、ということで。
InDesignで、てきとうに2つ3つオブジェクトを置いたドキュメントを作り
var items = app.activeDocument.pageItems;
for (var i = 0, item; item = items[i]; i++) {
$.writeln(item);
}
と、やってみた。
……無限ループ発生。

なにが起こってるのか理解できなかったので、
$.writeln(items[100])
とかやってみたら、falseもエラーも返さない。
なるほど、pageItemsとか、InDesignのコレクションはただindexで引くだけじゃ、存在してなくてもfalseを返さないのか。pageItemsだけでなくtextFramesやpathItemsでも同じ。これじゃループ抜けられないわな……。

あ、そうか、要は配列でくればいいのかな?と思い
var items = app.activeDocument.pageItems.everyItem().getElements();
for (var i = 0, item; item = items[i]; i++) {
$.writeln(item);
}
これなら上手くいく。よし。

どちらにしろpageItemsへのアクセスは遅いし、ループさせるならいったん配列にした方が無難みたいです。

2010年7月28日水曜日

InDesignでそのアイテムを持つドキュメントを得る

以外とめんどくさいんですよねこれ。再帰してparentを上がっていったり……。
と思っていたけどいい方法を思いついたのでメモ。



2010.7.29追記:時間を計ってみた。

再帰してdocumentを得る関数が、parentDoc2。
新規のドキュメントにたくさんオブジェクトを作って、このスクリプトを実行した結果、
アイテムの数 -> 644
------ parentDoc(obj) -> 171
------ parentDoc2(obj) -> 724

と、こんな感じ。グループ多めにしたけど、4倍近く速い!

2010年7月7日水曜日

ExtendScriptで、for文のiを外に出すと速いのか?

たまに、for文の中の変数(よく使うのはi)を、for文の外に出すと速くなる、と聞く。
本当かな?と思ったので試してみた。
var ary = new Array(999999);
var start,end;

//iを中で宣言、ary.lengthを変数に渡さない
start = new Date().getTime();
for(var i1=0;i1<ary.length;i1++){}; //test1
end = new Date().getTime();
$.writeln('test1 -> '+(end-start));

//iを中で宣言、ary.lengthを中で変数に渡す
start = new Date().getTime();
for(var i2=0,len2=ary.length;i2<len2;i2++){}; //test2
end = new Date().getTime();
$.writeln('test2 -> '+(end-start));

//i、ary.lengthをfor文の外で宣言
start = new Date().getTime();
var i3=0,len3=ary.length;
for(;i3<len3;i3++){}; //test3
end = new Date().getTime();
$.writeln('test3 -> '+(end-start));

結果は、
test1 -> 1293
test2 -> 726
test3 -> 730
ということになった。配列の数をfor文中の最初で変数に入れるのは明らかに効果があるけど、変数をfor文の外で宣言するのは、(少なくともExtendScriptでは)誤差の範囲内でしかなかった。

じゃあオブジェクトでforinだったらどうか?
//オブジェクトを作る
var obj = {};
var c = 'abcdefghijklmnopqrstuvwxyz'.split('');
var clen = c.length;
for(var i=0;i<99999;i++){
var key = [];
for(var ii=0;ii<10;ii++){key.push(c[Math.floor(Math.random()*clen)])};
obj[key.join('')]=i
};

var start,end;

//iを中で宣言
start = new Date().getTime();
for(var i1 in obj){}; //test1
end = new Date().getTime();
$.writeln('test1 -> '+(end-start));

//iを外で宣言
start = new Date().getTime();
var i2;
for(i2 in obj){}; //test2
end = new Date().getTime();
$.writeln('test2 -> '+(end-start));
オブジェクトを作るのにかなり時間がかかるけど気にしない。
結果は……
test1 -> 19256
test2 -> 19200
というわけで、誤差の範囲だった。

2010年6月27日日曜日

「第二回テクニカルDTP勉強会」フォロー、スクリプト・プログラマー宣言

2010年6月26日「テクニカルDTP勉強会」第二回」が開催され、前回に引き続き、自分も発表させていただきました。
発表スライドをslideshareに置いてみました。

今回、プレゼン用のスライドを初めて書きました。OpenOffice.orgのImplessを使ったのですが、慣れてないのでなかなか上手くいきませんね。Keynote使えたら良かったなぁ。


話の流れですが、Adobe系アプリを操作するためのJavaScript、いわゆるExtendScriptを書く人たちは、自分の技術を割と過小評価してるんじゃないのかな?(書く人も、雇う人も!)と思う所があり、「スクリプト書きだって、プログラマーだ!」と宣言することで、ただスクリプトを書く便利なDTPオペではなく、その先に進もう、そのためには何をしたら良いのか? といったところをまとめてみたつもりです。

実際、スクリプトを書きたい、書こうと思ってる人は多くいるのに、実際に書けるようになる人ってやっぱり少ない。それができる人は自分で調べて学ぶことのできる貴重な人材なのです。もっと自信持って良いと思うのですよ。

で、ExtendScriptに閉じこもるのではなく、もっとプログラマの使ういろいろなツールを使ってみようよ、と。
今回のプレゼンでは、
  • TextMate
  • JSDoc-toolkit
  • Git
この3つを紹介してみました。

それともうひとつ、ExtendScriptから入った人は、もうひとつ別の言語を覚えると良いよ、という提案をさせていただきました。今回例として取り上げたのは、コンソールから動かせるスクリプト言語が良いだろうと思ったんで
  • Perl
  • Ruby
  • Python
  • Groovy
の4つだったのですが、phpでも、あるいは今回の勉強会でも使われていたFlex(mxml+ActionScript)でも良いかと思います。別の言語をおぼえることで、利用範囲や考え方が大幅に広がります。おススメです。


最後に、勉強会で聞いてくださったみなさま、ありがとうございました。
また機会がありましたらよろしくお願いします。

2010年6月9日水曜日

ExtendScript TextMate Bundle を公開しました。

TextMateからショートカットでExtendScriptの.jsxを直接走らせるバンドルです。
元々は自分の上司の@tyama氏が作成し、それを自分が使いやすいように少しずつ直していたものです。このたび@tyama氏の許可が取れたので公開します。
興味のある方は、使ってみて感想下さい。

GitHubに置きました。
kanemu / extendscript.tmbundle - GitHub

使い方:
GitHubから落としてきたフォルダ名を[ExtendScript.tmbundle]として、 ~/Library/Application Support/TextMate/Bundles に入れて下さい。
$ cd ~/Library/Application\ Support/TextMate/Bundles/
$ git clone git://github.com/kanemu/extendscript-tmbundle.git ExtendScript.tmbundle

インストールしたバンドルは、TextMateの「Bundles」メニューの、「Bundle Editor -> Show Bundle Editor」から編集、カスタマイズすることができます。

.jsxファイルを判別するための、Scope Selector の「source.js」は JavaScript.tmbundle に依存しています。なので JavaScript.tmbundle が入ってなければ入れて下さい。
JavaScript.tmbundle の Language JavaScript の 3行目
fileTypes = ( 'js', 'htc' );

fileTypes = ( 'jsx', 'js', 'jsxinc', 'htc' );
に変更して下さい。

コードを拡張子.jsxで保存し、ショートカットキーを叩くと実行されます。
#target が設定してある場合、そのアプリが立ち上がって実行します。
なければデフォルトで、InDesign上で実行されます。

デフォルトのショートカットは、以下のように設定してあります。
  • Command + 4 -> CS4で実行
  • Command + 3 -> CS3で実行
  • Command + 2 -> CS2で実行
  • Command + option + 4 -> ExtendScript Toolkit CS4 で開く
  • Command + option + 3 -> ExtendScript Toolkit 2 で開く
このキーはぶっちゃけ結構間違えるので(CS4で走らせたい場合にCS3をキックしてしまい、よけいなアプリが立ち上がったり……)、自分で適当に直して下さい。

2010.06.16追記:
TextMateは、標準では和文の入力と、2バイトフォントの表示ができません。
※現時点で、Version 1.5.9 (1510)です。
日本語入力プラグインと見た目をまともにするためのフォントを入れる必要があります。
詳しくは下記ページにて。
TextMate で日本語をわりとまともに表示する - d.hetima

2010年6月5日土曜日

InDesignの字送りで囲み文字を作る。



モリサワMC-B2やCANONのEdianにはデフォルトで、「文字合成」(合成文字じゃない)の機能があります。
要は2つの文字を重ね合わせる機能。○囲みの文字なんかを作るのに便利。でもInDesignには文字合成はありません。

○囲みの作り方は、ディザInDesignのお~まちさんが、圏点を使うやり方を紹介されています。
ディザ InDesignブログ: 圏点を利用して囲み文字
囲み文字の作成をサポートする

ですが、この方法だとテキスト書き出しした時やスクリプトでcontentsを呼び出した時に、肝心の文字が「○」で出てしまいます。文字の意味としては、「合」の方にあるわけで……
素直に字送りで重ねる方法もあった方いいと思ったので、サンプルを書いてみました。InDesignCS3 v5.0.4、MacOSX 10.5.8にて動作確認。

「○合」を重ね合わせたいならば、「○合」と入力した場所を選択して、スクリプトを実行してください。
文字のサイズを変更したいならば、スクリプトの中を直接編集。

20100608追記:
いき♂さんのアドバイスを取り入れ、丸囲み文字の後ろに「結合なし」文字を挿入するようにしました。

2010年6月2日水曜日

たけうちとおるさんの「DTP Booster 13のお題」をやってみた

たけうちとおるさんが、今度のDTP Booster 13用の例題を発表されていた。
DTP Booster 13のお題 - たけうちとおるのスクリプトノート
  • 書き出されるPDFはドキュメントと同じファイル名で(拡張子が.pdfになる)同一階層に保存されるとします。
  • PDF書き出しプリセットは「PDFx/1-a」です。
  • PDFを書き出したドキュメントは保存せずに閉じます。
  • 20行以内で出来ると思います。
  • 多少のエラー処理はしなくて結構です。

そういうわけでやってみた。
Mac OS X 10.5.8、InDesign 5.0.4にて動作確認。


20100603追記:
当日のDTPBoosterのたけうちとおるさんのUSTREAMにて
「document.exportFile()の引数4つ目はPDFExportPresetオブジェクトを入れる」
と話していて、自分はStringで名前を入れていたので、心配になって正しく動作しているか確認してみました。
  • 今度はInDesign CS4 6.0.5にて確認
  • '[PDF/X-1a:2001 (日本)]'を指定、Acrobatにてプリフライト→OK
  • '[PDF/X-3:2002 (日本)]'を指定、Acrobatにてプリフライト→OK
  • 自前で作ったプロファイル'x1a-tombo'を指定→トンボ付きで生成される
というわけで、ちゃんと動いているようです。

20100604追記:
たけうちとおるさんに、解答編でとりあげていただきました。
ありがとうございます!
DTP Booster 13のお題(解答編) - たけうちとおるのスクリプトノート

2010年5月10日月曜日

Groovyでスクリプト自身の場所を確認する

Javaと全く同じ。(Groovyだし、もっといい方法もあるかも)


実行。
$ groovy urltest.groovy 
file:/Users/kanemu/Desktop/groovytest/urltest.groovy

Groovyのファイル名ハマりどころメモ

ちょっと焦ったのでメモ。

その1:
groovyスクリプトを書くとき、たとえば
println('Hello, World!')
とだけ書いて、ファイル名を「これは最初のスクリプトだから……」とか思って
01_println.groovy
とつけて実行すると、
Caught: java.lang.ClassFormatError: Illegal class name "01_println" in class file 01_println
というエラーが出て動かない。
groovyはいったんJavaのクラスとしてコンパイルされてから実行される。その際ファイル名のクラスが作られるので、ファイル名の頭文字が数字だと動かない。

その2:
groovyスクリプト内にclassを書く時、
class Test {
def name = 'kanemu'
}

def test = new Test()
println test.name
のようなスクリプトを書いたとして、ファイル名を
Test.groovy
にすると、
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
/Users/kanemu/Desktop/groovytest/Test.groovy: 1: Invalid duplicate class definition of class Test : The source /Users/kanemu/Desktop/groovytest/Test.groovy contains at least two definitions of the class Test.
One of the classes is a explicit generated class using the class statement, the other is a class generated from the script body based on the file name. Solutions are to change the file name or to change the class name.
@ line 1, column 1.
class Test {
^

1 error
のようなエラーが出て、動かない。
これも上と同じ理由で、いったんJavaクラスとして実行される際、ファイル名をとってTest.classを作っているので、スクリプト内のTestクラスとかぶってエラーが出てる。
中で使うクラス名と、ファイル名は別にしておかないといけない。

うーん、これはちょっとどうかと思うよ。

2010年5月5日水曜日

Jsdoc-ToolkitをExtendScriptで試す

Jsdoc-toolkitという、JavaDocのようなドキュメント生成ツールがある。
それをExtendScriptで使えないか、ずっと試そうと思ってたんだけど、GW休みになってようやく試す事ができたのでメモ。

Jsdoc-toolkitは、JavaDocと似た文法でJavaScriptのソースコードにコメントを書くと、それからHTMLのドキュメントを書き出してくれるフリーウェア。
JsDoc Toolkitを使う!
を参考にさせてもらう。上記ページにほとんどのことは書いてあるので、ドキュメントの記述方法など、詳しい事はそちらを参照して下さい。

手順としては、
1.Javaのインストール
これはMacには標準で入っているのでOK。

2.JsDoc-toolkitのインストール
http://code.google.com/p/jsdoc-toolkit/downloads/list
から最新のjsdoc_toolkit-x.x.x.zipを落として解凍し、任意の場所に置く。
(自分の習慣として、こういうのは/optに置いています。)

3.とりあえずドキュメントを用意
http://www12.atwiki.jp/aias-jsdoctoolkit/pages/13.html
のサンプルコードをテスト用に、そのままコピってくる。jsxなので#targetとか入れてみる。
#target "Illustrator"
/**
* 数値に1を加算して返す。
* @param {Number} num 数値
* @returns {Number} 引数で与えられた数値に1を加算した数値
*/

function incrementNumber(num) {
return num + 1;
}
これを
~/Desktop/jsdoctest/jsdoctest.jsx
に保存する。

4.コマンド実行
$ cd '/opt/jsdoc-toolkit/'
$ java -jar jsrun.jar app/run.js /Users/kanemu/Desktop/jsdoctest -x=jsx
-t=templates/jsdoc/ -d=/Users/kanemu/Desktop/jsdoctest/jsdoc
-xオプションは拡張子の指定。.jsならば入れなくても書き出すが、.jsxを書き出すためには-xオプションで指定しなければいけない。
-tオプションはHTMLテンプレート。これはJsdoc-toolkitに付属しているものを使うが、カスタマイズもできるみたい。
-dオプションは書き出し先の指定。

これでターミナルから実行すると……

できた!こんなふうに、ドキュメントが生成される。
いくつか試したが、ExtendScriptだからってとくにエラーを出すでもなく、きれいに行った。いいんじゃないかな?


さて、いちいちコマンドを打つのは面倒なんで、もっと簡単な方法はないかと、Antを使った方法(jsdoctoolkit-ant-task)なんかをいろいろ試したが、あんまり上手くいかなかったので割愛。

自分はスクリプトを書くのにTextMateを使っているので、それのバンドルがないかと思って探したら、見つけた。
jsdoctoolkit-tmbundle
これを試してみる。ターミナルから、
$ cd ~/Library/Application\ Support/TextMate/Bundles/
$ git clone http://github.com/choan/jsdoctoolkit-tmbundle.git Jsdoctoolkit.tmbundle
これでTextMateを立ち上げると、Bundleが入っている。Jsdoc-Toolkitも一緒に入るので、別にインストールしたものは削除してしまって良い。

ただ、このままだと.jsxの拡張子に対応しないので、Bundle EditorでJsdocToolkitバンドルの"Create Docs"の下の方にある
java -jar "${JSDOC_DIR}/jsrun.jar" "${JSDOC_DIR}/app/run.js" -a
-t="${JSDOC_DIR}/templates/jsdoc" -d="${DOCS_DIR}" -r .
の行を
java -jar "${JSDOC_DIR}/jsrun.jar" "${JSDOC_DIR}/app/run.js" -a
-x="js,jsx" -t="${JSDOC_DIR}/templates/jsdoc" -d="${DOCS_DIR}" -r .
に変更。

あと、Scope Selectorの「source.js」はJavaScriptバンドルの方で記述されているので、
JavaScriptバンドルの"JavaScript"の上の方の行
fileTypes = ( 'js', 'htc' );

fileTypes = ( 'js', 'jsx', 'jsxinc', 'htc' );
に変更。これで Bundles>JsDoc Toolkit>Cleate Docs を実行すれば、JsDocが生成される。

満足!これからはがんばってドキュメント書くぞ!

20100509追記:
もう一つ。jsdoctoolkit-tmbundleを入れているとjsdocの
/**
*
*/
が/*+tabで入力できるのだが、これの中でreturnを押すと行頭に * のついた行が1行増える。英語環境なら良いのだけど、CJK-Input.tmpluginを入れて日本語を使っていると、エンターで変換できなくなっちゃう……。
なので、 jsdoctoolkit-tmbundleの "Newline"の、入力補完をreturnからshift+returnに変更。これでjsdocの中も日本語入力可能に。

2010年5月3日月曜日

Illustrator EPSのみを.aiで別名保存する

EPSは、Illustratorで作成しても、Photoshopで作成してもどちらも拡張子は.eps。当然Windowsでは区別がつかないし、MacでもSnowLeopardからはクリエータが廃止されてしまった。
今後は拡張子が違う形式で運用していく必要がある。
なので、IllustratorEPSのみを.aiで再保存する方法を考えてみる。

まず、普通に書いた変換スクリプト。Illustrator CS3、MacOSX 10.5.8にて検証。
あまりテストしてないので、ファイルや環境によってはエラーを出すかもです。
#target "Illustrator"
(function(){

var fol=Folder.selectDialog('フォルダを選択して下さい');
if(!fol) return;

var mask=function(file){
return /^[^\.].*\.eps/i.test(file.name);
};

var files=fol.getFiles(mask);
for(var i=0,len=files.length;i<len;i++){
var file=files[i];
var doc=app.open(file, DocumentColorSpace.CMYK);
var aiFile=new File(file.absoluteURI.replace(/eps$/i,'ai'));
doc.saveAs(aiFile, undefined);
doc.close(SaveOptions.DONOTSAVECHANGES);
}

alert('処理を終わりました');

})();
これだと、maskフィルタでは.epsを判別しているだけなので、当然Photoshop EPSも全て.aiで再保存してしまう。

まず案1。Macのクリエータを使う方法。
#target "Illustrator"
(function(){

var fol=Folder.selectDialog('フォルダを選択して下さい');
if(!fol) return;

var mask=function(file){
if(/^[^\.].*\.eps/i.test(file.name)){
if(file.creator==='ART5'){
return true;
}
}
return false;
};

var files=fol.getFiles(mask);
for(var i=0,len=files.length;i<len;i++){
var file=files[i];
var doc=app.open(file, DocumentColorSpace.CMYK);
var aiFile=new File(file.absoluteURI.replace(/eps$/i,'ai'));
doc.saveAs(aiFile, undefined);
doc.close(SaveOptions.DONOTSAVECHANGES);
}

alert('処理を終わりました');

})();
maskフィルタ内で、.epsを拾った中でさらにクリエータがIllustrator形式のものだけにtrueを返している。
問題点は、Macでないと使えない事。MacでもSnowLeopard以降では動くかどうかわからない。
また、Macでもネットワークドライブ経由でコピーしたりして、クリエータを落としてしまったファイルの判別はできない。

案2。ファイルのXMPから判別する方法。
#target "Illustrator"
(function(){

var fol=Folder.selectDialog('フォルダを選択して下さい');
if(!fol) return;

var mask=function(file){
return /^[^\.].*\.eps/i.test(file.name);
};

var files=fol.getFiles(mask);
for(var i=0,len=files.length;i<len;i++){
var file=files[i];
var doc=app.open(file, DocumentColorSpace.CMYK);

var xmpStr=doc.XMPString;
var xmp=new XML(xmpStr);
var creator=xmp.xpath('/rdf:RDF/rdf:Description/xmp:CreatorTool');
if(!/Illustrator/i.test(creator)){
doc.close(SaveOptions.DONOTSAVECHANGES);
continue;
}

var aiFile=new File(file.absoluteURI.replace(/eps$/i,'ai'));
doc.saveAs(aiFile, undefined);
doc.close(SaveOptions.DONOTSAVECHANGES);
}

alert('処理を終わりました');

})();
まず.epsを判別し、一度Illustratorで開いた後、ファイルのXMPを読み出して、CreatorToolに"Illustrator"という文字が含まれていなければ保存せずに閉じる。
この方法ならWindows、Macともに使えるだろうが、ファイルにXMPを付加しない過去バージョンについては判別できない。
たぶん今も大量にあるIllustrator8形式のファイルには対応しないと思う。

案3。epsファイルのヘッダ部分を読み出す方法。
#target "Illustrator"
(function(){

var fol=Folder.selectDialog('フォルダを選択して下さい');
if(!fol) return;

var mask=function(file){
if(/^[^\.].*\.eps/i.test(file.name)){
if(file.open('r')){
var header=file.read(200);
if(/illustrator/i.test(header)){
return true;
}
}
}
return false;
};

var files=fol.getFiles(mask);
for(var i=0,len=files.length;i<len;i++){
var file=files[i];
var doc=app.open(file, DocumentColorSpace.CMYK);
var aiFile=new File(file.absoluteURI.replace(/eps$/i,'ai'));
doc.saveAs(aiFile, undefined);
doc.close(SaveOptions.DONOTSAVECHANGES);
}

alert('処理を終わりました');

})();

maskフィルタ内で.epsを拾った後、まずファイルをテキストとして一定量読み出し、そこに"illustrator"の文字が含まれているかどうかを調べ、あればtrueを返している。
これなら古いファイルにも対応するし、きっと上手くいく……と思うのだが、実際に運用してみると、ネットワークドライブのファイルを直接処理しようとするとひとつも処理できなかったり、他にも失敗する要素があるみたい。

どれが一番良いだろう?

2010年4月26日月曜日

パッケージしたFontsフォルダからフォントを収集するGroovyスクリプト

今日、上司から「AntBuilderを使うと便利だぜ」と教えてもらえたので、InDesignでパッケージしたフォントを一カ所に集めるスクリプトを書いてみた。


とても簡単。
ひとつのディレクトリに1媒体分全部のデータを集めて、ターミナルから
$ groovy collectFonts.groovy bookdir
みたいにすればFontsフォルダ内のフォントを全て、ディレクトリ直下に作ったFontsフォルダに集めてくれる。

AntというのはJavaでは定番の、Cでいうmakeのようなビルドツール。
でもそれだけでなく、ファイルをコピーしたりzipしたりしてくれる便利ツールらしい。
そういえばmakeも、そういうふうに使うんだと言ってたっけ。

20100513: Fontsフォルダの中にディレクトリがあっても収集できるように修正。

2010年4月16日金曜日

InDesignスクリプトで、アクセスするだけで落ちるオブジェクトの回避方法。

スクリプトを使っているとたまに、プロパティでアクセスするだけで落ちる(プロパティを持たない?)オブジェクトに出会うことがある。読み込んでプロパティを調べようとするだけで落ちるので,なんともしようがない。
Try & catchをうまく使って、それを回避してみる。
function checkProp(obj){
try{
var p=obj.properties;
return true;
}catch(e){
return false;
}
};

var obj=app.activeDocument.selection[0];
if(checkProp(obj)){
//実際の処理
}
こんな感じで,一回プロパティ(存在するはずのプロパティなら何でもよい)を調べて、エラーになるならfalseを返すようにしておく。
それで一度アクセスして大丈夫ならtrue、だめならfalseを返す。これでいけるはず……。

2010年3月24日水曜日

「第一回テクニカルDTP勉強会」フォロー、IllustratorのScript作成のTips

2010年3月20日「テクニカルDTP勉強会」第一回が開催され、自分も拙いながらも発表させていただきました。この記事はそのフォローアップです。

まず、スクリプトの便利さを示すために、最近書いたIllustrator用のスクリプトのデモ。
それから自分がどのようにスクリプトを書いているかを、実際に書きながら説明していきました。

自分が普段使っているツールは、動作確認のためのIllustrator(主にCS3)、ExtendScript Toolkit2、それとTextMateを併用しています。

まず、(function(){})()を書きます。
(function(){

})()
これは、「なるほどreturnすりゃ止まるんだ」に書いた通り、変数名が干渉しないようにと、スクリプトを抜けたいときreturnで抜けられるようにするためです。
次に、自分のブログの「Illustratorで、選択オブジェクトの持つプロパティと値をコンソールに書き出す」にある下の2行をコピペします。
(function(){
var o=app.activeDocument.selection[0];
(function(o){for(var i in o){try{i+=':'+o[i]}catch(e){i+=':'+e}$.writeln(i)}})(o);
})()
※InDesignで同じことをする場合は、$.writeln(obj.properties.toSource().replace(/,/g,",\n")); を使う事が多いです。

Illustrator上の調べたいオブジェクトを選択して、スクリプトを実行。



すると、ExtendScript ToolkitのJavaScriptコンソールに、選択アイテムのプロパティが書き出されます。
それらを全選択して、一旦TextMateへペーストしておきます。



次に、「オブジェクトの位置を調べたい」とするなら、そのオブジェクトの位置を少し移動してみます。



コンソールを一度クリアした上で、もう一度スクリプトを実行。コンソールに書き出された内容をコピーします。

TextMateにはdiff(テキストの差分を比較する)機能が付いています。
テキストがコピーされた状態で、Bundles→Diff→Document With Clipboard を実行します。



すると、ファイルの差分が別ウィンドウで表示されます。


これで差が表示された部分、anchor, position, width, geometricBounds, visibleBounds, controlBounds, top, leftは位置に関係したプロパティであることがわかります。
調べたいプロパティに目星がついたら、ExtendScriptのオブジェクトモデルビューアやPDFのリファレンス等で意味を調べ、スクリプトを書いて行きます。

こうやって実際に動かしながらプロパティを調べていくことで、掘り進むようにスクリプトを完成させていくわけです。

……以上が今回の勉強会で自分が発表した内容でした。
参考になりましたでしょうか?
ほぼ即興での発表になりましたので、いろいろたどたどしい、分かりにくい部分があったように思います。
また機会がありましたら、よろしくお願いします。

2010年3月5日金曜日

InDesignでテキストフレームを連結するスクリプト


選択したテキストフレームを上から順に(タテ組みなら右から順に)連結するスクリプトのサンプル。
写真のキャプションをまとめて作りたい時なんかに使えるかも。
MacOSX 10.5.8、InDesignCS3 5.0.4にて動作。他バージョンの検証はしていません。


2010.4.9追記:
@iki_osu さんに「タテ組の場合左から順になってしまう」との指摘をいただいたので、右からになるように修正しました。

2010年2月22日月曜日

GroovyからInDesignを叩く(Mac限定)

昔、ターミナル、AutomatorからAdobeアプリ用JavaScriptを呼び出すというエントリを書いたことがあるんですが、Macならコマンドを叩ける言語であれば、基本的にどんな言語からでもAppleScriptを介してExtendScriptを呼べるはずです。

なので、GroovyからInDesignを呼んでみます。
String[] init = ["osascript", "-e",
'''
tell application "Adobe InDesign CS3"
do script "

#targetengine \\"groovy\\"
$.evalFile(\\"~/Desktop/json2.js\\");

" language javascript
end tell
'''
]
init.execute()

String[] args = ["osascript", "-e",
'''
tell application "Adobe InDesign CS3"
do script "
#targetengine \\"groovy\\"
(function(){

var obj={};
var sels = app.activeDocument.selection;
for(var i=0;i<sels.length;i++){
if(sels[i].constructor.name === \\"TextFrame\\"){
var id = sels[i].id;
var cont = sels[i].contents;
obj[id] = cont;
alert(cont)
}
}
return JSON.stringify(obj);
})()

" language javascript
end tell
'''
]

def ary = []
def proc = args.execute().text.readLines().collect{ary.push(it)}
def cont = ary.join('')
println cont

実行結果。


返り値。


値のやりとりをどうするか考えたんですが、結局json2.jsを使う事にしました。
targetengineを指定することで、セッション間でも読み込んだライブラリを使い回すことができます。
一度Javaのコンパイラを通るので、スクリプト内のバックスラッシュは2重にしないといけません。

これでちょっとGroovyが流行るの希望。

2010年2月10日水曜日

ExtendScript ToolKitのヘルプから、10桁数値→人間語のハッシュマップを生成する

昨日のエントリを読んで、milligrammeさんがEnumerationの値を列挙したハッシュマップを作ってくれました。ありがとうございます!
なんと、ExtendScript Toolkitのヘルプ用のXMLファイルから、Enum値を抜き出す離れ業。
InDesign_10桁数値→人間語のハッシュマップ - diary NET. 1mg
元ネタのExtendScript Tool Kit のInDesign用のヘルプのxmlファイルは
/Users/xxxxxx/Library/Preferences/ExtendScript Toolkit/2.0/omv$indesign-5.0-ja_jp.xml(Mac CS3の場合)

自分はこれをスクリプトで抜き出してみます。(後出しジャンケンの強みで、オブジェクト名も入れてみます。)InDesignCS3、Macのみ対応。Windowsの場合のファイルパスは調べてません。また、CS4でもパスを書き換えて試したのですが動きませんでした。


最後尾のテストの、コメントアウトを外して実行して下さい。

できた!
使用する場合は、このスクリプトを basedOnEnum.jsx というファイル名で保存して、スクリプトの先頭に #include "basedOnEnum.jsx" を入れてもらえればOKです。
正直、一人ではおそらくできませんでした。
今更ながらインターネットってすごいや。

InDesignの10桁数値から意味のあるプロパティ名を得たい

InDesignのプロパティを取ろうとすると、10桁の整数で返るものがある。
たとえば、定規の単位を取ろうとして
var vUnit=app.activeDocument.viewPreferences.verticalMeasurementUnits;
alert(vUnit);
とやると

と出る。ExtendScript ToolkitのInDesignCS3オブジェクトモデルを開き、この10桁を見ながらこれが何なのか探す。書き出して、開いて、探して…
そんな風に探すのめんどくさい!意味のある言葉で返せないのか?

まず、viewPreferencesの取る値「MeasurementUnits」を見てみると
$.writeln(MeasurementUnits.toSource());

({POINTS:2054188905, PICAS:2054187363, INCHES:2053729891, INCHES_DECIMAL:2053729892, MILLIMETERS:2053991795, CENTIMETERS:2053336435, CICEROS:2053335395, Q:2054255973, HA:1516790048, AMERICAN_POINTS:1514238068, CUSTOM:1131639917, AGATES:2051106676})
undefined
と出る。要するにこれは単なるハッシュマップで、意味のある文字を書くと数値として代入されてるだけ。
だったらこれを逆マップにしてやれば…。
試しに書いてみる。
var map=(function(o,m){for(var i in o){m[o[i]]=i}return m})(RulerOrigin,{});
var map=(function(o,m){for(var i in o){m[o[i]]=i}return m})(MeasurementUnits,map);

var v=app.activeDocument.viewPreferences;
var rOrigin=v.rulerOrigin;
var vUnit=v.verticalMeasurementUnits;
var hUnit=v.horizontalMeasurementUnits;
var kUnit=v.typographicMeasurementUnits;
var tUnit=v.textSizeMeasurementUnits;
var lUnit=v.lineMeasurementUnits;

//ふつうに表示
var text="定規の単位\n開始位置:\t\t\t"+rOrigin
+"\n水平方向:\t\t\t"+vUnit
+"\n垂直方向:\t\t\t"+hUnit
+"\n組版:\t\t\t\t"+kUnit
+"\nテキストサイズ:\t\t"+tUnit
+"\n線幅:\t\t\t\t"+lUnit;
alert(text);

//意味のある
var text2="定規の単位\n開始位置:\t\t\t"+map[rOrigin]
+"\n水平方向:\t\t\t"+map[vUnit]
+"\n垂直方向:\t\t\t"+map[hUnit]
+"\n組版:\t\t\t\t"+map[kUnit]
+"\nテキストサイズ:\t\t"+map[tUnit]
+"\n線幅:\t\t\t\t"+map[lUnit];
alert(text2);

これを走らせると、ひとつ目のアラートで10桁数字を表示。

ふたつ目のアラート。

これで、Enumerationな値を全部とって同じ逆マップに入れて代入できれば、どんな数値が来ても意味のある文字列として返せるじゃないか!と思ったけど、そこまで。スクリプトでそれを全部とってリストする方法は見つからなかった。
オブジェクトモデルから全部名前ひろって、ひとつひとつ手書きで書くしかないかな…もし方法知っている人がいたら教えて下さい。

20100211追記:
milligrammeさんがEnumerationの値を列挙したハッシュマップを作ってくれました。
InDesign_10桁数値→人間語のハッシュマップ - diary NET. 1mg
それを受けて、自動でマップを生成するエントリを書きました。
ExtendScript ToolKitのヘルプから、10桁数値→人間語のハッシュマップを生成する

2010年2月4日木曜日

ExtendScriptでテキストに追記する。

ちょっと調べたメモ。
普通に
var file=new File("test.txt");
file.encoding="UTF8";
file.lineFeed="Unix";
if(file.open('w')){
file.writeln('これを\n追記\nするんだよ!');
file.close();
}
とやると、ファイルの内容が上書きされてしまう。
ポイントは2つ。
  1. File.open('e')で開く
  2. File.seek(0,2)でいちばん後ろにファイルポインタを移動させる
var file=new File("test.txt");
file.encoding="UTF8";
file.lineFeed="Unix";
if(file.open('e')){
file.seek(0,2);
file.writeln('これを\n追記\nするんだよ!');
file.close();
}

2010年1月30日土曜日

Groovyの型付け、型変換。

Groovyは単体で使うスクリプト言語ではなく、Javaのツールという扱いなので、「Groovyのためのライブラリ」というものはあまりない。なにか難しい事をするときはJavaのライブラリをそのまま使う。
ただ、Groovyは動的型付けだけど、Javaは静的型付け。
Javaに引数を渡す所で、型がそれに合ってないとエラーになる。

以下は、JavaのCSV読み書きライブラリ、opencsvを使った例。
import au.com.bytecode.opencsv.*

def myList = [
["月曜日","火曜日","水曜日"],
["モスバーガーSセット","250円弁当","ジューシーハムサンド"],
[320,262,278]
]

def writer = new FileWriter(new File("/Users/kanemu/Desktop/myList.csv"))
def cwriter = new CSVWriter(writer)
cwriter.writeAll(myList)
cwriter.flush()

これを実行すると、こんなエラーが出る。
Exception thrown: java.util.ArrayList cannot be cast to [Ljava.lang.String;

java.lang.ClassCastException: java.util.ArrayList cannot be cast to [Ljava.lang.String;
at au.com.bytecode.opencsv.CSVWriter.writeAll(Unknown Source)
at ConsoleScript2.run(ConsoleScript2:11)

これは、こうすると上手くいく。
import au.com.bytecode.opencsv.*

def myList = [
["月曜日","火曜日","水曜日"],
["モスバーガーSセット","250円弁当","ジューシーハムサンド"],
[320,262,278]
]
myList2=[]
myList.each{myList2.push(it as String[])}

def writer = new FileWriter(new File("/Users/kanemu/Desktop/myList.csv"))
def cwriter = new CSVWriter(writer)
cwriter.writeAll(myList2)
cwriter.flush()

きっちりとそのメソッドが受け取る型に変換してやらないといけない。
とくに、Javaは配列とList(java.util.ArrayList、Groovyで使うList)の違いがはっきりしているので、そのへんスクリプト感覚で使っていると足をすくわれる。

型変換の基本は「変数 as 型」。
def a = 1
assert a.class == java.lang.Integer
//文字列にする
a = a as String
assert a.class == java.lang.String
//数値にする
a = a as Integer
assert a.class == java.lang.Integer

配列とListの例。toList()、toArray()が使える。文字列をふつうにSplitするだけだとStringの配列になってしまうので注意。
def str = "わたしは,24歳,です"

//StringのArrayにする
def strAry = str.split(/,/)
//注意:SplitだけならStringのArrayになる。
assert strAry.class == java.lang.String[]

//Listに変換する
def strList = strAry.toList()
assert strList.class == java.util.ArrayList

//Arrayにする
def strAry2 = strList.toArray()
assert strAry2.class == java.lang.Object[]

//StringのArrayにもどす
def strAry3 = strList as String[]
assert strAry3.class == java.lang.String[]

2010年1月6日水曜日

Illustratorのパステキストをパスに戻すスクリプト





選択したパステキストを、パスに戻すスクリプトです。
IllustratorCS3、MacOSX10.5.8環境にて動作確認。