naritocの日記

2004-08-12 蝉がうるさい…

[]フォントの指定

非常に非常に貴重なサンプルを見る限り、フォントの指定は名前で行っていたので真似してみたが一向にフォントが変わらない。別段間違ったフォント名を指定してもエラーにはならない。でもってググってみたら、PerlMagickで同じようにハマった人がいてその時の対処をJMagickでも試してみた…おっ、日本語が表示された。至って単純、フォントファイルのフルパスを次のような感じで指定してあげればよい。

 DrawInfo dinfo = new DrawInfo(new ImageInfo("sample.jpg"));
 dinfo.setGravity(GravityType.SouthEastGravity); // 右下に表示
 dinfo.setPointsize(30); // 文字サイズ
 PixelPacket packet = PixelPacket.queryColorDatabase("green"); // 文字の色
 dinfo.setFill(packet); 
 dinfo.setOpacity(0);
 dinfo.setFont("C:\\WINDOWS\\Fonts\\msgothic.ttf"); // MSゴシックの場合
 dinfo.setText("日本語テスト"); // 表示する文字
 dinfo.setTextAntialias(true);

でもって、これをMagickImage#annotate(DrawInfo)に渡してあげればよい。

[][]batik入門記その1 〜インストール編〜

ひょんな事からSVGを触ることになったので、Javaだと何か便利そうなのないかなと探したら、batikなるものを見つけた。他にもないか探したが、特に目に付くものはなかったので早速インストールからはじめてみる。

 ・System Requirements

  -JVM:A Java 1.3 or greater compatible virtual machine must be present.

 ・Choosing a distribution to download

  -batik-version.zip (e.g., batik-1.5.zip)

  -batik-src-version.zip (e.g., batik-src-1.5.zip)

  -batik-docs-version.zip

 ・Optional Components

  -Mozilla Rhino JavaScript Engine (js.jar)←デフォルト

  -Python, Then you put the jython.jar file in the Batik lib directory.

  -Tcl, Then you put the jacl.jar & tcljava.jar files in the Batik lib directory.

OptionalについてはどうせスクリプトJavaScriptだから無視。てか、PythonだのTclだのはまったく知らないし、Rhinoなんてのがあること自体知らんかった。

まぁ、あとはアーカイブ解凍してあげればインストールは終了ですかな。

[][]batik入門記その2 〜Squiggle-the SVG Browser解析編1〜

ってことでさっそくサンプルアプリとして付属しているSVGBrowserを見てみることに。Browserの起動はこんな感じ。バイナリ版を解凍したディレクトリのbatik-squiggle.jarがSVGBrowser本体。

 cd <installationDirectory datetime="2004-08-12T12:11:26+09:00">
 java -jar batik-squiggle.jar

とりあえずサムネイル機能はナイス。Scriptingがどうなのか気になる。ドキュメント自体はBrowserの機能を丁寧に説明してくれているのだが、所詮使い方の説明なのであまり参考にはならなかった。まぁbatikの持つ機能を概観するにはなかなかよいサンプルだったのかな。

といきなりBrowserに手を出すのは早いって事で、SVGを表示してくれるJSVGCanvasのドキュメントにあるSVGを単純に表示するだけのアプリを作ることにした。GUIアプリは久しく作ってなかったのでちょっと楽しみである反面、いろいろとハマった思い出が…

