[原文链接: 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, eventMirror:EventDispatcher = null, textRotation:String = "rotate0")
传入的的ElementFormat将应用于整个文本字符串。因此,如果你指定了一个红颜色的的ElementFormat,那整个文本字符串会呈现红色。
GraphicElement
GraphicElement接受一个DisplayObject实例(实例!),以及它的宽度和高度:
GraphicElement(graphic:DisplayObject = null, elementWidth:Number = 15.0, elementHeight:Number = 15.0, elementFormat:ElementFormat = null, eventMirror:EventDispatcher = null, textRotation:String = "rotate0")
ElementFormat的一些属性也适用于GraphicElement,如alpha、baselineShift等。显然,GraphicElement并不遵守FontDescription和ElementFormat的字体相关设置。
GroupElement
最后是GroupElement:
GroupElement(elements:Vector.<ContentElement> = null, elementFormat:ElementFormat = null, eventMirror:EventDispatcher = 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);
}
好吧, 我已经说了很多了, 该来一个例子了.
下面是这个例子的代码:
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('?', new ElementFormat(new FontDescription(), 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项目. 它应该要有一次重大更新了, 不过我很快就会和大家谈到它.