Monday, July 20, 2009

Flex 4: Threaded Text Using TextFlow


Introduction

Ever wanted to build threaded text like you see above? This is trivial to do in a DTP package like Adobe InDesign or even a graphics tool like Adobe Illustrator, but has been next to impossible to do on the web. That is, until now.

For those of you unfamiliar with the term “threaded,” it’s when you have text flow from one frame to another as a chain. E.g., try to select the text in the left frame above and continue to the right frame: your selection will flow from one frame to the other.

How’s this possible? With the new Flex 4 (“Gumbo”) TextFlow control. TextFlow builds on the powerful Text Layout Framework (TLF) that runs on the new text engine introduced in Flash Player 10. Among its many capabilities: threading.

Playtime

Before we see how to code this, check out a few nifty features of TextFlow in the example above.

  1. Selection You can select across frames by simply dragging the mouse
  2. Edit You can insert/delete/update text
  3. Undo You can undo changes to text: make any edit you like and try Ctrl+Z
  4. Copy/Paste You can copy/paste from/to the frames
  5. Embedded Fonts You can use any font without worrying if the user has it

Excited? Read on for specifics on how you can do it too.

Application MXML

Flex uses a combination of MXML and ActionScript to define applications. TextFlow is new to Flex 4, so you will need either the Flex 4 SDK or the Flash Builder 4 IDE (new name for Flex Builder) to build. Both are currently in beta. Be sure to use the Beta 1 SDK (build 7219) that’s included in the Flash Builder download by default.

Here’s the code:

  1: <?xml version="1.0" encoding="utf-8"?>
  2: <mx:Application initialize="init();" layout="absolute" backgroundAlpha="0" xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/halo" minWidth="1024" minHeight="768" viewSourceURL="srcview/index.html">
  3:   <fx:Declarations>
  4:     <fx:String id="textXML" source="Snake.xml"/>
  5:   </fx:Declarations>
  6:
  7:   <fx:Style>
  8:     @namespace mx "library://ns.adobe.com/flex/halo";
  9:     @namespace s "library://ns.adobe.com/flex/spark";
 10:  
 11:     @font-face {
 12:       src: url("C:/Windows/Fonts/Windsong.ttf");
 13:       fontFamily: Windsong;
 14:       advandedAntiAliasing: true;
 15:       unicodeRange: U+0020-U+007E;
 16:       cff: true;
 17:     }
 18:   </fx:Style>
 19:
 20:   <fx:Script>
 21:     <![CDATA[
 22:       import flashx.textLayout.conversion.TextFilter;
 23:       import flash.text.engine.FontLookup;
 24:       import flashx.undo.UndoManager;
 25:       import flashx.textLayout.edit.EditManager;
 26:       import mx.core.UIComponent;
 27:       import flashx.textLayout.container.ContainerController;
 28:       import flashx.textLayout.elements.TextFlow;
 29:    
 30:       private var textFlow:TextFlow;
 31:       private var sections:XML =
 32:         <sections>
 33:           <section x="10" y="20" w="160" h="360"/>
 34:           <section x="270" y="80" w="160" h="390"/>
 35:         </sections>
 36:       private var textHolder:UIComponent = new UIComponent();
 37:    
 38:       private function init():void {
 39:         XML.ignoreWhitespace = false;
 40:         textFlow = TextFilter.importToFlow(textXML, TextFilter.TEXT_LAYOUT_FORMAT);
 41:         textFlow.fontLookup = FontLookup.EMBEDDED_CFF;
 42:      
 43:         for each (var section:XML in sections.*) {
 44:           var sprite:Sprite = new Sprite();
 45:        
 46:           sprite.x = section.@x;
 47:           sprite.y = section.@y;
 48:           textHolder.addChild(sprite);
 49:           textFlow.flowComposer.addController(new ContainerController(sprite, section.@w, section.@h));
 50:         }
 51:      
 52:         addElement(textHolder);
 53:         textFlow.interactionManager = new EditManager(new UndoManager());
 54:         textFlow.flowComposer.updateAllControllers();
 55:       }
 56:     ]]>
 57:   </fx:Script>
 58:
 59:   <s:Group>
 60:     <s:VideoElement id="snake" source="Snake.flv" complete="snake.play();"/>
 61:   </s:Group>
 62: </mx:Application>

