package org.tinytlf.layout { import flash.display.Sprite; import flash.text.engine.TextBlock; import flash.text.engine.TextLine; import flash.text.engine.TextLineValidity; public class TextContainer extends Sprite { private var mHeight:Number = 0; public function get measuredHeight():Number { return mHeight; } private var explicitHeight:Number = NaN; override public function get height():Number { return explicitHeight; } override public function set height(value:Number):void { if(value === explicitHeight) return; explicitHeight = value; } private var explicitWidth:Number = NaN; override public function get width():Number { return explicitWidth; } override public function set width(value:Number):void { if(value === explicitWidth) return; explicitWidth = value; invalidateLines(); } private var lines:Vector.<TextLine> = new <TextLine>[]; /** * Accepts a TextBlock and last line rendered from the block. Renders * as many lines from the TextBlock as will fit within this container, * and returns the last line successfully rendered. * * If all lines are successfully rendered from the TextBlock, this * method should return null. * * If all lines were rendered from the TextBlock, this method will be * called a second time with a new TextBlock and the previousLine * argument set to null. * * <ul> * <li>If previousLine or the previousLine.nextLine is null, this method * should use TextBlock.createTextLine.</li> * <li>If previousLine has an invalid nextLine member, this method should * attempt to use TextBlock.recreateTextLine.</li> * <li>If the previousLine has a valid nextLine member, this method * should skip ahead in the linked list until one of the previous two * conditions are true.</li> * </ul> */ public function layout(block:TextBlock, previousLine:TextLine):TextLine { _y = measuredHeight; var line:TextLine = createTextLine(block, previousLine); while(line) { addChild(line); lines.push(line); position(line); if(checkConstraints(line)) { return line; } line = createTextLine(block, line); } return line; } public function preLayout():void { _y = 0; mHeight = 0; removeOrphanedLines(); } private function invalidateLines():void { lines.forEach(function(l:TextLine, ...args):void{ l.validity = TextLineValidity.INVALID; }); } /** * Creates or recreates a given TextLine. */ private function createTextLine(block:TextBlock, line:TextLine):TextLine { removeOrphanedLines(); if(line) { if(line.validity === TextLineValidity.INVALID) { return block.recreateTextLine(line, null, width, 0.0, true); } else if(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); } private var _y:Number = 0; private function position(line:TextLine):void { _y += line.ascent; line.y = _y; _y += line.descent; mHeight += line.height; } private function checkConstraints(line:TextLine):Boolean { if(explicitHeight != explicitHeight) return false; return measuredHeight + line.textHeight > explicitHeight; } private function 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; } } private static const orphans:Vector.<TextLine> = new <TextLine>[]; private static function getFirstOrphan(exceptForMe:TextLine):TextLine { if(orphans.length == 0) return null; var orphan:TextLine = orphans.pop(); while(orphan == exceptForMe) orphan = orphans.pop(); while(orphan && orphan.validity == TextLineValidity.VALID) orphan = orphans.pop(); return orphan; } } }