[原文链接:?http://guyinthechair.com/2010/06/the-flash-text-engine-part-1/??]
[原文作者:?Paul Taylor? ?原文时间:??Jun. 3,2010 ]
[原创翻译:?http://www.smithfox.com/?e=73?,转载请保留此声明,谢谢]
我将写系列文章来介绍Flash文本引擎( Flash Text Engine,Flash Player 10中新的低层API,以下简称FTE),本文是开篇:?第一部分,?第二部分,?第三部分.??
首先需要澄清,这些系列文章不是写Adobe的文本布局框架(Text Layout Framework,以下简称TLF)的,TLF是一个高级的排版和文字布局框架,TLF是建立在FTE(Flash Text Engine)之上的,FTE是一个低层的Player native API,它在flash.text.engine?package内.
FTE是用来渲染文本的 "文档样式" 的,它的主要目的是取代TextField,而不是提供一个完整的HTML渲染引擎规模的文本布局框架.?
FTE处理一种我称之为流(flow)的东西: 格式化,引起文本在段落内换行,我不知道这是不是官方术语,但似乎也挺适合. 它不处理布局相关的事,比如项目符号,缩进排版,图像环绕,padding等等. 它也不处理装饰之类的东西,比如,?下划线,删除线,背景颜色,选择高亮等. FTE将这些交给外层处理,我相信是因为:
1. 布局比流更为复杂和细致,这些功能不需要在FlashPlayer核心中
2. 装饰不会使文本回流,或是引起文本换行
FTE遵循了一个小的MVC架构,大约有10个核心类,它们提供了大部分的功能,剩下的类是一些常量声明. 需要注意的是,所有的FTE的类都声明为final(译者注: 不可继承) ?:).
FTE Model(MVC之 model)
FTE模型的基础是ContentElement。 ContentElement的是一个抽象基类。你不能直接 new ContentElement()(这有点象DisplayObject),但可以实列化它的三个子类:TextElement、GraphicElement或GroupElement。这些类描述了文本树状层次结构,但在深入之前,我想先多谈一点ContentElement。
ContentElement
先看一下ContentElement的构造函数
ContentElement(elementFormat:ElementFormat = null,eventMirror:EventDispatcher = null,textRotation:String = "rotate0")
它有两个重要的参数,elementFormat和eventMirror(还有一个不太重要的第三个参数,只有当你想旋转文本时才会用到这个参数)。让我们先只看ElementFormat,后面我们再回来看eventMirror。
ElementFormat类描述了处理文本流所需的绝大部分属性。它有一个FontDescription成员,正如其名,在FontDescription中你能取到标准fontFamily、fontWeight、fontPosture(这相当于传统的Flash中的FontStyle),以及字体是如何检索自Flash播放器深度(紧凑字体格式或设备字体)的。
ElementFormat有alpha,color,baselineShift,kerning等属性,基本上都能够影响reflow(回流?)。
好的,上面描述了现在你需要知道的有关ElementFormat和FontDescription对象的内容。接下来让我们看一看你会用到的实现类。
TextElement
在这三个当中,TextElement的是最直接的。它只是接受一个文本字符串:
TextElement(text:String = null,elementFormat:ElementFormat = null,textRotation:String = "rotate0")
传入的的ElementFormat将应用于整个文本字符串。因此,如果你指定了一个红颜色的的ElementFormat,那整个文本字符串会呈现红色。
GraphicElement
GraphicElement接受一个DisplayObject实例(实例!),以及它的宽度和高度:
GraphicElement(graphic:DisplayObject = null,elementWidth:Number = 15.0,elementHeight:Number = 15.0,textRotation:String = "rotate0")
ElementFormat的一些属性也适用于GraphicElement,如alpha、baselineShift等。显然,GraphicElement并不遵守FontDescription和ElementFormat的字体相关设置。
GroupElement
最后是GroupElement:
GroupElement(elements:Vector.<ContentElement> = null,textRotation:String = "rotate0")
GroupElement非常重要,它是TextElements、GraphicElements或其他GroupElements任意组合的集合。 GroupElement为FTE模型提供了树的功能。 TextElement的不能拥有孩子,它只控制一个字符串。同样,GraphicElement也只是描述了一个DisplayObject实例。 而GroupElements则把它绑在一起。
GroupElements提供了一套处理标准树的API函数,你可以提取,分割,合并,组合孩子。以我的经验,你不会经常用这个,除非你正在写一个可编辑的文本字段。如果你正在编写一个可编辑的文本字段,上帝保佑你(开玩笑的,it is hella fun)。
好,说够了模型,下面...
FTE View(MVC之view)
有两个(两个!)类构成了FTE的视图division: TextLine和TextLineMirrorRegion。现在你可以忘掉TextLineMirrorRegion,因为它是处理交互的,这是一个复杂的话题,我将在后面会详细地讨论。因此,现在只专注于TextLine。
TextLine
TextLine是一个DisplayObjectContainer。是的,这就意味着它有get/add/removeChild方法(他们仍然有效!)。TextLine也是在InteractiveObject,你可以侦听所有正常的交互事件。不过,尽管它继承自InteractiveObject,但有几个属性,是只读的。这些都已经详细地写在TextLine官方文档了。
TextLine增加了原子的概念,是指在一个TextLine不可分割的字符。单个字符以及任何图形是原子性的。重要的是要知道原子永远不能被行与行分裂。FTE测量单位最小是原子水平,没有更小。
保持原子信息可能是昂贵的,TextLine只呈现其文本,但它不知道它包含的任何原子,然而调用各种方法将引起TextLine创建原子数据。比如,你调用getAtomIndexAtPoint(),TextLine就会为每个原子创建相关信息,这样它才能计算你所指定的点是哪个原子。一旦你做完,一定要调用flushAtomData(),这样原子数据才会GC.
TextLine有指向上一行和下一行的引用,因为TextLine是一个双向链表! 多方便呀! 当然,如果没有前一个引用或是后一个引用,那自己分别就是第一个或是最后一个.
TextLine还有一个有效状态,用来表示该行从渲染后是否已经改变。有TextLineValidity类表示这个状态。
TextLine绝对不是Sprite. TextLine是DisplayObjectContainer. 这最重要的含义是: TextLine没有graphics上下文. 这意味着你不能调用textLine.graphics.draw. :( 哎!
虽然TextLine是一个具体类,你能直接用它,但你不能通过调用它的构造函数来实例化它. 你需要 ...
FTE Controller(MVC之C)
FTE controller部分可能只有一个类: TextBlock。我说是"可能",因为还有TextJustifier和TabStop类,但它们仅仅影响TextBlock的渲染,不...,嗯。好吧,我说服了我自己把它们也算着控制器类,但只有一点点.
请相信我,很快你也会认为TextLine是唯一的控制器类。
TextBlock的是一个相当标准的工厂模式的实现:TextBlock的主要工作是接受ContentElement作为输入然后输出一些你想要的给定宽度的TextLine。ContentElement-> TextBlock -> TextLines。明白了吗?我也没有。
TextBlock 有一个函数 createTextLine():
createTextLine(previousLine:TextLine = null,width:Number = 1000000,lineOffset:Number = 0.0,fitSomething:Boolean = false):TextLine
你所要做的就是将你刚创建的前一行作为第一个参数传入,再加一个参数表示你想让当前行有多宽,TextBlock就会为你丈量了一个新的TextLine. 你是否看到了双向链表了?
创建第一个TextLine时,你只需要传null作为第一个参数就可以了. 如果TextBlock的content属性有内容了,并且至少有一个原子(字符或是图形),那么传入null总是会返回一个TextLine. 如果已经没有新行需要创建,调用TextBlock.createTextLine() 将会返回 null.
下面的简单例子是一个宽度为200的TextBlock渲染行的过程
var y:Number = 0;
var line:TextLine = block.createTextLine(null,200);
while(line)
{
addChild(line);
y += line.height;
line.height = y;
line = block.createTextLine(line,200);
}
好吧,我已经说了很多了,该来一个例子了.
如果你看不到flash,你就直接到 http://www.smithfox.com/myopensource/fte/SimpleDemo1.swf
下面是这个例子的代码:
package
{
import flash.display.Sprite;
import flash.text.engine.ContentElement;
import flash.text.engine.ElementFormat;
import flash.text.engine.FontDescription;
import flash.text.engine.FontPosture;
import flash.text.engine.FontWeight;
import flash.text.engine.GroupElement;
import flash.text.engine.TextBlock;
import flash.text.engine.TextElement;
import flash.text.engine.TextLine;
[SWF(width="450",height="32")]
public class SimpleDemo1 extends Sprite
{
public function SimpleDemo1()
{
super();
var e1:TextElement = new TextElement('Consider,what makes a text line a ',new ElementFormat(new FontDescription(),24));
var e2:TextElement = new TextElement('text line',new ElementFormat(new FontDescription("_serif",FontWeight.NORMAL,FontPosture.ITALIC),24));
var e3:TextElement = new TextElement('?',24));
var e:Vector. = new Vector.();
e.push(e1,e2,e3);
var block:TextBlock = new TextBlock(new GroupElement(e));
var line:TextLine = block.createTextLine(null,stage.stageWidth);
var _y:Number = 0;
while(line)
{
addChild(line);
_y += line.height;
line.y = _y;
line = block.createTextLine(line,stage.stageWidth);
}
}
}
}
Holy crap Batman!
正如你所看到,它需要三个TextElement和一个GroupElement来完成一个中间有斜体文字和不同字体文字的文本行.
第二部分我将讲解互动相关内容,访问TextBlock,以及全部. 一直到你看到在github上的tinytlf项目. 它应该要有一次重大更新了,不过我很快就会和大家谈到它.