If you know Flex, this should be self-explanatory. But you’ll want to make special note of a few things:

  1. The basic process is: define a TextFlow, create frames with “controllers,” attach the controllers to the TextFlow, and add a manager to handle user interactions
  2. Flex 4 TLF uses CFF, so see how we need to specify CFF (lines 16, 41) for TextFlow – this is in flux though, and Adobe has changed “cff: true” to “embedAsCFF: true” in subsequent builds

TextFlow XML

The text for the TextFlow is not dissimilar to HTML, but is enhanced to support some very powerful functionality. Our example is quite simple however. It is adapted from the Wikipedia entry on Snake.

  1: <TextFlow xmlns="http://ns.adobe.com/textLayout/2008" fontFamily="Windsong" fontSize="30" color="0x000000">
  2:   <linkNormalFormat color="0x763524" textDecoration="underline"/>
  3:
  4:   <p>Snakes are elongate legless <a href="http://en.wikipedia.org/wiki/Carnivore">carnivorous</a> <a href="http://en.wikipedia.org/wiki/Reptile">reptiles</a> of the suborder Serpentes that can be distinguished from <a href="http://en.wikipedia.org/wiki/Legless_lizard">legless lizards</a> by their lack of eyelids and external ears. Like all <a href="http://en.wikipedia.org/wiki/Squamates">squamates</a>, snakes are ectothermic amniote vertebrates covered in overlapping <a href="http://en.wikipedia.org/wiki/Scale_(zoology)">scales</a>. Like <a href="http://en.wikipedia.org/wiki/Lizards">lizards</a>, from which they evolved, they have loosely articulated <a href="http://en.wikipedia.org/wiki/Skulls">skulls</a> and most can swallow prey much larger than their own head. In order to accommodate their narrow bodies, snakes<span>'</span> paired organs (such as kidneys) appear one in front of the other instead of side by side, and most have only one functional <a href="http://en.wikipedia.org/wiki/Lung">lung</a>. Some species retain a <a href="http://en.wikipedia.org/wiki/Pelvic_girdle">pelvic girdle</a> with a pair of vestigial claws on either side of the cloaca.</p>
  5:   <p>Living snakes are found on every continent except Antarctica. Fifteen <a href="http://en.wikipedia.org/wiki/Family_(biology)">families</a> are currently recognized comprising 456 <a href="http://en.wikipedia.org/wiki/Genus">genera</a> and over 2,900 species.[1][2] They range in size from the tiny, 10 cm long <a href="http://en.wikipedia.org/wiki/Leptotyphlops_carlae">thread snake</a> to pythons and anacondas of up to 7.6 m (25 ft) in length. The recently discovered fossil <a href="http://en.wikipedia.org/wiki/Titanoboa">Titanoboa</a> was 13 m or 43 ft long. Snakes are thought to have evolved from either burrowing or aquatic lizards during the <a href="http://en.wikipedia.org/wiki/Cretaceous">Cretaceous</a> period (c 150 Ma). The diversity of modern snakes appeared during the Paleocene period (c 66 to 56 Ma).</p>

6: </TextFlow>

Conclusion

If you haven’t already checked out Flex 4, hope I’ve convinced you to try it. Download Flex 4 SDK or Flash Builder 4 today – if you’re new to Flex, I’d strongly recommend Flash Builder.

If you want to build the snake animation, that’s a different story. I did this using NewTek LightWave 3D. If you want to know how, I can prepare a tutorial. Meanwhile, you might be interested in my other tutorial, LightWave: Animation Along a Predefined Path.