ふりがなクラス

ふりがなを使えるようにしたラベルっぽいクラス。HBox とかに適当に addChild したオブジェクトの x や y が設定されなかったので、文字数とかから無理矢理場所を計算したりしている。書くのに無駄に時間がかかってしまった…。しかも後半は疲れてあまり考えずに書いているので凡ミスとか意味不明な箇所があるかも。
id:suerさん、id:mallowlabsさん、id:k-tokinoさんの助言等に感謝致します。
こんな感じ。

以下ソース。

package hurigana
{
  import mx.containers.Canvas;
  import mx.containers.VBox;
  import mx.controls.Label;
  
  public class HuriganaText extends VBox
  {
    private var huriganabox : Canvas = new Canvas();
    private var bodyLabel : Label = new Label();
    public  var bodyFontSize : int = 0;
    public  var huriganaFontSize : int = 0;
    public  var spaceSize : int = 0;
    public  var offset : int = 0;
    
    /**
     * 専用の簡易マークアップ言語のパーサ
     * example: "これは[日本語|にほんご]の[文章|ぶんしょう]です。"
     * バックスラッシュでエスケープ
     * @param str パースするテキスト
     * @return ふりがな付き単語の配列
     */
    private function parseHuriganaText(str : String) : Array {
      function createAndAppendWord(p : Function) : void {
        var word : HuriganaWord = new HuriganaWord();
        p(word);
        hwords.push(word);
      }
      
      var pos : int = 0;          // 読み込み位置
      var count : int = 0;        // ふりがな部を埋める空白文字数
      var hwords : Array = new Array();  // ふりがなつき単語(HuriganaWord)の配列
      
      const NOT_IN_HWORDS : int = 0;
      const IN_KANJI_PART : int = 1;
      const IN_KANA_PART : int = 2;
      var mode : int = NOT_IN_HWORDS;    // パーサの状態
      var numOfHurigana : int = 0;    // これまでに読んだふりがなブロックの数
      
      var read_str : String = "";      // 読んだ文字列
      var read_kana : String = "";    // 読んだふりがな
      
      while (pos < str.length)
      {
        switch ( str.charAt(pos) ) {
          case '[':
            if ( mode == NOT_IN_HWORDS ) {
              createAndAppendWord(
                function (word : HuriganaWord) : void {
                  word.body = read_str;
                  word.pos = pos - (read_str.length + numOfHurigana);
                  read_str = "";
                  word.kana = "";
                  read_kana = "";
                  mode = IN_KANJI_PART;
                });
            } else {
              trace("parse error:  pos "+pos.toString());
            }
            break;
          case '|':
            if ( mode == IN_KANJI_PART ) {
              mode = IN_KANA_PART;
            } else {
              trace("parse error:  pos "+pos.toString());
            }
            break;
          case ']':
            if ( mode == IN_KANA_PART ) {
              createAndAppendWord(
                function(word : HuriganaWord) : void{
                  word.body = read_str;
                  word.kana = read_kana;
                  word.pos = pos - (read_str.length + read_kana.length + numOfHurigana + 2);
                  numOfHurigana += 1 + read_kana.length + 2;
                  read_str = "";
                  read_kana = "";
                  mode = NOT_IN_HWORDS;
                });
            } else {
              trace("parse error:  pos "+pos.toString());
            }
            break;
          case '\\':
            if ( str.charAt(pos+1) != '[' && str.charAt(pos+1) != '|' &&
               str.charAt(pos+1) != ']' && str.charAt(pos+1) != '\\' )
            {
              trace("parse error(backslash):  pos "+pos.toString());
              return hwords;
            } else {
              if ( mode == IN_KANA_PART ) {
                read_kana += str.charAt(pos+1);
              } else {
                read_str += str.charAt(pos+1);
              }
              pos += 1;
            }
            break;
          default:
            if ( mode == IN_KANA_PART ) {
              read_kana += str.charAt(pos);
            } else {
              read_str += str.charAt(pos);
            }
            break;
        }
        pos += 1;
      }
      // 末尾の文章を出力
      createAndAppendWord(
        function(word : HuriganaWord) : void{
          word.body = read_str;
          word.kana = "";
          word.pos = pos - read_str.length;
        });
      
      return hwords;
    }
    
    /**
     * ふりがな付き単語リストを使って内容を更新
     * @param hwords ふりがな付き単語の配列
     * @return なし
     */
    private function updateContents(hwords : Array) : void {
      // ふりがな付き単語リストを反映
      for each (var word : HuriganaWord in hwords) {
        // 本文を作成
        bodyLabel.text += word.body;
        
        if ( word.kana == "" ) {
          continue;
        }
        
        // ふりがながあったらふりがなのテキストボックスも作成
        var mxlabel : Label = new Label();
        mxlabel.setStyle("fontSize", this.huriganaFontSize.toString());
        mxlabel.text = word.kana;
        mxlabel.x = this.spaceSize * word.pos + this.offset;
                
        huriganabox.addChild(mxlabel);
      }
    }
    
    /**
     * @param str マークアップ文字列
     * @param spaceSize 空白文字の幅
     * @param bodyFontSize 本文のフォントサイズ
     * @param huriganaFontSize ふりがなのフォントサイズ
     * @param offset ふりがなの位置を補正するオフセット
     * @return なし
     */
    public function HuriganaText(str : String, spaceSize : int, bodyFontSize : int = 12, 
      huriganaFontSize : int = 6, offset : int = 0) : void
    {
      this.bodyFontSize = bodyFontSize;
      this.huriganaFontSize = huriganaFontSize;
      this.bodyLabel.setStyle("fontSize", bodyFontSize.toString());
      this.spaceSize = spaceSize;
      this.offset = offset;
      
      this.setStyle("verticalGap", "0");
      
      this.addChild(huriganabox);
      this.addChild(bodyLabel);
      updateContents(parseHuriganaText(str));
    }
  }
}
package hurigana
{
  public class HuriganaWord
  {
    public var kana : String = "";
    public var body : String = "";
    public var pos : int = 0;
  }
}