2008年7月5日星期六

AS3 - BitmapText 程式分享

Ticore's Blog

由於 Flash Player 一直以來都無法對系統字體的文字作旋轉與不規則形狀遮罩
可是到了 Flash Player 9 AS3,可以將文字繪製成點陣圖
也可以自行繼承內建類別 Sprite, TextField 等
於是想到做一個 TextField Wrapper,提供系統文字的點陣化功能
藉以達到文字可旋轉、套用不規則遮罩

其實這個 BitmapText 程式去年就已經寫了
主要是出於研究目的
專案一次也沒有用到過
沒想到最近新出的 Flash Player 10 beta 文字引擎
已經可以使用系統字體作旋轉了
倘若再不把這程式拿出來使用一下,恐怕就這樣永不見天日了~~><

主要是包含兩個類別 TextWrapper, BitmapText
TextWrapper 只是 TextField 的外覆類別
真正在做點陣繪圖的是 BitmapText
由於 AS3 TextField 成員屬性與方法相當多
我已經很盡量的複寫大部分的方法了

TextWrapper Class:

/*
TextWrapper AS3 v0.0.2
 
Date: 2007.04.25
作者: Ticore Shih
Blog:http://ticore.blogspot.com
EMail: swl@ms53.url.com.tw
 
v0.0.1
繼承 Sprite 包覆一個 TextField
 
v0.0.2
增加 TextField 一般性方法
*/