SVGの表示は非常に簡単。JSVGCanvas#setURI(String)にSVGファイルパスを食わせてあげれば後は勝手に読み込んで表示してくれる。う〜ん、便利だ。とこれだけだと何なので、JSVGCanvasに設定できるリスナをちらっと調べてみた。

 ・SVGDocumentLoaderListener:SVGDocumentLoaderEventを拾う

  -void documentLoadingCancelled(SVGDocumentLoaderEvent e) 
  -void documentLoadingCompleted(SVGDocumentLoaderEvent e) 
  -void documentLoadingFailed(SVGDocumentLoaderEvent e) 
  -void documentLoadingStarted(SVGDocumentLoaderEvent e)  

 SVGのロードに関するイベントをお知らせしてくれる。

 ・GVTTreeBuilderListener:GVTTreeBuilderEventを拾う

  -void gvtBuildCancelled(GVTTreeBuilderEvent e) 
  -void gvtBuildCompleted(GVTTreeBuilderEvent e) 
  -void gvtBuildFailed(GVTTreeBuilderEvent e) 
  -void gvtBuildStarted(GVTTreeBuilderEvent e)  

 DOMツリーの構築に関するイベントをお知らせしてくれる。

 ・SVGLoadEventDispatcherListener:SVGLoadEventDispatcherEventを拾う

  -void svgLoadEventDispatchCancelled(SVGLoadEventDispatcherEvent e) 
     Called when a onload event dispatch was cancelled. 
  -void svgLoadEventDispatchCompleted(SVGLoadEventDispatcherEvent e) 
      Called when a onload event dispatch was completed. 
  -void svgLoadEventDispatchFailed(SVGLoadEventDispatcherEvent e) 
      Called when a onload event dispatch failed. 
  -void svgLoadEventDispatchStarted(SVGLoadEventDispatcherEvent e) 
      Called when a onload event dispatch started. 

 This event is triggered only in dynamic documentsなんだってさ。onload eventって

 ひょっとしていつものあれか?

 ・GVTTreeRendererListener:GVTTreeRendererEventを拾う

  -void gvtRenderingCancelled(GVTTreeRendererEvent e) 
  -void gvtRenderingCompleted(GVTTreeRendererEvent e) 
  -void gvtRenderingFailed(GVTTreeRendererEvent e) 
  -void gvtRenderingPrepare(GVTTreeRendererEvent e) 
  -void gvtRenderingStarted(GVTTreeRendererEvent e)  

 イメージのレンダリングに関するイベントをお知らせしてくれる。

 In dynamic documents this event is fired only once for the initial renderingだとか。

 ・UpdateManagerListener:UpdateManagerEventを拾う

  -省略

 どうやらUpdateManagerなるものがいて、そいつの起動だの停止だののイベントをお知らせ

 してくれるようだが、現時点ではようわからん。まぁ必要ならそのうち調べるだろう…

 Only dynamic documents trigger this eventなのだ。

それぞれ、以下のメソッドでリスナを設定する。

 public void addSVGDocumentLoaderListener(SVGDocumentLoaderListener l)
 public void addGVTTreeBuilderListener(GVTTreeBuilderListener l)
 public void addSVGLoadEventDispatcherListener(SVGLoadEventDispatcherListener l)
 public void addGVTTreeRendererListener(GVTTreeRendererListener l) 
 public void addUpdateManagerListener(UpdateManagerListener l)

まぁ1、2と4番目のリスナは今回使ったように進捗を表示するくらいにしか使わないんだろうなぁ。結局これらは肝心のScriptingには全く関係ない。ってかさっさとScriptingがどの程度、どんな感じで実現できるのか調べておかないと後で大変なんじゃない?と言っても選択肢としてこれぐらいっきゃないから、なきゃないで何とかしないといけないんだよなぁ…

それにしても、jarファイルかなり分割してあるけど単にSVGを表示するだけでほとんど全部使ってるし。きっとScriptingしたらさらにあいつも追加になるんだろう。

んで、静的なドキュメントは

 1.parsing

 2.building

 3.rendering

てな感じで表示されて、setURI/setSVGDocumentで開始されるんだけど、それは gvtRenderingCompleted で完了する、と。一方の動的なドキュメントは

 1.parsing

 2.building

 3.SVGLoad dispatch

 4.initial rendering

 5.updates

てな感じで流れて、updateManagerStopped で完了する、と。

[][]batik入門記その3 〜Squiggle-the SVG Browser解析編2〜

