Flex 4 includes the new Adobe Text Layout Framework (TLF) which is a fantastic new addition that allows greater control of text rendering and the flow of text through containers. You can view the demo material here.
I recently had to use the TLF to generate a whole bunch of links in a stream that flowed through several columns within a composition. The first problem I noticed was that all of the links when chained together were converted to one link. I lost track of how this was fixed but I think it had to do with the links not having href attributes. Anyway, that wasn’t the main problem I encountered. I managed to stream my links together into one long chain and had them nicely justified to the columns :
As you can see I was trying to create a nice wall of links. Eventually these will be names and the user will be able to click on a link for more information etc. Nice! So all it needs is a nice hover state on each link, right? So I add a hover format to the TextFlow and then fell off my chair and sobbed.
Check out the proof-of-concept I created here : http://lyraspace.com/transfer/tlf/TextFlow_POC.html and then read through the posts I wrote on the official forum :
http://forums.adobe.com/message/2682942#2682942
and
http://forums.adobe.com/thread/598789?tstart=0
As you can see, there’s a massive performance issue here with formatting links within a paragraph. It needs to re-draw the paragraph to format the link and with 500 links in the paragraph it’s going to get very sluggish.
The answer then is to create a paragraph for each link? No, this will create a new line for each link.
So I had to work out when a new link would sit on a new line and then create a new paragraph from that link.
And here’s how I did it …
programmatically creating a TextFlow stream of Link Elements
- <?xml version="1.0" encoding="utf-8"?>
- <s:Application
- xmlns:fx="http://ns.adobe.com/mxml/2009"
- xmlns:s="library://ns.adobe.com/flex/spark"
- xmlns:mx="library://ns.adobe.com/flex/mx"
- xmlns:maps="com.cravens.nr.maps.*"
- minWidth="955" minHeight="600"
- viewSourceURL="srcview/index.html"
- >
- <fx:Declarations>
- <maps:MainEventMap id="eventMap" />
- <maps:ModelMap id="modelMap" />
- </fx:Declarations>
- <fx:Script>
- <![CDATA[
- import com.cravens.nr.model.FontsManager;
- import flash.text.engine.BreakOpportunity;
- import flash.text.engine.FontLookup;
- import flash.text.engine.RenderingMode;
- import flash.text.engine.TypographicCase;
- import flashx.textLayout.compose.TextFlowLine;
- import flashx.textLayout.conversion.TextConverter;
- import flashx.textLayout.elements.Configuration;
- import flashx.textLayout.elements.LinkElement;
- import flashx.textLayout.elements.OverflowPolicy;
- import flashx.textLayout.elements.ParagraphElement;
- import flashx.textLayout.elements.SpanElement;
- import flashx.textLayout.elements.SubParagraphGroupElement;
- import flashx.textLayout.elements.TextFlow;
- import flashx.textLayout.events.CompositionCompleteEvent;
- import flashx.textLayout.events.DamageEvent;
- import flashx.textLayout.events.FlowOperationEvent;
- import flashx.textLayout.events.UpdateCompleteEvent;
- import flashx.textLayout.formats.TextAlign;
- import flashx.textLayout.formats.TextLayoutFormat;
- import mx.collections.ArrayCollection;
- import mx.events.FlexEvent;
- import mx.utils.ObjectUtil;
- protected var config:Configuration;
- protected var paragraphText:String = "<p>";
- [Bindable]
- protected var textFlow:TextFlow;
- [Bindable]
- protected var namesArr:ArrayCollection;
- protected var linkCount:int = 0;
- protected var para:ParagraphElement;
- protected var link:LinkElement;
- protected var space:SpanElement;
- protected var updateDelayTimer:Timer;
- //---------------------------------------------------------------------------
- public function handleResult(e:Object):void
- {
- trace("Result:"+ObjectUtil.toString(e));
- /*
- The XML structure returned from the server maps to an ArrayCollection
- */
- namesArr = e.signatures.signature as ArrayCollection;
- startTextFlowCreation();
- }
- //---------------------------------------------------------
- public function handleFault(e:Object):void
- {
- trace("Fault:"+ObjectUtil.toString(e));
- }
- //---------------------------------------------------------------------------
- protected function startTextFlowCreation():void
- {
- FontsManager.getInstance();
- config = Configuration(TextFlow.defaultConfiguration).clone();
- var linkNormalFormat:TextLayoutFormat = new TextLayoutFormat();
- linkNormalFormat.color = 0x000000;
- var linkHoverFormat:TextLayoutFormat = new TextLayoutFormat();
- linkHoverFormat.color = 0x999999;
- var defaultFormat:TextLayoutFormat = new TextLayoutFormat();
- defaultFormat.paddingTop = 6;
- defaultFormat.color = 0x333333;
- defaultFormat.textAlign = TextAlign.JUSTIFY;
- defaultFormat.textAlignLast = TextAlign.JUSTIFY;
- defaultFormat.fontSize = 10;
- defaultFormat.lineHeight = 12;
- defaultFormat.fontLookup = FontLookup.EMBEDDED_CFF;
- defaultFormat.typographicCase = TypographicCase.UPPERCASE;
- defaultFormat.renderingMode = RenderingMode.CFF;
- defaultFormat.fontFamily = FontsManager.NOVAMONO;
- config.textFlowInitialFormat = defaultFormat;
- config.defaultLinkNormalFormat = linkNormalFormat;
- config.defaultLinkHoverFormat = linkHoverFormat;
- config.overflowPolicy = OverflowPolicy.FIT_DESCENDERS;
- textFlow = new TextFlow(config);
- textFlow.columnCount = 4;
- para = new ParagraphElement();
- textFlow.addChild(para);
- updateDelayTimer = new Timer(50,1);
- updateDelayTimer.addEventListener(TimerEvent.TIMER_COMPLETE,checkLink);
- addLinkElement();
- }
- //---------------------------------------------------------
- protected function addLinkElement():void
- {
- var o:Object = namesArr[linkCount];
- var firstName:String = o.first_name;
- var lastName:String = o.last_name;
- link = new LinkElement();
- link.href = "event:"+o.id;
- var linkText:SpanElement = new SpanElement();
- linkText.text = o.first_name+" "+o.last_name;
- space = new SpanElement();
- space.text = " ";
- link.addChild(linkText);
- para.addChild(link);
- para.addChild(space);
- textFlow.flowComposer.composeToPosition(textFlow.textLength);
- updateDelayTimer.start();
- }
- //---------------------------------------------------------
- protected function checkLink(event:TimerEvent = null):void
- {
- updateDelayTimer.reset();
- if(link)
- {
- var linkStart:int = link.getAbsoluteStart();
- var linkEnd:int = linkStart+link.textLength;
- var textFlowLine:TextFlowLine = textFlow.flowComposer.findLineAtPosition(linkStart);
- var lineStart:int = textFlowLine.absoluteStart;
- var isStartOfLine:Boolean = linkStart==lineStart;
- if(isStartOfLine && linkStart!=0)
- {
- para.removeChild(link);
- para.removeChild(space);
- para = new ParagraphElement();
- textFlow.addChild(para);
- para.addChild(link);
- para.addChild(space);
- }
- linkCount++;
- if(linkCount == namesArr.length) textFlowComplete();
- else addLinkElement();
- }
- }
- //---------------------------------------------------------
- protected function textFlowComplete():void
- {
- updateDelayTimer.removeEventListener(TimerEvent.TIMER_COMPLETE,checkLink);
- updateDelayTimer = null;
- }
- ]]>
- </fx:Script>
- <s:VGroup width="100%" height="100%">
- <s:TextArea
- id="textArea"
- width="100%"
- height="100%"
- textFlow="{textFlow}"
- editable="false"
- selectable="false"
- />
- </s:VGroup>
- </s:Application>
In the example above you have to assume that the names_arr ArrayCollection is loaded in via the Mate framework eventMap and that this collection is made up of objects with a firstName and lastName property that I can use to create the links.
The formatting of the TextFlow can be created in advance by setting up a Configuration object and adding it as an argument while instantiating the TextFlow.
Creating a TextFlow Configuration object
- config = Configuration(TextFlow.defaultConfiguration).clone();
- var linkNormalFormat:TextLayoutFormat = new TextLayoutFormat();
- linkNormalFormat.color = 0x000000;
- var linkHoverFormat:TextLayoutFormat = new TextLayoutFormat();
- linkHoverFormat.color = 0x999999;
- var defaultFormat:TextLayoutFormat = new TextLayoutFormat();
- defaultFormat.paddingTop = 6;
- defaultFormat.color = 0x333333;
- defaultFormat.textAlign = TextAlign.JUSTIFY;
- defaultFormat.textAlignLast = TextAlign.JUSTIFY;
- defaultFormat.fontSize = 10;
- defaultFormat.lineHeight = 12;
- defaultFormat.fontLookup = FontLookup.EMBEDDED_CFF;
- defaultFormat.typographicCase = TypographicCase.UPPERCASE;
- defaultFormat.renderingMode = RenderingMode.CFF;
- defaultFormat.fontFamily = FontsManager.NOVAMONO;
- config.textFlowInitialFormat = defaultFormat;
- config.defaultLinkNormalFormat = linkNormalFormat;
- config.defaultLinkHoverFormat = linkHoverFormat;
- config.overflowPolicy = OverflowPolicy.FIT_DESCENDERS;
- textFlow = new TextFlow(config);
… and then to find the position of the element on the line I used this code :
Finding the position of an element within a TextFlow
- var linkStart:int = link.getAbsoluteStart();
- var linkEnd:int = linkStart+link.textLength;
- var textFlowLine:TextFlowLine = textFlow.flowComposer.findLineAtPosition(linkStart);
- var lineStart:int = textFlowLine.absoluteStart;
- var isStartOfLine:Boolean = linkStart==lineStart;
As you can see I re-parent the element to a new ParagraphElement object if the LinkElement is at the start of the line.
The Text Layout Framework is still new and there are bugs, this highlights just how erratic the performance can be with it at the moment so handle cautiously and be sure to post your problems on the forum. The guys at Adobe were very helpful and as with all problems like this, you end up learning a hell of a lot about the framework when solving them!
Hey, you might have noticed I’m now using Snipplr to embed my code. I hope it doesn’t disappear any time soon.














Recent Comments