Flex SDKのswfutilsをハックしてみる
Flex SDKのswfutilsでswfとXMLの相互変換を試してみる で、swfxc.jarを作成し、SWFXからSWFへの変換を試してみたところ、上手くいきませんでした。 swfxc.jarのMain-ClassであるSwfxParserクラスを中心にFlex SDKのソースを読んで、swfxcが失敗する理由を探ってみました。
eclipseの準備
まず、eclipseの準備をします。準備の手順は %FLEX_SDK%/development/eclipse/readme.txt に書いてあります。importするプロジェクトを選択する画面で、flex-swfutilsを選択してください。他にcompiler等を選ぶことができます。
SwfxParser.javaの概観
SwfxParser#main()は、SwfxParserクラスのインスタンスを生成してparse()メソッドを呼んでいます。SwfxParser#parse()は、SAXParserのインスタンスを生成して、自身をイベントハンドラとしてSAXParser#parse()を呼んでいます。SwfxParserは、DefaultHandlerクラスのstartElement(), endElement(), endDocument()メソッドをオーバーライドして、SAXのイベントを処理しています。
startElement()は以下のようになっており、XMLエレメントの名前と同じ名前で、引数にAttributesクラスを取るメソッドを探して実行します。endElement()も、引数が空のメソッドを探す以外はstartElement()と同じです。例えば、<DefineSprite>タグを見つけるとSwfxParser#DefineSprite(Attributes attributes)が実行され、</DefineSprite>を見つけるとSwfxParser#DefineSprite()が実行されることになります。
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { try { Method method = getClass().getMethod(qName, new Class[]{Attributes.class}); method.invoke(this, new Object[]{attributes}); } catch (NoSuchMethodException e) { warning(new SAXParseException("no start handler for " + qName, locator)); }
これらのイベントハンドラは、基本的に、そのタグに対応するオブジェクトを生成してtagHandlerに渡します。SwfxParser#mainはtagHandlerにTagEncoderというswfバイナリを出力するクラスのオブジェクトを登録しているので、swfファイルが出力される事になります。
他に重要な部分に、SwfxParserのメンバ変数のdictとstackがあります。Define系タグを処理するときにdictにidとオブジェクトを記憶させておいて、後にPlaceObject等を処理するときにid経由で参照先を探すのに利用します。stackは、DefineShapeとlineやcurveのようにタグとデータ構造がツリー状になっているときに使います。親タグの開始時にstack.push()し、子データを処理するときにstack.peek()で参照した親にデータを登録し、親タグの終了時にstack.pop()します。
klab.swfxをswfに変換させてみる
と、SwfxParserが理解できたところで、何でklab.swfxからklab.swfが生成できなかったかを調べてみます。 例外ハンドラを幾つか追加して調べてみたところ、dictからid=3のオブジェクトを探して見つからないために、そこで終了していました。klab.swfxのなかでid=3のオブジェクトを探したところ、DefineTextでした。SwfxParser#DefineText()メソッドが無いために、dictにid=3のDefineTextオブジェクトが登録されておらず、エラーになっています。DefineTextの部分のswfxは次のようになっています。
<DefineText id='3' bounds='(106,414),(2570,1654)' matrix='t0,0'> <textRecord font='Verdana' height='1620' yOffset='1620' color='#00000000'> 1+1152 </textRecord> </DefineText>
DefineText以外にも大量にタグのハンドラが足りてない気がするのは無視して、とりあえずこのswfxのparseに挑戦します。
まず、<textRecord>に対応するSwfxParser#textRecord()を作ります。楽勝・・・と思いきや、font='Verdana'
で引っかかりました。フォントは、DefineTextの前にDefineFont2タグで定義されており、id='2'が割り当てられています。idが一意なのに対してフォント名は一意ではないので、この部分はフォント名ではなくidを参照するべきです。とりあえず、その場しのぎでごまかしたものが次のコードになります。
public void textRecord(Attributes attributes) throws SAXParseException { TextRecord tr = new TextRecord(); String fontname = getAttribute(attributes, "font"); tr.setFont(dict.getFontFace(fontname, true, false)); // set dummy value to bold and italic. tr.setHeight(parseInt(getAttribute(attributes, "height"))); tr.setY(parseInt(getAttribute(attributes, "yOffset"))); tr.setColor(parseColor(getAttribute(attributes, "color"))); DefineText dt = (DefineText)stack.peek(); dt.records.add(tr); } public void textRecord() { }
次はSwfxParser#DefineText()です。これは特に引っかかることなく次のように実装しました。
public void DefineText(Attributes attributes) throws SAXException { DefineText dt = new DefineText(stagDefineText); int id = parseInt(getAttribute(attributes, "id")); dt.bounds = parseRect(getAttribute(attributes, "bounds")); dt.matrix = parseMatrix(getAttribute(attributes, "matrix")); createCharacter(id, dt); stack.push(dt); } public void DefineText() { DefineText dt = (DefineText)stack.pop(); tagHandler.defineText(dt); }
これで動くか?と思ったら、まだダメでした。引っかかったのはparseRect()
です。swfxを生成するときは、Rect#toString()を利用していて、swfxを読み込むときにはSwfxParser#parseRect()を利用しているのですが、Rect#toString()が状況によって2種類のフォーマットを使い分けて出力していて、SwfxParser#parseRect()はその片方にしか対応していないために、parseに失敗していました。
SwfxParser#parseRect()をRect#toString()にあわせて拡張したらまた先に進めるハズです。が、ここでキレました。そもそも、Rect#toString()があまりイケてません。しかも、rect.equals(parseRect(rect.toString())) == true
を満たすようなparseRect()がSwfxParserのメソッドというのもどうかと思います。SwfxParser#parseRect()じゃなくてRect#fromString()であるべきでは無いでしょうか?せめて同じpackageにあるべきですよね?swfxのフォーマットも含めていろいろ修正したいです。
でも、Flex SDK プロジェクトと離れたところで頑張って修正するのももったいないです。ということで、Flex SDKを使ってSWF⇔XML相互変換は一旦あきらめます。これからは、swf弄りは今までどおりswfmillを使いつつ、Flex SDKに対してはプロジェクトのforumで議論してパッチ作成という形で参加して行こうと考えています。
結局こんなオチになってしまってすみません。Flex SDKプロジェクトでswfutils.jarへのAdobe外部からの貢献が多くなれば、AdobeもSWF File Format SpecificationのEULA(swfutils.jarの修正すら不可能)を変更してくれる気になるかもしれないので、swf関係者の皆さんも一緒にFlex SDKをハックしてみましょう。