I’ve written a little feature explorer app to showcase some of tinytlf’s latest features. You can play around with it below, or you can launch the demo app into a new window.
You can manipulate the renderer by editing CSS or switching which HTML document is rendered. I threw in a circular paragraph renderer just for fun. I also included a time indicator to show how long Flash takes to parse each HTML source. Even though Flash takes forever to serialize Strings to XML, this shows that tinytlf renders it almost instantaneously every time. I’ll talk about this more in my next post.
This is now the official tinytlf demo, so I’ll be updating it as I add features in the future. To demonstrate tinytlf’s capabilities beyond sanitized HTML input, I’ve included examples from Idle Words and the HTML version of Christian Cantrell’s eBook, Farmer One.
This entry was posted
on Friday, October 21st, 2011 at 1:33 am and is filed under actionscript, community.
You can follow any responses to this entry through the RSS 2.0 feed.
You can leave a response, or trackback from your own site.
by Paul Taylor,
Sep. 13, 2011, under [
actionscript
]
I recently embarked on the journey towards tinytlf v2.0, a shiny new version replete with glossy sheen, twinkling diamond star, and cash-machine *jing* sound effect.
First, I gotta apologize for pushing v1 out into the world. It was crap. I’ve changed almost everything in v2, making it smaller, faster, and more feature rich. Crazy you say? Here’s a few of the additions and changes that make version 2 smarter than version 1:
Alongside the renderer, adding a parser for content blocks, which is a better abstraction for implementing percent sizing, margins, padding, lists, tables, and floats than the way I did it in v1
A rewrite of the interaction model, implementing gestures using raix, a port of Microsoft’s incredible Rx.NET framework. (events are fun again!)
Because of the addition of raix, I can do away with the Flash Text Engine’s EventMirrors. I’ve implemented a (faster, smaller) replacement. You can see this action in AnchorMirror.as
Dependency Injection
Instead of wiring everything up through the combination of a Facade and maps, I’ve switched to SwiftSuspenders for Dependency Injection. Using a formal IoC framework makes tinytlf infinitely more extensible, rids the framework of ceremony for the sake of it, and gives us sweet [Inject] syntax to boot. And SwiftSuspenders is only 15K; fucking awesome.
Maps maps maps
I’ve written a generic mapping class, implemented a few interfaces for the sake of IoC mappings (and to reduce map collisions), and stuck them all in the IoC container. This reduces file size and cyclomatic complexity, as each map implementation is the same. That’s not to say you can’t extend it; feel free to extend the map and replace it in the IoC container if required.
HTML and CSS
The only way anybody is going to work with text is with markup and CSS. So I wrote a small CSS parser to inject and lookup styles, and the model is a DOM built from XML.
New Rendering Algorithm
I completely rewrote the render and layout algorithms. The new algorithm is referentially transparent, with zero side effects (aside from a bit of TextLine caching for performance’ sake).
What does that mean to you? There should be no weird bugs or race conditions caused by variables you can’t replicate in testing.
The previous rendering algorithm was shit. It’s the primary reason I never implemented editing. Virtualization was hacked in. It suffered from race conditions and required property synchronization, due to an architecture that maintained state and only worked if started in the correct configuration.
I’ve rewritten the layout algorithm from the ground up, solved all these problems, and exposed more points of extension.
If you want to see the speed of the new layout algorithm, scroll through this as fast as you can (looks cool with OS X Lion’s inertial scrolling). I already know of many places it should be improved, but it’s a hell of a lot better than what it was!
New interactions via Reactive eXtensions
Reactive Programming is already a wave breaking over the .NET (and maybe JS?) world, but unfortunately ActionScript developers have been largely left behind by this revolution in interactive programming, and have suffered for it.
I wrote a class called Observables which creates and manages IObservable streams for various interaction
events, some of which (like drag, doubleDown, doubleDrag, tripleDown, and tripleDrag) are compositions of event streams.
Observables.as also allows you to register IEventDispatchers to be included in master IObservable streams. A developer can subscribe to the master streams if he’s interested in receiving events from all IEventDispatchers registered with Observables.
HTML Demo
For my demos, I’ve chosen to work with the unaltered HTML from a few posts on idlewords.com, specifically Why Arabic is Terrific, and the July 2010 index page. The text has many features I wish to support: malformed XHTML, CSS, inline styles, tables, lists, anchors, bidirectional text, and floats. Here’s the demo source, HTML, and CSS. Click here to open the SWF in a fullscreen window.
Main.swf
Where does that leave things?
I wrote this post to show people that the framework is back in active development. There are already many places I know where performance can be improved. I don’t have a TextField component yet, decorations aren’t done, and most HTML tags (and their layout configurations) haven’t been implemented. Editing is back on the roadmap, but not done either.
Like always, feel free to email me with questions or feature requests, and I’m always monitoring github for forks and pull requests. Happy coding!
This entry was posted
on Tuesday, September 13th, 2011 at 1:32 pm and is filed under actionscript.
You can follow any responses to this entry through the RSS 2.0 feed.
You can leave a response, or trackback from your own site.
If you talked to me at a conference sometime in the last nine months, chances are I ranted loudly about type-variant auto-mediation. At the time, I meant an implementation I wrote in PureMVC because I assumed robotlegs already supported covariant auto-mediation. It doesn’t. Explanation to follow.
Flash and the City
Before I begin, this was the topic of my Flash and the City talk this year. You can find the slides here if you want.
Auto Mediation
Robotlegs has an interface called IMediatorMap, which maps UIComponent types to Mediator types. Robotlegs watches the display list, and when instances of any mapped components are added to (or removed from) the stage, it will create (or destroy) an instance of the mapped Mediator for it.
mediatorMap.mapView(MyViewType, MyViewMediator);
Each time you create and add a new instance of MyViewType to the stage, a corresponding MyViewMediator instance will be created.
Problems
The problem with MediatorMap is two-fold:
It enforces an arbitrary restriction that each View instance can only have one Mediator instance. This is dangerous, because it blurs the line between view logic and business logic. Does this functionality belong in the UIComponent or the Mediator? Developers end up writing Views and Mediators with poor separation of concerns, and inevitably overflow View logic into their Mediators. Mediators aren’t seen as reusable bits of business logic, they’re seen as an extension to the UI component.
MediatorMap is a type invariant mapping. The map creates a Mediator only if the view component is an exact instance of a mapped type.
For example, if you register the MyViewComponentMediator type for MyViewComponent, subclasses of MyViewComponent (say, MyViewComponentSubclass) don’t get an instance of MyViewComponentMediator automatically created for them.
If MyViewComponent encapsulates enough useful functionality to supplement it with its own Mediator, doesn’t MyViewComponentSubclass still implement (through inheritance) and require the same functionality from the MediatorMap? Sure, the subclass may override certain functions, but that’s just polymorphism at work.
Covariant Auto Mediation
The idea behind covariant mediation is that a Mediator is created if a component extends or implements a mapped view component type.
Here’s the practical difference between an invariant check and a covariant check:
//Invariant check:if(myViewComponent.constructor == MyViewComponent){//Do something}//Covariant check:if(myViewComponent is MyViewComponent){//Do something else}
If the instance of myViewComponent is actually a subclass of the MyViewComponent type, the first check will fail, but the second check will pass.
Extends or Implements
Extending classes for functionality is all well and good, but it’s not as flexible or as clean as implementing an interface. For starters, AS3 allows you only one parent class, so you can’t compose much functionality through inheritance before running into the diamond problem.
Before I go any further, yes, this means you can map a Mediator for all instances or subclasses of Sprite or UIComponent.
// An instance of SpriteMediator will be created each time an instance// or subclass of Sprite is added to the display list. Crazy, huh?map.mapMediator(Sprite, SpriteMediator);
Naturally this introduces performance problems, so everything from the “flash.*” and “mx.*” packages are filtered out by default.
Anyway, covariantly auto-mediating base classes is alright I guess, but who wants to maintain a complex inheritance tree? Not me. The real sexy magic happens when you start mapping Interfaces for auto-mediation.
Interfaces are covariantly checked:
trace(myComponent.constructor == IMyInterface);//will always be falsetrace(myComponent is IMyInterface);//can evaluate to true
Demo Time
This post has been highly theoretical so far, and if you let me continue extolling the virtues of designing interfaces to apply behaviors to components via covariant mediation it will only devolve into something more abstract and academic.
So instead, I’ll show off some code to do it for me. Here’s the demo app I created for my FATC talk.
This demo has three progressively more impressive examples of common problems solved in very few LOC using covariant mediation:
An example of handling system-wide error events.
An example of managing selected screen state.
An example DataGrid where each cell is representative of a unique and open streaming channel from the server.
Rundown
I’ll run through just the meaty parts of this demo. The source for the whole thing is here.
App.mxml and IoC Mappings
Here’s the important part of the Application MXML file:
override protectedfunction createChildren():void{// Normally these IoC mappings are done inside the Context.// Do them here because we have access to our children.var context:FATC_Context = new FATC_Context(systemManager asDisplayObjectContainer);varmap:IVariantMediatorMap = context.variantMap;// Map implementations of ISystemErrorUI to have// SystemErrorUIMediators registered for them automaticallymap.mapMediator(ISystemErrorUI, SystemErrorUIMediator);// Map implementations of IScreen to have ScreenMediators// registered for them automaticallymap.mapMediator(IScreen, ScreenMediator);// Map implementations of IStreamingServiceUI to have// StreamingServiceUIMediator registered for them automaticallymap.mapMediator(IStreamingServiceUI, StreamingServiceUIMediator);var injector:IInjector = context.theInjector;// Tell the injector to create and inject a new instance of// StreamingService each time it sees a dependency on IStreamingService
injector.mapClass(IStreamingService, StreamingService);super.createChildren();// Register the mediators for the Screens class immediately.// (normally the registration is deferred to the next frame).map.registerMediators(screens);}
public interface ISystemErrorUI
{function handleError(error:SystemErrorEvent):void;functionget errorAcknowledged():ISignal;}
Components that are interested in being notified when errors occur can implement the ISystemErrorUI interface. Whenever an error happens in the Application, it’s the responsibility of the errored operation to dispatch a SystemErrorEvent, either bubbling on the display list, or on robotlegs’ shared eventBus. Each instance of SystemErrorUIMediator will notify its ISystemErrorUI instance.
public interface IScreen
{functionset selectedScreen(screen:IScreen):void;functionget screenChangedSignal():ScreenChangedSignal;}
Implementations of IScreen will be notified when the selectedScreen changes. Implementations of IScreen can cause the screen to change by dispatching their screenChangedSignal member.
Whether the implementor of IScreen truly wishes to change the selectedScreen, or whether it’s simply interested when the selectedScreen is changed, the ScreenMediator doesn’t care. In the demo, three of the four implementations of IScreen respond uniquely when their selectedScreen setter is called:
The Screens class is a ViewStack. When its selectedScreen setter is called, it checks first whether the IScreen instance is a child of itself. If not, Screens adds it as a child. Screens ultimately changes its selectedChild property to the new IScreen.
The ErrorNotificationView class is the bar at the top that drops down when an error occurs. If the bar is showing and the selectedScreen setter is called, the bar hides itself.
The StreamingServiceItemRenderer class is the item renderer for the DataGrid. When the selectedScreen changes to and from the GridScreen instance, StreamingServiceItemRenderer starts or stops the incoming data stream. In this way, we can shut down any data sources for screens that aren’t currently visible.
public interface IStreamingServiceUI
{function setStreamData(value:StreamData):void;functionget updateStreamInfo():ChangeStreamInfoSignal;}
There’s only one implementation of IStreamingServiceUI: StreamingServiceItemRenderer. StreamingServiceItemRenderer is the item renderer for each cell in the DataGrid.
StreamingServiceItemRenderer takes advantage of the fact that the DataGrid reuses its itemRenderers. When the user scrolls, the DataGrid sets in a new value for data. When that happens, the itemRenderer dispatches its updateStreamInfo member Signal.
When the UI dispatches its updateStreamInfo Signal, StreamingServiceUIMediator tells the service instance the new channel name. Then, the service closes its current channel, and opens a stream with the new channel name.
Because the DataGrid reuses its itemRenderers, and the itemRenderers dispatch changes in its data values to its Mediator, the Mediator can reuse its service instance, which manages opening and closing streams.
This means we only keep open connections for visible cells.
by Paul Taylor,
Jun. 3, 2011, under [
actionscript
]
This should be a relatively short post, since it’s late and I’ve been drinking. I’m going to focus on just one property from ElementFormat: breakOpportunity.
BreakOpportunity is subtler. It gives you control over line breaks without inserting control characters into your content.
Documentation
Here’s the documentation of breakOpportunity. I’m pasting it here to illustrate that this doesn’t tell the whole story.
String value
Description
BreakOpportunity.ALL
All characters in the range are treated as line break opportunities, meaning that a line break will occur after each character. Useful for creating effects like text on a path.
Subclass
Effect of setting property
GraphicElement
Has no effect.
GroupElement
Determines the break opportunity between adjacent text elements in the group. If the elementFormat of the group is null, the format of the first of the adjacent elements is used.
TextElement
Determines the break opportunity between the characters in the text element.
Standard Line Breaking
I’ve posted this before, but here’s a refresher. Breaking lines in the Flash Text Engine couldn’t be easier:
varcontent:ContentElement = new TextElement("Some example text to be broken into TextLine objects by a TextBlock instance.",new ElementFormat());var tb:TextBlock = new TextBlock(content);var line:TextLine = tb.createTextLine(null,200);vary:Number = 0;while(line != null){addChild(line);y+= line.ascent;
line.y = y;y+= line.descent;
line = tb.createTextLine(line,200);}
Tricks
But what happens if we modify the breakOpportunity of the ElementFormat?
var ef:ElementFormat = new ElementFormat();
ef.breakOpportunity = BreakOpportunity.ALL;varcontent:ContentElement = new TextElement("Some example text to be broken into TextLine objects by a TextBlock instance.", ef);var tb:TextBlock = new TextBlock(content);var line:TextLine = tb.createTextLine(null,200);vary:Number = 0;while(line != null){addChild(line);y+= line.ascent;
line.y = y;y+= line.descent;
line = tb.createTextLine(line,200);}
As you can see, when the ElementFormat for a TextElement has its breakOpportunity set to BreakOpportunity.ALL, the TextBlock breaks a new TextLine instance after each character. Crazy right?
When I figured this out about 9 months ago, my next thought was to test how GroupElement responds to BreakOpportunity:
var ef:ElementFormat = new ElementFormat();
ef.breakOpportunity = BreakOpportunity.ALL;var group:GroupElement = new GroupElement(new<ContentElement>[new TextElement("Some example text ",new ElementFormat()),new TextElement("to be broken into ",new ElementFormat()),new TextElement("TextLine objects by ",new ElementFormat()),new TextElement("a TextBlock instance.",new ElementFormat()),], ef);var tb:TextBlock = new TextBlock(group);var line:TextLine = tb.createTextLine(null,200);vary:Number = 0;while(line != null){addChild(line);y+= line.ascent;
line.y = y;y+= line.descent;
line = tb.createTextLine(line,200);}
Sweet. So it does what the documentation says it’ll do, break between adjacent TextElements. But is it only TextElements? What about adjacent GraphicElements?
var ef:ElementFormat = new ElementFormat();
ef.breakOpportunity = BreakOpportunity.ALL;var group:GroupElement = new GroupElement(new<ContentElement>[new TextElement("Some example text ",new ElementFormat()),new GraphicElement(new GraphicRect(),20,20,new ElementFormat()),new TextElement("with GraphicElements ",new ElementFormat()),new GraphicElement(new GraphicRect(),20,20,new ElementFormat()),new TextElement("to be broken into ",new ElementFormat()),new TextElement("TextLine objects by ",new ElementFormat()),new TextElement("a TextBlock instance.",new ElementFormat()),], ef);var tb:TextBlock = new TextBlock(group);var line:TextLine = tb.createTextLine(null,200);vary:Number = 0;while(line != null){addChild(line);y+= line.totalHeight;
line.y = y;
line = tb.createTextLine(line,200);}
Ah ha! So it doesn’t only work for TextElements, it works for any siblings enclosed in a GroupElement. Good to know. Can you imagine the implications of this? (hint, this is important for rendering floats using the FTE!)
This entry was posted
on Thursday, June 2nd, 2011 at 11:48 pm and is filed under actionscript.
You can follow any responses to this entry through the RSS 2.0 feed.
You can leave a response, or trackback from your own site.
I don’t always work with the Flex Framework, so over and over I’ve written the following:
package my.project.package.utils
{publicclass Type
{private static const cache:Dictionary = newDictionary(false);public static function describe(value:Object):XML{};}}
The describe function caches the results of a call to describeType() based on the type of Class passed in as the Value. But it’s super ugly! All over the place I’ve had to reference a static class: “Type.describe(myObject);” lame.
Why can’t the results of describeType() just be cached in the player? Maybe it is now and I just don’t know it? If so, please tell me!
So here’s the solution if you’d rather have a global function than a static method call:
An internal class! Yipee! Static, but internalized. Until now I didn’t think you could store a static stateful reference for global functions to use.
Now you can go around all over the place calling “reflect(myObject);”, enjoying the convenience of describeType() and the benefits of using a static method.
This entry was posted
on Tuesday, February 8th, 2011 at 7:01 pm and is filed under actionscript, community.
You can follow any responses to this entry through the RSS 2.0 feed.
You can leave a response, or trackback from your own site.
Found this comment from a commit in November 2010. Had to repost it here. In case anybody else does this too, know you’re not alone. At least the code this describes passes the unit tests.
////
// Everything after this point is basically magic. It was written during
// one coffee fueled weekend, and to be honest, I don’t remember much of
// it. That night is just a blur in my memory. Modify at your own peril.
//
// Here be dragons.
// (I’ve always felt that any serious library needs this comment at
// least once).
////
This entry was posted
on Sunday, January 23rd, 2011 at 5:36 pm and is filed under actionscript, community.
You can follow any responses to this entry through the RSS 2.0 feed.
You can leave a response, or trackback from your own site.
Sorry for the dearth of tinytlf posts lately, I’ve been head-down coding tons of new stuff. If you don’t believe me, check out the git repository history.
I’m working on an official tinytlf demo explorer app, ala minimalcomps but it’s going to let you do lots of cool stuff. In the meantime, I’ll post a few choice demos to this post, kind of show off what I’ve been working on.
Mobile TextField
First up, huge updates to gestures, behaviors, and decorations. So much that I’ve been able to come up with this demo, primarily meant for Android devices, but still usable on the desktop. Highlight in this TextField:
Better HTML support
Here’s some awesome HTML demos that Tanya Gray has put together…
and… maybe Divs and Tables?
Advanced Constraint Layouts
Floats aren’t just for HTML. Floats take advantage of a more generalized layout algorithm, which flows text around constraints. Floats are just a simple (Rectangle) constraint. You can implement the ITextConstraint interface with your own constraint implementation.
This entry was posted
on Monday, November 15th, 2010 at 11:50 am and is filed under actionscript, community.
You can follow any responses to this entry through the RSS 2.0 feed.
You can leave a response, or trackback from your own site.
This is part four in an ongoing series about the Flash Text Engine. You can see the previous entries here.
You should know, this series isn’t about Adobe’s Text Layout Framework, which is an advanced typography and text layout framework. The Flash Text Engine is the low-level API that TLF is built on. In Flash Player 10, the FTE resides in the flash.text.engine package.
Advanced Text Rendering
The FTE may be low level, but that affords developers like you and me serious control over the presentation of our text fields. The FTE renders glyphs into a series of TextLines, but forces us to handle the sizing and layout of the lines. Today, I’ll cover how to efficiently create and position lines. For simplicity’s sake, I’ll assume the TextLines’ widths extend to the edges of the Container they are rendered into.
In this post I’ll cover:
text flow across containers
container resizing
TextLine positioning, invalidation, and reuse
Caveats
In order to keep this post relatively short, I’ve simplified the algorithms here down to the basic principles. This is a general description of the layout algorithms in tinytlf, though they are getting more complex by the day.
Example
Here’s the final product of what I’m going to walk through. Click on the text to resize the columns:
Defining the Problem
From a layout perspective, we have a number of paragraphs that need to be rendered into a list of DisplayObjectContainers. When we run out of DisplayObjectContainers, we’re going to call it quits. What if we run out of lines before we’re done rendering containers? Good! It’s easy to quit when you don’t have any content left to render.
So what we need is a layout algorithm that will break TextLines from a Vector of TextBlocks across a list of DisplayObjectContainers (DOCs).
Text Layout
In order to coordinate the layout between paragraphs and containers, I’ve defined a controller I call TextLayout. TextLayout’s job is to run through the list of TextBlocks, rendering as many lines as possible into the available DOCs. When the DOC is full, TextLayout moves to the next container. If there is no next container, TextLayout breaks out of the rendering algorithm. Similarly with TextBlocks, if we run out paragraphs to render, TextLayout breaks out of the layout algorithm.
/**
* Renders as many lines from the list of TextBlocks into the specified
* conatiners as possible.
*/publicfunctionrender(blocks:Vector.<TextBlock>):void{if(!containers ||!containers.length||!blocks ||!blocks.length)return;
containers.forEach(function(c:TextContainer,...args):void{c.preLayout();});var block:TextBlock = blocks[0];var i:int = 0;var container:TextContainer = containers[0];while(block && container){
container = renderBlockAcrossContainers(block, container);
block = ++i < blocks.length? blocks[i]:null;}}
TextContainer
TextContainer is a single DOC that renders TextLines. It is a layout controller that determines the positions and sizes of the TextLines inside himself.
TextContainer maintains a very important relationship with TextLayout’s rendering algorithm. TextLayout repeatedly calls the TextContainer.layout() method, passing in a TextBlock to render lines from, and optionally, the previous line that was rendered from the block. It is TextContainer’s responsibility to render as many lines from the TextBlock into itself until either, 1. the TextBlock has no lines left to render, or 2. the TextContainer is full and has no more room for additional TextLines.
TextContainer.layout() should return the last line rendered from the TextBlock. Null is a valid value to return. If a (non-null) TextLine was returned, TextLayout assumes the TextContainer is full, finished rendering lines, and moves to the next TextContainer, keeping the same TextBlock. If TextContainer returns null, TextLayout assumes there’s still room in the TextContainer for lines, and TextLayout passes the next TextBlock to the TextContainer.
publicfunction layout(block:TextBlock, previousLine:TextLine):TextLine
{
_y = measuredHeight;var line:TextLine = createTextLine(block, previousLine);while(line){addChild(line);
lines.push(line);position(line);//If there's no room, return the last line broken.if(checkConstraints(line)){return line;}
line = createTextLine(block, line);}//This will be null here.return line;}
Line layout is straightforward. I’m sure you know what the position() and checkConstraints() methods do, and if not, the source is available here.
This is good enough to work for the first layout pass, but resizing introduces a bit more complexity.
TextLineValidity
We can take advantage of an FTE TextBlock and TextLine feature to get resizing.
When the ContentElement “model” is updated, the TextLine “views” should updated to reflect the changes. But we as FTE users have no concept of the data behind each individual TextLines, so if we updated the screen, we’d have to start from the first line and re-create each one. This is extremely inefficient, especially if the change only reflected in one actual line update.
Luckily, the TextBlock can track and resolve such changes for you. Whenever anything in the TextBlock’s content member changes, the TextBlock marks relevant lines “invalid” (TextLineValidity.INVALID). The TextBlock exposes a pretty handy property called firstInvalidLine, which points to the first line in the TextBlock that needs updating. Look mom, no searching!
You can check the status of individual lines simply by reading the TextLine.validity property. Luckily, validity is also writeable, and the TextBlock respects our decision if we choose to mark certain lines “invalid” ourselves. I guess we know best!
Resizing
When a TextContainer is resized, we need to know about it and invalidate our TextLine children.
During the first layout pass, we use the TextBlock.createTextLine method exclusively. But TextLines are expensive to create, so Flash Player 10.1 introduced the TextBlock.recreateTextLine method. Allowing us to recreate TextLines means that the Flash Player can re-jigger the TextLine’s contents internally, without the overhead of creating a new TextLine instance.
During a resize operation, lines will be either created or destroyed. If you set the width smaller, the TextField will be forced to render new TextLines. If you set the width wider, the TextField can remove TextLines at the end. In the first case, we’ll have to make extra calls to TextBlock.createTextLine. In the second case, we can remove the TextLines from the display list and remove all references. In this case, the lines have been orphaned.
But with the introduction of recreateTextLine, it’s optimal to cache orphaned lines, at least for a little while. It’s possible that we will resize the TextField smaller again, which will require the creation of new lines. But if we’ve previously created lines, why not reuse them instead of creating new ones? Good thinking you.
However, this changes the meaning of the line argument in the TextContainer.layout method. Now, the line can either be:
A valid and successfully broken TextLine, which should be used as the previousLine argument to create or re-create a TextLine.
An invalid TextLine that needs to be re-created. This should only happen in the case that the line is the TextBlock.firstLine value, because there is no previously broken valid TextLine. Since we don’t want to wipe out our orphan cache looking for a line, we can detect this case and recreate the line.
This introduces just a bit more complexity, but luckily we can encapsulate it in the TextContainer.createTextLine method.
/**
* Creates or recreates a given TextLine.
*/privatefunction createTextLine(block:TextBlock, line:TextLine):TextLine
{
removeOrphanedLines();if(line){if(line.validity === TextLineValidity.INVALID){return block.recreateTextLine(line,null,width,0.0,true);}elseif(orphans.length){var orphan:TextLine = getFirstOrphan(line);if(orphan){return block.recreateTextLine(orphan, line,width,0.0,true);}}}return block.createTextLine(line,width,0.0,true);}privatefunction removeOrphanedLines():void{var line:TextLine;var n:int = lines.length;for(var i:int = 0; i < n;++i){
line = lines[i];if(line.validity === TextLineValidity.VALID)continue;if(contains(line))removeChild(line);
lines.splice(i,1);
orphans.push(line);
n = lines.length;}}//Static so it's shared between instances of TextContainerprivate static const orphans:Vector.<TextLine> = new<TextLine>[];/**
* Returns the first invalid orphan that also isn't the input line.
*/private static function getFirstOrphan(exceptForMe:TextLine):TextLine
{if(orphans.length == 0)returnnull;var orphan:TextLine = orphans.pop();while(orphan == exceptForMe)
orphan = orphans.pop();while(orphan && orphan.validity == TextLineValidity.VALID)
orphan = orphans.pop();return orphan;}
Because we can possibly recreate TextLines without calling getFirstOrphan, sometimes a TextLine in the orphan list is recreated but not removed from the list of possible orphans. The two checks in this method ensure that we don’t hit this case.
And there you have it! That’s the basic gist of TextLine rendering, resizing, and reuse across multiple DisplayObjectContainers. You can see all the source for the demo here, and be sure to check out tinytlf, the small text layout framework I’m writing. I described the basic line rendering algorithm in tinytlf (with enhancements of course). Though I’m currently working on a more iterative version. If I’m successful, it’ll be the topic of a future blog post. Cheers!
This entry was posted
on Sunday, October 10th, 2010 at 5:07 am and is filed under actionscript, misc.
You can follow any responses to this entry through the RSS 2.0 feed.
You can leave a response, or trackback from your own site.
Expounding on last week’s image flow algorithm, I present to you a generalized algorithm for text flow around inline graphics. You can see it here: ImageFlowContainer, and fork the repo here: tinytlf.
This algorithm only works for left-aligned paragraphs, if you try it with any other alignment I can’t guarantee it’ll look good. These images don’t respect float, they’re just placed at fortuitous positions in the content. They respect box-model padding properties (padding-left, etc.). Also, I changed the default selection colors to be as close to Aqua Blue as possible.
Tinytlf’s selection algorithms are character and line level algorithms, not block level algorithms like most web browsers. That means that even if you select an entire paragraph, tinytlf only knows you’re selecting from the paragraph begin index to the paragraph end index.
This leads to some interesting consequences, like an image on the first atomIndex in a line causing the entire line height to be as tall as him. You see some overlap, because tinytlf’s default selectionAlpha is 0.28.
In addition, all the decorations in tinytlf only draw underneath the TextLines. Therefore you don’t see selection over images, like you would in a web browser. Later I might allow the option for decorating on top of the Lines layer, but I’ve left it out for 1.0.
This entry was posted
on Monday, August 30th, 2010 at 6:30 am and is filed under actionscript, community.
You can follow any responses to this entry through the RSS 2.0 feed.
You can leave a response, or trackback from your own site.
Since last week, pretty much the most requested feature has been text flow around inline graphics. Yes, even more than editability. I’ve had cleaning up and adding advanced features to the TextLayout and TextContainer on the tinytlf 1.0 roadmap for a while, but last night I finally got to work on it. These classes are only preliminary, but I hope they demo just how powerful tinytlf’s layout architecture can be.
Ok, so say we have this wikipedia entry about the fascinating Atrophaneura hector (Crimson Rose) butterfly. It’s a nice article, and tinytlf formats it well (except for the TLMR bug):
Don’t encyclopedia entries come with an image?
Much better!
Put that image where you want it
Alright, now we’re rockin’
Ok, I know this is ugly, but I thought I’d show off a little bit. You aren’t constricted to docking on the left or the right, the new layout algorithm will wrap text around images no matter where they are in the markup.
Features
This shows off some features I’ve never talked about before. Of course there’s flow around the image, but that’s really just some fancy layout math, it’s not too complicated. I’m probably most proud of the fact that tinytlf intelligently renders only the invalid TextLines.
Invalidation
This is a Flash Text Engine feature, but it’s one that I love: when members of the FTE ContentElement model change (text, ElementFormat, etc.), the TextBlock will tag the TextLines which render the content “invalid.” The FTE can’t automatically update the TextLines; whomever renders the TextLines (tinytlf, in this case), is responsible for surgically removing and re-rendering the invalid lines.
It’s a delicate procedure, but tinytlf handles it like a champ. You see the result of this in the examples whenever you roll over an anchor tag and it changes fontPosture or color.
Layout
The second part of this is the little bit of fancy math I did to break and layout the lines in the proper order. If you want to see the algorithm, check out the newest ImageFlowContainer here.
It’s not too difficult. Basically, as I lay out the lines, I calculate the (x, y) position for the next TextLine. Because I can change the x and y independently of each other, I can break TextLines across the plane of the graphic.
Where can it go from here? My next feature will be to respect padding set on the <img/> tag. After that will be allowing a way for the <img/> to specify whether it renders inline, causes line/paragraph breaking, etc. There’s a lot that can be done.
Caveats
I haven’t tested this with more than one image. In theory it should work, but I’ve been awake for longer than 24 hours, so I can’t trust I’m actually thinking as clearly as I think I am o.O.
And yes, there’s a bug with the links. It’s especially prominent here, but basically when you move the mouse very quickly, the FTE TextLineMirrorRegions dispatch a “mouseOver” but never its corresponding “mouseOut.” If anybody on Adobe’s TLF or FTE team can shed some light on this situation, I’d be very grateful.
This entry was posted
on Tuesday, August 24th, 2010 at 10:54 am and is filed under actionscript, community.
You can follow any responses to this entry through the RSS 2.0 feed.
You can leave a response, or trackback from your own site.