JSVGCanvasへの機能追加は、Interactor なるインタフェースを実装すればいいらしい。デフォルトでは以下のInteractor が組み込まれている。

  /**
     * An interactor to perform a zoom.
     * Binding: BUTTON1 + CTRL Key
     */
    protected Interactor zoomInteractor = new AbstractZoomInteractor() {
        public boolean startInteraction(InputEvent ie) {…}
    };
    /**
     * An interactor to perform a realtime zoom.
     * Binding: BUTTON3 + SHIFT Key
     */
    protected Interactor imageZoomInteractor
        = new AbstractImageZoomInteractor() {
        public boolean startInteraction(InputEvent ie) {…}
    };
    /**
     * An interactor to perform a translation.
     * Binding: BUTTON1 + SHIFT Key
     */
    protected Interactor panInteractor = new AbstractPanInteractor() {
        public boolean startInteraction(InputEvent ie) {…}
    };
    /**
     * An interactor to perform a rotation.
     * Binding: BUTTON3 + CTRL Key
     */
    protected Interactor rotateInteractor = new AbstractRotateInteractor() {
        public boolean startInteraction(InputEvent ie) {…}
    };
    /**
     * An interactor to reset the rendering transform.
     * Binding: CTRL+SHIFT+BUTTON3
     */
    protected Interactor resetTransformInteractor =
        new AbstractResetTransformInteractor() {
        public boolean startInteraction(InputEvent ie) {…}
    };

んで、JSVGanvasのデフォルトコンストラクタを見ると

 JSVGCanvas(SVGUserAgent, boolean, boolean)

を this(null, true, true)で呼び出している。とりあえずSVGUserAgentは置いとくとして、中では、

 List intl = getInteractors();
  intl.add(zoomInteractor);
  intl.add(imageZoomInteractor);
  intl.add(panInteractor);
  intl.add(rotateInteractor);
  intl.add(resetTransformInteractor);

のようにデフォルトInteractor をすべて登録している。ってことはさっき作ったすかすかアプリでも既にこれらの機能が使用可能ってことですかい?

う〜ん、確かにいろいろとイベントにくっ付いているみたいだけど、何だか上の宣言のとおりに動いていないのがあるような…とりあえずこの辺の基本的なのは用意してある、と。後でちゃんと動作検証しないといけませんな。

[][]batik入門記その4 〜Squiggle-the SVG Browser解析編3〜

UserAgentについて調べてみることにした。そもそも、UserAgentなるものは以下の二つがある。

それぞれ、

 public interface UserAgent
  An interface that provides access to the User Agent informations 
 needed by the bridge. 

 public interface SVGUserAgent
  This interface must be implemented to provide client services to 
 a JSVGComponent. 

となっている。UserAgentって何?ドキュメントにも何もんだか記載がないためようわからない。そもそもこの二つって非常に似ててわかりづらいよ。ざっと見API同じなんですけど…全く継承関係ないんですよね。

全然見えてこないので、とりあえずSVGBrowserでどうなってるかを調べることにする。なのでJSVGViewerFrameにZoomIn。う〜ん、独自にSVGUserAgentを実装している。そっか、mustなんだもんね。

 protected class UserAgent implements SVGUserAgent{…}

駄目だ…まるでわからない。アプリ(独自実装のViewr)固有の設定情報、エラー発生時の挙動等を記述するっぽいのだがそんなに必要なのか?と正直思う。JSVGCanvasではそのUserAgent(中身はSVGUserAgent)を受け取って、UserAgentは自分で作っている。中身の方はというと、SVGUserAgentに頼れる所は処理を委譲して、それ以外であれば自分(UserAgent)で処理をしている。これ以上は深入り出来そうにないので悔しいけれど無視して進むことにしよう。

[][]batik入門記その5 〜Squiggle-the SVG Browser解析編4〜

いいかげん疲れたのでちょっと便利なサムネイルを自分で作ったスカスカアプリで使えるように改造しよう。で、すぐに出来てしまった。そりゃ該当部分をコピペするだけだもの。さて後何して遊ぼうか。

とりあえず以下の2点について調べることにしよう。

[][]batik入門記その6 〜イベントハンドリング編1〜

理想としては、

  1. ViewerPort上でマウスクリック
  2. ポイント上のElementでイベントFire
  3. Elementを使ってフガフガ…

