Flash文本引擎, 第三部分: 布局

本文深入介绍了Flash文本引擎(FTE)的文本布局原理和技术细节,包括TextBlock和TextLine的使用,以及如何实现不同样式的文本布局。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

flash fte layout

[原文链接: http://guyinthechair.com/2010/08/the-fte-part-3-textblocks-textlines-and-text-layout/  ]
[原文作者: Paul Taylor  原文时间:  Aug. 9, 2010 ]
[原创翻译: http://www.smithfox.com/?e=184 ,转载请保留此声明, 谢谢]

这是介绍Flash文本引擎系列文章的第三部分: 第一部分, 第二部分, 第三部分.

首先需要澄清, 这些系列文章不是写Adobe的文本布局框架(Text Layout Framework, 以下简称TLF)的, TLF是一个高级的排版和文字布局框架, TLF是建立在FTE(Flash Text Engine)之上的, FTE是一个低层的Player native API, 它在flash.text.engine package内.

Flash Player 10的FTE是一个强大的字体渲染库, 但这也仅是将字符渲染到指定宽度的TextLine内. 想想TextField的全部功能, 你就会意识到, 渲染字形, 只是所有工作的一小部分(当然也很重要).

文本布局

回想第一篇文章, 说道, 主要的 "Controller" 是 TextBlock 类, TextBlock是TextLine的工厂. 你提供 ContentElement 对象给TextBlock, 它就会通过计算然后创建一个你所需要的TextLine对象来显示内容. TextBlock的渲染算法是作用于段落(paragraph)级别的. 每个段落一个TextBlock, 或是每个TextBlock一个段落. 例如在FTE中, 段落应表示为一个单独的TextBlock, 这个TextBlock包含了多个定义格式的ContentElement.

优秀的布局引擎范式

如果你熟悉HTML样式, 你肯定知道HTML有两个不同的布局范式:  block layout and inline formatting. 

Block layout影响整个文本块, padding(填充), indentation(缩进), 和margins(边距)这些样式会影响 block 级的布局. Inline formatting 是在block范围内渲染字符, 比如 color(颜色), size(大小), posture(位置), weight(粗细), justification(对齐)等等.

TextBlock的算法照顾了inline formatting, 因为inline formatting影响到TextLine之间的字符流, 剩下来你可以随意用什么Block格式了, 例如, 当你调用TextBlock的createTextLine方法时, 你可以指定TextLine的宽度. 这个简单的选项能让我们用一些花哨点的数学方法来实现块级的布局的许多特性. 

布局算法

我们想用有点通用的方法来完成各种各样的布局. 一切都从最简单的开始, 先看单段布局, 然后块布局, 再到复杂的多TextBlock和多列(就象报纸那样) 的布局.

我已经演示了最简单的布局方法: 循环渲染所有行, 直到TextBlock.createTextLine返回null为止. 简单直接, 你可以应用你想用的任何块样式.

var y:Number = 0;
var line:TextLine = block.createTextLine(null, 200);
while(line)
{
    addChild(line);
    y += line.ascent;
    line.y = y;
    y += line.descent;
    line = block.createTextLine(line, 200);
}

Source

虽然上面的demo只不过像一个TextField, 但我已经完成了两个任务: 我已经按我的需要用TextBlock创建了每一个TextLine, 我用 y 变量作计数完成了对TextLine的最原始的布局.

缩进:

缩进非常简单, 将第一行的width变小的一点, 变小的尺寸用x右移作为补偿.

var y:Number = 0;
var line:TextLine = block.createTextLine(null, 185);
line.x = 15;
while(line)
{
    addChild(line);
    y += line.ascent;
    line.y = y;
    y += line.descent;
    line = block.createTextLine(line, 200);
}

Demo:

Source

对齐:

居中对齐:

var y:Number = 0;
var line:TextLine = block.createTextLine(null, 200);
while(line)
{
    addChild(line);
    y += line.ascent;
    line.y = y;
    line.x = (200 - line.width) * 0.5;
    y += line.descent;
    line = block.createTextLine(line, 200);
}


Source

右对齐是一样的, 只是无需要乘以0.5了

Source

看到没? 标准的布局实战! 最终, 我们用最常见数学实现了组件的布局. 你可能会说: "嗯! 缩进和对齐太简单了, 它们只需要计算x就行了". 没错!就很简单, 要知道其它的一些块格式, 比如 padding(填充), margin(边距), 和line spacing(行间距) 等等, 也都只是计算x,y 并且根据条件在循环内应用.

多TextBlock的布局

OK, 我们已经知道怎么在单个TextBlock内布局了, 重用上面的代码, 做多个TextBlock的布局就是小菜.



var blocks:Vector.<TextBlock> = new <TextBlock>[block1, block2];
var y:Number = 0;
for(var i:int = 0; i < blocks.length; ++i)
{
    y = layoutBlock(blocks[i], y);
    y += 5;
}
// Returns the aggregate y after this layout operation
function layoutBlock(block:TextBlock, y:Number):Number
{
    var line:TextLine = block.createTextLine(null, 185);
    line.x = 15;
    while(line)
    {
        addChild(line);
        y += line.ascent;
        line.y = y;
        y += line.descent;
        line = block.createTextLine(line, 200);
    }
    return y;
}


Source

多容器布局

啊! 让我们进入到好东东, 多容器布局真的很cool, 因为它可以让文本从一个DisplayObjectContainer overflow到另一个DisplayObjectContainer. 还可以让我们实现列的布局.

总的想法是让尽可能多的TextLine放入到DisplayObjectContainer (DOC). 当碰到边界后, 就切换到下一个DOC. 我们只需将之前的方法略做修改就可以达到这个效果. 布局方法需要返回适合在DOC内的最后一个TextLine, 这样, 我们就可以重新进入布局, 并且是接着停止的那个TextBlock开始.



var line:TextLine = layoutBlock(block, null, container1);
if(line)
    line = layoutBlock(block, line, container2);
 
// Returns the last line rendered out of the TextBlock
function layoutBlock(block:TextBlock, previousLine:TextLine, 
                             container:DisplayObjectContainer):TextLine
{
    var line:TextLine = block.createTextLine(previousLine, container.width);
    var y:Number = 0;
    while(line)
    {
        container.addChild(line);
        y += line.ascent;
        line.y = y;
        y += line.descent;
        //If we reached the height boundary, return the last line that fit.
        if(y + line.height > container.height)
            return line;
        line = block.createTextLine(line, container.width);
    }
    return line;
}


Source

多文本块布局和多容器布局

这将是非常cool的事情, 我准备将上面两个方法合并起来.

为解决这个问题, 先让我们列一下问题:

1. 我们有一个TextBlock的列表
2. 我们有一个DisplayObjectContainer列表, 要将所有的TextBlock装入
3. 我们希望一个 DisplayObjectContainer装尽可能多的行
4. 当一个 DisplayObjectContainer 满了, 就到下一个DisplayObjectContainer , 并且接着断掉地方开始
5. 我们需要记录最后一个TextLine, 以便知道是哪个断掉了

前面的经验: 当没有line时TextBlock会返回null, 当Container满的时候, 会返回在Container的最后的line, 因此, 逻辑如下:

1. 循环每个TextBlock
2. 尽可能将更多的TextLine放入TextBlock, 并返回最后一个TextLine
    如果TextLine是null, 那意味着TextBlock已经没有TextLine, 并且DOC还有空间, 那就保持DOC不变, 移到下一个TextBlock.
    如果TextLine不是null,  那意味TextBlock还有TextLine, 但是DCO已经没有空间, 那就保持TextBlock不变, 移动下一个DCO.
3. 如果任何一个list结束(TextBlock list, 和DCO list), 就返回

代码如下:

var blocks:Vector.<TextBlock> = new <TextBlock>[block1, block2, block3, block4];
var containers:Vector.<DisplayObjectContainer> = 
    new <DisplayObjectContainer>[container1, container2, container3];
 
layout(blocks, containers);
 
function layout(blocks:Vector.<TextBlock>, containers:Vector.<DisplayObjectContainer>):void
{
    var blockIndex:int = 0;
    var containerIndex:int = 0;
 
    var block:TextBlock;
    var container:DisplayObjectContainer;
 
    var line:TextLine;
    while(blockIndex < blocks.length)
    {
        block = blocks[blockIndex];
        container = containers[containerIndex];
 
        line = layoutInContainer(container, block, line);
 
        if(line && ++containerIndex < containers.length)
        {
            container = containers[containerIndex];
            containerY = 0;
        }
        else if(++blockIndex < blocks.length)
            block = blocks[blockIndex];
        else
            return;
    }
}
 
var containerY:Number = 0;
 
function layoutInContainer(container:DisplayObjectContainer, 
                                   block:TextBlock, previousLine:TextLine):TextLine
{
    var line:TextLine = createTextLine(block, previousLine);
    while(line)
    {
        container.addChild(line);
        containerY += line.ascent;
        line.y = containerY;
        containerY += line.descent;
 
        if(containerY + line.height > container.height)
            return line;
 
        line = createTextLine(block, line);
    }
 
    //This will be null.
    return line;
}
 
function createTextLine(block:TextBlock, previousLine:TextLine):TextLine
{
    var w:Number = 190;
    var x:Number = 0;
    //Apply indention properties here.
    if(previousLine == null)
    {
        w -= 15;
        x += 15;
    }
    var line:TextLine = block.createTextLine(previousLine, w, 0.0, true);
    if(line)
        line.x = x;
    return line;
}


Source
Text Source

哇哦! 休息一下

我知道, 有太多内容需要好好消化. 但你现在已经知道TextLayout不是什么黑魔法巫术, 它完全是用FTE来实现的. 如果你有什么问题, 可以发表评论或是发邮件给我, 我非常乐意看到任何文本布局的技术问题, 或是评论. 我的这些demo还是不系统, 我已经构建出很多更复杂, 并且已经调优过的布局, 放到了tinytlf, 这是一个我已经秘密进行了几个月的小型文本布局框架.

旁白: 文本布局 vs 组件布局

典型的组件布局引擎(比如Flex), child的创建是和布局分开的. 通常是所有的child先加入到displaylist中, 然后在某个时候进行布局.  不会因为child的大小和位置而去创建或是销毁child, 布局也不会影响未来的child创建(这里假设不讨论 virtualized layout, 因为那是一个特例). 

块级的布局属性(如padding, 缩进等) 决定了每个TextLine是如何创建和布局. 反过来又影响TextBlock怎么渲染下一个TextLine, 等等, 等等...

我一直无法将块级布局属性从TextLine的创建过程中分开. 这在实际情况在还不是那么糟糕, 但有时会让我走些弯路, 我觉得应该有一个更好的办法, 只是我还没有找到它.

基于数据挖掘的音乐推荐系统设计与实现 需要一个代码说明,不需要论文 采用python语言,django框架,mysql数据库开发 编程环境:pycharm,mysql8.0 系统分为前台+后台模式开发 网站前台: 用户注册, 登录 搜索音乐,音乐欣赏(可以在线进行播放) 用户登陆时选择相关感兴趣的音乐风格 音乐收藏 音乐推荐算法:(重点) 本课题需要大量用户行为(如播放记录、收藏列表)、音乐特征(如音频特征、歌曲元数据)等数据 (1)根据用户之间相似性或关联性,给一个用户推荐与其相似或有关联的其他用户所感兴趣的音乐; (2)根据音乐之间的相似性或关联性,给一个用户推荐与其感兴趣的音乐相似或有关联的其他音乐。 基于用户的推荐和基于物品的推荐 其中基于用户的推荐是基于用户的相似度找出相似相似用户,然后向目标用户推荐其相似用户喜欢的东西(和你类似的人也喜欢**东西); 而基于物品的推荐是基于物品的相似度找出相似的物品做推荐(喜欢该音乐的人还喜欢了**音乐); 管理员 管理员信息管理 注册用户管理,审核 音乐爬虫(爬虫方式爬取网站音乐数据) 音乐信息管理(上传歌曲MP3,以便前台播放) 音乐收藏管理 用户 用户资料修改 我的音乐收藏 完整前后端源码,部署后可正常运行! 环境说明 开发语言:python后端 python版本:3.7 数据库:mysql 5.7+ 数据库工具:Navicat11+ 开发软件:pycharm
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值