package com.ticore.text {
 
 import flash.geom.*;
 import flash.display.*;
 import flash.text.*;
 import flash.events.*;
 
 public class TextWrapper extends Sprite {
  //
  // Protected Property
  //
  protected var _txt_:TextField;
  //
  // Constructor
  //
  public function TextWrapper() {
   init();
  }
  protected function init():void {
   _txt_ = new TextField();
   this.addChild(_txt_);
  }
  //
  // Override DisplayObject Property and Method
  //
  public override function set width(w:Number):void {
   _txt_.width = w;
  }
  public override function get width():Number {
   return _txt_.width;
  }
  
  public override function set height(h:Number):void {
   _txt_.height = h;
  }
  public override function get height():Number {
   return _txt_.height;
  }
  //
  // Proxy Common TextField Property
  //
  public function set antiAliasType(value:String):void {
   _txt_.antiAliasType = value;
  }
  public function get antiAliasType():String {
   return _txt_.antiAliasType;
  }
  
  public function set autoSize(value:String):void {
   _txt_.autoSize = value;
  }
  public function get autoSize():String {
   return _txt_.autoSize;
  }
  
  public function set gridFitType(value:String):void {
   _txt_.gridFitType = value;
  }
  public function get gridFitType():String {
   return _txt_.gridFitType;
  }
  
  public function set htmlText(value:String):void {
   _txt_.htmlText = value;
  }
  public function get htmlText():String {
   return _txt_.htmlText;
  }
  
  public function set restrict(value:String):void {
   _txt_.restrict = value;
  }
  public function get restrict():String {
   return _txt_.restrict;
  }
  
  public function set text(txt:String):void {
   _txt_.text = txt;
  }
  public function get text():String {
   return _txt_.text;
  }
  
  public function set type(type:String):void {
   _txt_.type = type;
  }
  public function get type():String {
   return _txt_.type;
  }
  
  
  public function set backgroundColor(value:uint):void {
   _txt_.backgroundColor = value;
  }
  public function get backgroundColor():uint {
   return _txt_.backgroundColor;
  }
  
  public function set borderColor(value:uint):void {
   _txt_.borderColor = value;
  }
  public function get borderColor():uint {
   return _txt_.borderColor;
  }
  
  public function set textColor(value:uint):void {
   _txt_.textColor = value;
  }
  public function get textColor():uint {
   return _txt_.textColor;
  }
  
  
  public function get bottomScrollV():int {
   return _txt_.bottomScrollV;
  }
  
  public function get caretIndex():int {
   return _txt_.caretIndex;
  }
  
  public function get length():int {
   return _txt_.length;
  }
  
  public function get maxChars():int {
   return _txt_.maxChars;
  }
  
  public function get maxScrollH():int {
   return _txt_.maxScrollH;
  }
  
  public function get maxScrollV():int {
   return _txt_.maxScrollV;
  }
  
  public function get numLines():int {
   return _txt_.numLines;
  }
  
  public function get selectionBeginIndex():int {
   return _txt_.selectionBeginIndex;
  }
  
  public function get selectionEndIndex():int {
   return _txt_.selectionEndIndex;
  }
  
  
  public function set sharpness(value:Number):void {
   _txt_.sharpness = value;
  }
  public function get sharpness():Number {
   return _txt_.sharpness;
  }
  
  public function set thickness(value:Number):void {
   _txt_.thickness = value;
  }
  public function get thickness():Number {
   return _txt_.thickness;
  }
  
  public function get textHeight():Number {
   return _txt_.textHeight;
  }
  public function get textWidth():Number {
   return _txt_.textWidth;
  }
  
  
  public function set alwaysShowSelection(value:Boolean):void {
   _txt_.alwaysShowSelection = value;
  }
  public function get alwaysShowSelection():Boolean {
   return _txt_.alwaysShowSelection;
  }
  
  public function set border(value:Boolean):void {
   _txt_.border = value;
  }
  public function get border():Boolean {
   return _txt_.border;
  }
  
  public function set condenseWhite(value:Boolean):void {
   _txt_.condenseWhite = value;
  }
  public function get condenseWhite():Boolean {
   return _txt_.condenseWhite;
  }
  
  public function set displayAsPassword(value:Boolean):void {
   _txt_.displayAsPassword = value;
  }
  public function get displayAsPassword():Boolean {
   return _txt_.displayAsPassword;
  }
  
  public function set embedFonts(value:Boolean):void {
   _txt_.embedFonts = value;
  }
  public function get embedFonts():Boolean {
   return _txt_.embedFonts;
  }
  
  public function set mouseWheelEnabled(value:Boolean):void {
   _txt_.mouseWheelEnabled = value;
  }
  public function get mouseWheelEnabled():Boolean {
   return _txt_.mouseWheelEnabled;
  }
  
  public function set multiline(value:Boolean):void {
   _txt_.multiline = value;
  }
  public function get multiline():Boolean {
   return _txt_.multiline;
  }
  
  public function set selectable(value:Boolean):void {
   _txt_.selectable = value;
  }
  public function get selectable():Boolean {
   return _txt_.selectable;
  }
  
  public function set useRichTextClipboard(value:Boolean):void {
   _txt_.useRichTextClipboard = value;
  }
  public function get useRichTextClipboard():Boolean {
   return _txt_.useRichTextClipboard;
  }
  
  public function set wordWrap(value:Boolean):void {
   _txt_.wordWrap = value;
  }
  public function get wordWrap():Boolean {
   return _txt_.wordWrap;
  }
  //
  // Proxy Common TextField Method
  //
  public function appendText(text:String):void {
   _txt_.appendText(text);
  }
  
  public function getCharBoundaries(charIndex:int):Rectangle {
   return _txt_.getCharBoundaries(charIndex);
  }
  
  public function getCharIndexAtPoint(x:Number, y:Number):int {
   return _txt_.getCharIndexAtPoint(x,y);
  }
  
  public function getFirstCharInParagraph(charIndex:int):int {
   return _txt_.getFirstCharInParagraph(charIndex);
  }
  
  public function getImageReference(id:String):DisplayObject {
   return _txt_.getImageReference(id);
  }
  
  public function getLineIndexAtPoint(x:Number, y:Number):int {
   return _txt_.getLineIndexAtPoint(x,y);
  }
  
  public function getLineIndexOfChar(charIndex:int):int {
   return _txt_.getLineIndexOfChar(charIndex);
  }
  
  public function getLineLength(lineIndex:int):int {
   return _txt_.getLineLength(lineIndex);
  }
  
  public function getLineMetrics(lineIndex:int):TextLineMetrics {
   return _txt_.getLineMetrics(lineIndex);
  }
  
  public function getLineOffset(lineIndex:int):int {
   return _txt_.getLineOffset(lineIndex);
  }
  
  public function getLineText(lineIndex:int):String {
   return _txt_.getLineText(lineIndex);
  }
  
  public function getParagraphLength(charIndex:int):int {
   return _txt_.getParagraphLength(charIndex);
  }
  
  public function replaceSelectedText(value:String):void {
   _txt_.replaceSelectedText(value);
  }
  
  public function replaceText(beginIndex:int, endIndex:int, newText:String):void {
   _txt_.replaceText(beginIndex,endIndex,newText);
  }
  
  public function setTextFormat(format:TextFormat, beginIndex:int = -1, endIndex:int = -1):void {
   _txt_.setTextFormat(format,beginIndex,endIndex);
  }
  public function getTextFormat(beginIndex:int = -1, endIndex:int = -1):TextFormat {
   return _txt_.getTextFormat(beginIndex,endIndex);
  }
  
  public function set defaultTextFormat(format:TextFormat):void {
   _txt_.defaultTextFormat = format;
  }
  public function get defaultTextFormat():TextFormat {
   return _txt_.defaultTextFormat;
  }
  
 }
}