ってのを追い求めていたのだけれど、一向にやり方が、というか出来るのかもよくわからない。Scriptingのドキュメントを見たら独自にbatikで定義したメソッドをJavaScriptから呼び出すというサンプルがありました。これだとSVGにonclickだとかのハンドリングを自分でパースするときに書き込まないといけないから、スマートじゃないしやりたい事とは違うんだけれど、最悪の場合を想定してこの方法で出来ることをまずは確認しておくことにする。

で早速サンプルをコピペしてさくっと動作確認するぞと思いきや、全然ダメダメじゃないですか…セミコロンついてなかったり、定義されていないデフォルトコンストラクタを呼び出していたり…これほんとに動くの?って感じ。とりあえずコンパイルエラーをなくしたのがこれ。

public class ExtendedRhinoInterpreter extends RhinoInterpreter {
    public ExtendedRhinoInterpreter(URL documentURL) {
        super(documentURL); // build RhinoInterpreter
        final String[] names = { "print" };
        try {
            getGlobalObject().
	            defineFunctionProperties(names, 
	                          ExtendedRhinoInterpreter.class,
                                  ScriptableObject.DONTENUM);
        } catch (PropertyException e) {
            throw new Error(e.getMessage());
        }
    }
    
    public static void print(Context cx, Scriptable thisObj,
                             Object[] args, Function funObj) {
        for (int i=0; i < args.length; i++) {
            if (i > 0)
                System.out.print(" ");
	    
            // Convert the arbitrary JavaScript value into 
            // a string form.
            String s = Context.toString(args[i]);
	    
            System.out.print(s);
        }
        System.out.println();
    }
}

”print”ってメソッドを定義してそいつをECMAScriptってJavaScriptエンジンに登録する。こいつをSVGScriptの中で呼んであげればその引数がコンソールに出力されるはず、…だよね。後はこいつのFactoryをこんな感じで作って、

public class ExtendedRhinoInterpreterFactory extends RhinoInterpreterFactory {
  public Interpreter createInterpreter(URL documentURL) {
     return new ExtendedRhinoInterpreter(documentURL);
  }
}

JSVGCanvasのサブクラスでcreateBridgeContext()をオーバーライドして、BridgeContextに上のFactoryを登録してあげればよい。

class CustomSVGCanvas extends JSVGCanvas {
  protected BridgeContext createBridgeContext() {
    BridgeContext ctx = super.createBridgeContext();
    ctx.getInterpreterPool().putInterpreterFactory(
        "text/ecmascript", new ExtendedRhinoInterpreterFactory());
    return ctx;
  }
}

んで、適当SVGファイルを作ってあげて例えばマウスクリックでprintを呼び出す場合は、

 <circle cx="?" cy="?" r="?" style="?" onclick="print('onclick')"/>

としてやればよい。circleで描画された円の中でマウスクリックするたびにコンソールにonclickと表示される。

ScriptingをSVGBrowserでAdobeのビューワと同じように処理していたけど、今まで何でどうやってるんだろ?って不思議に思わなかったのが不思議Adobeの場合はIEプラグインだからIEJavaScriptエンジン使ってるんだろう。batikの場合はECMAScriptエンジン(js.jar)に処理を委譲してエンジン勝手Scriptを解釈してくれている。この辺て昔JavaScriptApplet間の相互呼び出しでハマッてた頃に知ってたらなぁ…としみじみ。確かJava-Plugin使うとJavaScriptからAppletが呼び出せなくて苦労したような。

で、ここでふと思ったのは、onclickのイベント自体は何らかの形でbatikが拾ってくるはず。パースの際にどっかに持ってるのかどうかようわからんが、その辺を調べればbatik上からマウスクリックを直接とれてもいいんでないか?と。

これで最悪の場合の逃げ道も確保できたわけだし、たまには答えに向かって一直線につっぱしろうとせずに、遠回りしてみるのも…あっ、これがいわゆる急がば回れってことですな。うん、納得!

追記:

 ドキュメントを見たら”Scripting With Java”ってのがあったよ…きっとここに求めていたものが書かれているように思える。はぁ、今回はただの遠回りに終わったか。