BitmapText Class:

/*
BitmapText AS3 v0.0.4
 
Date: 2007.04.25
作者: Ticore Shih
Blog:http://ticore.blogspot.com
EMail: swl@ms53.url.com.tw
 
v0.0.1
利用 BitmapData draw TextFIeld
達到可以呈現旋轉文字的效果
 
v0.0.2
增加 TextField 一般性方法與屬性控制
增加 validate 屬性,減少不必要 draw 次數
 
TextField.autoSize 功能會有一些問題
TextField 大小會一直跳動,重複 draw 會發生多重影像
 
v0.0.3
增加 TextField 一般性方法與屬性控制
 
v0.0.4
重構將 TextWrapper 功能獨立為單一 Class
*/

package com.ticore.text {
 
 import flash.geom.*;
 import flash.display.*;
 import flash.text.*;
 import flash.events.*;
 import flash.filters.*;
 
 public class BitmapText extends TextWrapper {
  
  // Private Property
  private var _validate_:Boolean = true;
  private var magniFactor:Number = 3;
  private var blurFilter:BlurFilter;
  private var _useBitmapText_:Boolean = true;
  
  private var bmpData:BitmapData;
  private var bmp:Bitmap;
  
  public function BitmapText() {
   init();
  }
  protected override function init():void {
   super.init();
   _txt_.addEventListener(Event.CHANGE, onChange);
   bmp = new Bitmap();
   bmp.smoothing = true;
   bmp.scaleX = bmp.scaleY = 1 / magniFactor;
   this.addChild(bmp);
   _txt_.visible = false;
   this.addChild(_txt_);
   blurFilter = new BlurFilter(1 + magniFactor / 6, 1 + magniFactor / 6, BitmapFilterQuality.MEDIUM);
   validate = false;
   addEventListener(Event.ENTER_FRAME, onEnterFame);
  }
  //
  // Private Method
  //
  private function updateBmpData():void {
   if (bmpData) {
    bmpData.dispose();
   }
   bmpData = new BitmapData((width + 1) * magniFactor, (height + 1) * magniFactor, true, 0x00000000);
   bmp.bitmapData = bmpData;
   bmp.smoothing = true;
   validate = false;
  }
  
  private function onChange(eventObj:Event):void {
   validate = false;
  }
  
  private function onEnterFame(eventObj:Event):void {
   if (!validate) {
    draw();
   }
  }
  
  private function draw():void {
   if (bmpData == null) {
    updateBmpData();
   }

   if (bmpData.width < (width + 1) * magniFactor || bmpData.height < (height + 1) * magniFactor) {
    updateBmpData();
   }
   var rect:Rectangle = new Rectangle(_txt_.x, _txt_.y, _txt_.width, _txt_.height);
   bmpData.fillRect(bmpData.rect, 0x00000000);
   var xOffset:Number = -_txt_.x;

   var dev:Number = magniFactor / 3;
   bmpData.draw(_txt_, new Matrix(magniFactor, 0, 0, magniFactor, xOffset * magniFactor + 0, 0));
   bmpData.draw(_txt_, new Matrix(magniFactor, 0, 0, magniFactor, xOffset * magniFactor + dev, 0));
   bmpData.draw(_txt_, new Matrix(magniFactor, 0, 0, magniFactor, xOffset * magniFactor + 0, dev));
   bmpData.applyFilter(bmpData, bmpData.rect, new Point(0, 0), blurFilter);
   bmpData.draw(_txt_, new Matrix(magniFactor, 0, 0, magniFactor, xOffset * magniFactor + dev / 2, dev / 2));

   bmp.x = _txt_.x;
   bmp.y = _txt_.y;
   validate = true;
  }
  
  //
  // Public Method
  //
  public function set validate(value:Boolean):void {
   _validate_ = value;
  }
  public function get validate():Boolean {
   return _validate_;
  }
  
  public function set useBitmapText(value:Boolean):void {
   _useBitmapText_ = value;
   _txt_.visible = !value;
   bmp.visible = value;
  }
  public function get useBitmapText():Boolean {
   return _useBitmapText_;
  }
  //
  // Override Method
  //
  public override function set width(w:Number):void {
   super.width = w;
   validate = false;
  }
  public override function set height(h:Number):void {
   super.height = h;
   validate = false;
  }
  public override function appendText(text:String):void {
   super.appendText(text);
   validate = false;
  }
  public override function replaceSelectedText(value:String):void {
   super.replaceSelectedText(value);
   validate = false;
  }
  public override function replaceText(beginIndex:int, endIndex:int, newText:String):void {
   super.replaceText(beginIndex,endIndex,newText);
   validate = false;
  }
  public override function setTextFormat(format:TextFormat, beginIndex:int = -1, endIndex:int = -1):void {
   super.setTextFormat(format,beginIndex,endIndex);
   validate = false;
  }
  public override function set defaultTextFormat(format:TextFormat):void {
   super.defaultTextFormat = format;
   validate = false;
  }
  
  public override function set antiAliasType(value:String):void {
   super.antiAliasType = value;
   validate = false;
  }
  public override function set autoSize(value:String):void {
   super.autoSize = value;
   validate = false;
  }
  public override function set gridFitType(value:String):void {
   super.gridFitType = value;
   validate = false;
  }
  public override function set htmlText(value:String):void {
   super.htmlText = value;
   validate = false;
  }
  public override function set restrict(value:String):void {
   super.restrict = value;
   validate = false;
  }
  public override function set text(value:String):void {
   super.text = value;
   validate = false;
  }
  
  public override function set backgroundColor(value:uint):void {
   super.backgroundColor = value;
   validate = false;
  }
  public override function set borderColor(value:uint):void {
   super.borderColor = value;
   validate = false;
  }
  public override function set textColor(value:uint):void {
   super.textColor = value;
   validate = false;
  }
  
  public override function set sharpness(value:Number):void {
   super.sharpness = value;
   validate = false;
  }
  public override function set thickness(value:Number):void {
   super.thickness = value;
   validate = false;
  }
  
  public override function set alwaysShowSelection(value:Boolean):void {
   super.alwaysShowSelection = value;
   validate = false;
  }
  public override function set border(value:Boolean):void {
   super.border = value;
   validate = false;
  }
  public override function set condenseWhite(value:Boolean):void {
   super.condenseWhite = value;
   validate = false;
  }
  public override function set displayAsPassword(value:Boolean):void {
   super.displayAsPassword = value;
   validate = false;
  }
  public override function set embedFonts(value:Boolean):void {
   super.embedFonts = value;
   validate = false;
  }
  public override function set multiline(value:Boolean):void {
   super.multiline = value;
   validate = false;
  }
  public override function set selectable(value:Boolean):void {
   super.selectable = value;
   validate = false;
  }
  public override function set wordWrap(value:Boolean):void {
   super.wordWrap = value;
   validate = false;
  }
  
 }
}

測試程式如下:

import com.ticore.text.*;

var bmpTxt:BitmapText = new BitmapText();
this.addChild(bmpTxt);
bmpTxt.border = true;
bmpTxt.multiline = true;
bmpTxt.rotation = 0;
bmpTxt.wordWrap = true;
bmpTxt.rotation = 15;
bmpTxt.width = 320;
bmpTxt.height = 250;
bmpTxt.x = 170;
bmpTxt.y = 30;

var format:TextFormat = bmpTxt.getTextFormat();
format.font = "新細明體";
format.align = "center";

for (var i:Number = 6; i <= 22; ++i) {
 format.size = i;
 bmpTxt.defaultTextFormat = format;
 bmpTxt.appendText("Font Size " + format.size + "  新細明體\n");
}

原始檔案下載

實際執行效果擷圖:

看起來應該還不錯吧!
即使在旋轉狀態下也沒有很嚴重的鋸齒
我特別在繪製點陣圖步驟提升了解析度
並且作了偏移繪製,套用濾鏡
當然這些動作是會消耗效能的
所以只有在必要時才會進行重繪的動作

相關連結:
Flash Player 10 兩種旋轉文字方式比較

轉載請註明出處 http://ticore.blogspot.com/2008/07/as3-bitmaptext.html