| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You under the Apache License, Version 2.0 |
| // (the "License"); you may not use this file except in compliance with |
| // the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| package flashx.textLayout.compose |
| { |
| import flash.display.Sprite; |
| import flash.system.Capabilities; |
| |
| import flashx.textLayout.accessibility.TextAccImpl; |
| import flashx.textLayout.container.ContainerController; |
| import flashx.textLayout.container.ScrollPolicy; |
| import flashx.textLayout.debug.assert; |
| import flashx.textLayout.edit.ISelectionManager; |
| import flashx.textLayout.elements.BackgroundManager; |
| import flashx.textLayout.elements.ContainerFormattedElement; |
| import flashx.textLayout.elements.TextFlow; |
| import flashx.textLayout.events.CompositionCompleteEvent; |
| import flashx.textLayout.formats.BlockProgression; |
| import flashx.textLayout.tlf_internal; |
| |
| use namespace tlf_internal; |
| |
| /** |
| * The StandardFlowComposer class provides a standard composer and container manager. |
| * |
| * <p>Each call to <code>compose()</code> or <code>updateAllControllers()</code> normalizes the text flow as a first step. |
| * The normalizing process checks the parts of the TextFlow object that were modified and takes the following steps: |
| * <ol> |
| * <li> Deletes empty FlowLeafElement and SubParagraphGroupElement objects.</li> |
| * <li> Merges sibling spans that have identical attributes.</li> |
| * <li> Adds an empty paragraph if a flow is empty.</li> |
| * </ol> |
| * </p> |
| * |
| * <p>To use a StandardFlowComposer, assign it to the |
| * <code>flowComposer</code> property of a TextFlow object. Call the <code>updateAllControllers()</code> |
| * method to lay out and display the text in the containers attached to the flow composer.</p> |
| * |
| * <p><b>Note:</b> For simple, static text flows, you can also use the one of the text line factory classes. |
| * These factory classes will typically create lines with less overhead than a flow composer, but do not |
| * support editing, dynamic changes, or user interaction.</p> |
| * |
| * @see flashx.textLayout.elements.TextFlow#flowComposer |
| * @includeExample examples\StandardFlowComposer_ClassExample.as -noswf |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| */ |
| |
| public class StandardFlowComposer extends FlowComposerBase implements IFlowComposer |
| { |
| /** @private */ |
| protected var _rootElement:ContainerFormattedElement; |
| private var _controllerList:Array; |
| private var _composing:Boolean; |
| |
| |
| /** |
| * Creates a StandardFlowComposer object. |
| * |
| * <p>To use an StandardFlowComposer object, assign it to the |
| * <code>flowComposer</code> property of a TextFlow object. Call the <code>updateAllControllers()</code> |
| * method to lay out and display the text in the containers attached to the flow composer.</p> |
| * |
| * @includeExample examples\StandardFlowComposer_constructor.as -noswf |
| * |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| */ |
| |
| public function StandardFlowComposer() |
| { |
| super(); |
| _controllerList = []; |
| _composing = false; |
| } |
| |
| /** |
| * True, if the flow composer is currently performing a composition operation. |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| */ |
| public function get composing():Boolean |
| { return _composing; } |
| |
| /** |
| * Returns the absolute position of the first content element in the specified ContainerController object. |
| * |
| * <p>A position is calculated by counting the division between two characters or other elements of a text flow. |
| * The position preceding the first element of a flow is zero. An absolute position is the position |
| * counting from the beginning of the flow.</p> |
| * |
| * @param controller A ContainerController object associated with this flow composer. |
| * @return the position before the first character or graphic in the ContainerController. |
| * |
| * @includeExample examples\StandardFlowComposer_getAbsoluteStart.as -noswf |
| * |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| */ |
| |
| public function getAbsoluteStart(controller:ContainerController):int |
| { |
| // don't look at controller's relativeStart property - it uses this method. hmmmm |
| // TODO: that does seem odd - clean the above implementation up. |
| var stopIdx:int = getControllerIndex(controller); |
| CONFIG::debug { assert(stopIdx != -1,"bad controller to LayoutFlowComposer.getRelativeStart"); } |
| var rslt:int = _rootElement.getAbsoluteStart(); |
| for (var idx:int = 0; idx < stopIdx; idx++) |
| rslt += _controllerList[idx].textLength; |
| |
| return rslt; |
| } |
| |
| /** @copy IFlowComposer#rootElement |
| * |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| */ |
| |
| public function get rootElement():ContainerFormattedElement |
| { return _rootElement; } |
| |
| |
| /** @copy IFlowComposer#setRootElement() |
| * |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| */ |
| public function setRootElement(newRootElement:ContainerFormattedElement):void |
| { |
| |
| if (_rootElement != newRootElement) |
| { |
| if (newRootElement is TextFlow && (newRootElement as TextFlow).flowComposer != this) |
| (newRootElement as TextFlow).flowComposer = this; |
| else |
| { |
| clearCompositionResults(); |
| |
| detachAllContainers(); |
| _rootElement = newRootElement; |
| _textFlow = _rootElement ? _rootElement.getTextFlow() : null; |
| attachAllContainers(); |
| } |
| } |
| } |
| |
| /** @private */ |
| tlf_internal function detachAllContainers():void |
| { |
| |
| // detatch accessibility from the containers |
| // Why only the first container? |
| if (_controllerList.length > 0 && _textFlow) |
| { |
| var firstContainerController:ContainerController = getControllerAt(0); |
| var firstContainer:Sprite = firstContainerController.container; |
| if (firstContainer) |
| clearContainerAccessibilityImplementation(firstContainer); |
| } |
| |
| var cont:ContainerController; |
| for each (cont in _controllerList) |
| { |
| cont.dispose(); |
| } |
| } |
| |
| static private function clearContainerAccessibilityImplementation(cont:Sprite):void |
| { |
| if (cont.accessibilityImplementation) |
| { |
| if (cont.accessibilityImplementation is TextAccImpl) |
| TextAccImpl(cont.accessibilityImplementation).detachListeners(); |
| cont.accessibilityImplementation = null; |
| } |
| } |
| |
| /** @private */ |
| tlf_internal function attachAllContainers():void |
| { |
| var cont:ContainerController; |
| for each (cont in _controllerList) |
| ContainerController(cont).setRootElement(_rootElement); |
| |
| |
| if (_controllerList.length > 0 && _textFlow) |
| { |
| // attach accessibility to the containers |
| // Why only the first container? There are workflows that this will fail |
| // for example: a pagination workflow that has a composed chain of containers but only displays one at a time. |
| if (textFlow.configuration.enableAccessibility && Capabilities.hasAccessibility) |
| { |
| var firstContainer:Sprite = getControllerAt(0).container; |
| if (firstContainer) |
| { |
| clearContainerAccessibilityImplementation(firstContainer); |
| firstContainer.accessibilityImplementation = new TextAccImpl(firstContainer, _textFlow); |
| } |
| } |
| |
| var curContainer:Sprite; |
| // turn off focusRect on all containers |
| for (var i:int = 0; i < _controllerList.length; ++i) |
| { |
| curContainer = getControllerAt(i).container; |
| if (curContainer) |
| curContainer.focusRect = false; |
| } |
| } |
| |
| // TODO: can be more efficient? - just damage all |
| clearCompositionResults(); |
| } |
| |
| /** @copy IFlowComposer#numControllers |
| * |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| */ |
| |
| public function get numControllers():int |
| { return _controllerList ? _controllerList.length : 0; } |
| |
| /** @copy IFlowComposer#addController() |
| * |
| * @includeExample examples\StandardFlowComposer_addController.as -noswf |
| * |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| */ |
| |
| public function addController(controller:ContainerController):void |
| { |
| CONFIG::debug { assert (_controllerList.indexOf(controller) < 0, "adding controller twice"); } |
| _controllerList.push(ContainerController(controller)); |
| if (this.numControllers == 1) |
| { |
| attachAllContainers(); |
| } |
| else |
| { |
| controller.setRootElement(_rootElement); |
| var curContainer:Sprite = controller.container; |
| if (curContainer) |
| curContainer.focusRect = false; |
| if (textFlow) |
| { |
| // mark the previous container as geometry damaged - this is more than is needed |
| controller = this.getControllerAt(this.numControllers-2); |
| var damageStart:int = controller.absoluteStart; |
| var damageLen:int = controller.textLength; |
| // watch out for an empty previous container |
| if (damageLen == 0) |
| { |
| if (damageStart != textFlow.textLength) |
| damageLen++; |
| else if (damageStart != 0) |
| { |
| damageStart--; |
| damageLen++; |
| } |
| } |
| if (damageLen) |
| textFlow.damage(damageStart,damageLen,FlowDamageType.GEOMETRY,false); |
| } |
| } |
| } |
| /** @copy IFlowComposer#addControllerAt() |
| * |
| * @includeExample examples\StandardFlowComposer_addControllerAt.as -noswf |
| * |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| */ |
| |
| public function addControllerAt(controller:ContainerController, index:int):void |
| { |
| CONFIG::debug { assert (_controllerList.indexOf(controller) == -1, "adding controller twice"); } |
| detachAllContainers(); |
| _controllerList.splice(index,0,ContainerController(controller)); |
| attachAllContainers(); |
| } |
| |
| /** Removes a trailing controller with no content without doing any damage */ |
| private function fastRemoveController(index:int):Boolean |
| { |
| if (index == -1) |
| return true; |
| var cont:ContainerController = _controllerList[index]; |
| if (!cont) |
| return true; |
| if (!_textFlow || cont.absoluteStart == _textFlow.textLength) |
| { |
| if (index == 0) |
| { |
| var firstContainer:Sprite = cont.container; |
| if (firstContainer) |
| clearContainerAccessibilityImplementation(firstContainer); |
| } |
| cont.setRootElement(null); |
| _controllerList.splice(index,1); |
| return true; |
| } |
| return false; |
| } |
| |
| /** @copy IFlowComposer#removeController() |
| * |
| * @includeExample examples\StandardFlowController_removeController.as -noswf |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| */ |
| |
| public function removeController(controller:ContainerController):void |
| { |
| var index:int = getControllerIndex(controller); |
| if (!fastRemoveController(index)) |
| { |
| detachAllContainers(); |
| _controllerList.splice(index,1); |
| attachAllContainers(); |
| } |
| } |
| /** @copy IFlowComposer#removeControllerAt() |
| * |
| * @includeExample examples\StandardFlowController_removeControllerAt.as -noswf |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| */ |
| |
| public function removeControllerAt(index:int):void |
| { |
| if (!fastRemoveController(index)) |
| { |
| detachAllContainers(); |
| _controllerList.splice(index,1); |
| attachAllContainers(); |
| } |
| } |
| /** @copy IFlowComposer#removeAllControllers() |
| * |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| */ |
| |
| public function removeAllControllers():void |
| { |
| detachAllContainers(); |
| _controllerList.splice(0,_controllerList.length); |
| } |
| |
| /** @copy IFlowComposer#getControllerAt() |
| * |
| * @includeExample examples\StandardFlowComposer_getControllerAt.as -noswf |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| */ |
| |
| public function getControllerAt(index:int):ContainerController |
| { |
| return _controllerList[index]; |
| } |
| |
| /** @copy IFlowComposer#getControllerIndex() |
| * |
| * @includeExample examples\StandardFlowComposer_getControllerIndex.as -noswf |
| * @playerversion Flash 10 |
| * @player version AIR 1.5 |
| * @langversion 3.0 |
| * @playerversion AIR 1.5 |
| */ |
| |
| public function getControllerIndex(controller:ContainerController):int |
| { |
| // TODO: binary search? |
| for (var idx:int = 0; idx < _controllerList.length; idx++) |
| { |
| if (_controllerList[idx] == controller) |
| return idx; |
| } |
| return -1; |
| } |
| |
| /** |
| * Returns the index of the controller containing the content at the specified position. |
| * |
| * <p>A position can be considered to be the division between two characters or other elements of a text flow. If |
| * the value in <code>absolutePosition</code> is a position between the last character of one |
| * container and the first character of the next, then the preceding container is returned if |
| * the <code>preferPrevious</code> parameter is set to <code>true</code> and the later container is returned if |
| * the <code>preferPrevious</code> parameter is set to <code>false</code>.</p> |
| * |
| * <p>The method returns -1 if the content at the specified position is not in any container or is outside |
| * the range of positions in the text flow.</p> |
| * |
| * @param absolutePosition The position of the content for which the container index is sought. |
| * @param preferPrevious Specifies which container index to return when the position is between the last element in |
| * one container and the first element in the next. |
| * |
| * @return the index of the container controller or -1 if not found. |
| * |
| * @includeExample examples\StandardFlowComposer_findControllerIndexAtPosition.as -noswf |
| * |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| */ |
| |
| public function findControllerIndexAtPosition(absolutePosition:int,preferPrevious:Boolean=false):int |
| { |
| |
| var lo:int = 0; |
| var hi:int = _controllerList.length-1; |
| while (lo <= hi) |
| { |
| var mid:int = (lo+hi)/2; |
| var cont:ContainerController = _controllerList[mid]; |
| if (cont.absoluteStart <= absolutePosition) |
| { |
| if (preferPrevious) |
| { |
| if (cont.absoluteStart + cont.textLength >= absolutePosition) |
| { |
| // find first container or first one with non-zero textLength |
| while (mid != 0 && cont.absoluteStart == absolutePosition) |
| { |
| mid--; |
| cont = _controllerList[mid]; |
| } |
| return mid; |
| } |
| } |
| else |
| { |
| |
| if (cont.absoluteStart == absolutePosition && cont.textLength != 0) |
| { |
| while (mid != 0) |
| { |
| cont = _controllerList[mid-1]; |
| if (cont.textLength != 0) |
| break; |
| mid--; |
| } |
| return mid; |
| } |
| if (cont.absoluteStart + cont.textLength > absolutePosition) |
| return mid; |
| } |
| lo = mid+1; |
| } |
| else |
| hi = mid-1; |
| } |
| return -1; |
| } |
| |
| /** Clear whatever computed values are left from the last composition, in the flow composer and |
| * in each of its controllers. @private |
| */ |
| |
| tlf_internal function clearCompositionResults():void |
| { |
| initializeLines(); |
| for each (var cont:ContainerController in _controllerList) |
| cont.clearCompositionResults(); |
| } |
| |
| /** |
| * Composes the content of the root element and updates the display. |
| * |
| * <p>Text layout is conducted in two phases: composition and display. In the composition phase, |
| * the flow composer calculates how many lines are necessary to display the content as well as the position of these |
| * lines in the flow's display containers. In the display phase, |
| * the flow composer updates the display object children of its containers. The <code>updateAllControllers()</code> |
| * method initiates both phases in sequence. The StandardFlowComposer keeps track of changes to content |
| * so that a full cycle of composition and display is only performed when necessary.</p> |
| * |
| * <p>This method updates all the text lines and the display list immediately and synchronously.</p> |
| * |
| * <p>If the contents of any container is changed, the method returns <code>true</code>.</p> |
| * |
| * @return true if anything changed. |
| * |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| * |
| */ |
| |
| public function updateAllControllers():Boolean |
| { |
| return updateToController(); |
| } |
| |
| /** |
| * Composes and updates the display up to and including the container at the specified index. |
| * |
| * <p>The <code>updateToController()</code> method composes the content and |
| * updates the display of all containers up to and including the container at the specified index. |
| * For example, if you have a chain of 20 containers and specify an index of 10, |
| * <code>updateToController()</code> ensures that the first through the tenth (indexes 0-9) |
| * containers are composed and displayed. Composition stops at that point. If <code>controllerIndex</code> |
| * is -1 (or not specified), then all containers are updated.</p> |
| * |
| * <p>This method updates all the text lines and the display list immediately and synchronously.</p> |
| * |
| * <p>If the contents of any container is changed, the method returns <code>true</code>.</p> |
| * |
| * @param index index of the last container to update (by default updates all containers) |
| * @return <code>true</code>, if anything changed. |
| * |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| * |
| */ |
| |
| public function updateToController(index:int = int.MAX_VALUE):Boolean |
| { |
| //CONFIG::debug { assert(!_composing,"updateToController: compose in process"); } |
| if (_composing) |
| return false; |
| |
| //note that this will always update the display AND update the |
| //selection. So, even if nothing has changed that would cause |
| //a recompose, the selection would still be redrawn. |
| var sm:ISelectionManager = textFlow.interactionManager; |
| if (sm) |
| sm.flushPendingOperations(); |
| CONFIG::debug { assert(!_composing, "Didn't expect to be composing here"); } |
| internalCompose(-1, index); |
| var shapesDamaged:Boolean = areShapesDamaged(); |
| if (shapesDamaged) |
| updateCompositionShapes(); |
| |
| if (sm) |
| sm.refreshSelection(); |
| return shapesDamaged; |
| } |
| |
| /** |
| * Sets the focus to the container that contains the location specified by the <code>absolutePosition</code> |
| * parameter. |
| * |
| * <p>The StandardFlowComposer calls the <code>setFocus()</code> method of the ContainerController object |
| * containing the specified text flow position.</p> |
| * |
| * @param absolutePosition Specifies the position in the text flow of the container to receive focus. |
| * @param leanLeft If true and the position is before the first character in a container, sets focus to the end of |
| * the previous container. |
| * |
| * @see flash.display.Stage#focus |
| * |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| */ |
| |
| public function setFocus(absolutePosition:int,leanLeft:Boolean=false):void |
| { |
| var idx:int = findControllerIndexAtPosition(absolutePosition,leanLeft); |
| if (idx == -1) |
| idx = this.numControllers-1; |
| if (idx != -1) |
| _controllerList[idx].setFocus(); |
| } |
| |
| /** |
| * Called by the TextFlow when the interaction manager changes. |
| * |
| * <p>This function is called automatically. Your code does not typically need to call this |
| * method. Classes that extend StandardFlowComposer can override this method to update |
| * event listeners and other properties that depend on the interaction manager.</p> |
| * |
| * @param newInteractionManager The new ISelectionManager instance. |
| * |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| */ |
| public function interactionManagerChanged(newInteractionManager:ISelectionManager):void |
| { |
| for each (var controller:ContainerController in _controllerList) |
| controller.interactionManagerChanged(newInteractionManager); |
| } |
| |
| |
| private function updateCompositionShapes():void |
| { |
| for each (var controller:ContainerController in _controllerList) |
| controller.updateCompositionShapes(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Composition |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** @private Override required because we may be damaged if the last container has scrolling */ |
| public override function isDamaged(absolutePosition:int):Boolean |
| { |
| // Returns true if any text from _damageAbsoluteStart through absolutePosition needs to be recomposed |
| if (!super.isDamaged(absolutePosition)) |
| { |
| if (absolutePosition == _textFlow.textLength) |
| { |
| var container:ContainerController = getControllerAt(numControllers-1); |
| if (container && (container.verticalScrollPolicy != ScrollPolicy.OFF || container.horizontalScrollPolicy != ScrollPolicy.OFF)) |
| return true; |
| } |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** Returns true if composition is necessary, false otherwise */ |
| protected function preCompose():Boolean |
| { |
| CONFIG::debug { checkFirstDamaged(); } |
| rootElement.preCompose(); |
| |
| // No content, nothing to compose - TextFlow isn't loaded or connected |
| CONFIG::debug { assert(rootElement.textLength != 0,"bad TextFlow after normalize"); } |
| |
| // brand new content |
| if (numLines == 0) |
| initializeLines(); |
| |
| return isDamaged(rootElement.getAbsoluteStart() + rootElement.textLength); |
| } |
| |
| /** @private */ |
| tlf_internal function getComposeState():ComposeState |
| { return ComposeState.getComposeState(); } |
| |
| /** @private */ |
| tlf_internal function releaseComposeState(state:ComposeState):void |
| { ComposeState.releaseComposeState(state); } |
| |
| /** @private Return the first damaged controller */ |
| tlf_internal function callTheComposer(composeToPosition:int, controllerEndIndex:int):ContainerController |
| { |
| |
| if (_damageAbsoluteStart == rootElement.getAbsoluteStart()+rootElement.textLength) |
| return getControllerAt(numControllers-1); |
| |
| var state:ComposeState = getComposeState(); |
| |
| var lastComposedPosition:int = state.composeTextFlow(textFlow, composeToPosition, controllerEndIndex); |
| if (_damageAbsoluteStart < lastComposedPosition) |
| _damageAbsoluteStart = lastComposedPosition; |
| CONFIG::debug { checkFirstDamaged(); } |
| |
| // make sure there is an empty TextFlowLine covering any trailing content |
| finalizeLinesAfterCompose(); |
| var startController:ContainerController = state.startController; |
| |
| releaseComposeState(state); |
| |
| if (textFlow.hasEventListener(CompositionCompleteEvent.COMPOSITION_COMPLETE)) |
| textFlow.dispatchEvent(new CompositionCompleteEvent(CompositionCompleteEvent.COMPOSITION_COMPLETE,false,false,textFlow, 0,lastComposedPosition)); |
| |
| CONFIG::debug { textFlow.debugCheckTextFlow(); } |
| return startController; |
| } |
| |
| private var lastBPDirectionScrollPosition:Number = Number.NEGATIVE_INFINITY; |
| |
| static private function getBPDirectionScrollPosition(bp:String,cont:ContainerController):Number |
| { |
| return bp == BlockProgression.TB ? cont.verticalScrollPosition : cont.horizontalScrollPosition; |
| } |
| |
| /** Bottleneck function for all types of compose. Does the work of compose, no matter how it is called. @private |
| * @return first controller with changed shapes |
| */ |
| private function internalCompose(composeToPosition:int = -1, composeToControllerIndex:int = -1):ContainerController |
| { |
| var bp:String; |
| |
| // Flush pending events (e.g. insert events) |
| var sm:ISelectionManager = textFlow.interactionManager; |
| if (sm) |
| sm.flushPendingOperations(); |
| |
| CONFIG::debug { assert(_composing == false,"internalCompose: Recursive call"); } |
| |
| _composing = true; |
| |
| var startController:ContainerController; |
| try |
| { |
| if (preCompose()) |
| { |
| if (textFlow && numControllers != 0) |
| { |
| // This code should really be in preCompose, but we cannot add new parameters to it without breaking the API. |
| var damageLimit:int = _textFlow.textLength; // If we aren't composed up to this point, we'll have to force composition |
| // If the container index is above the range, set it to the last container |
| composeToControllerIndex = Math.min(composeToControllerIndex,numControllers-1); |
| if (composeToPosition != -1 || composeToControllerIndex != -1) |
| { |
| if (composeToControllerIndex < 0) |
| { |
| if (composeToPosition >= 0) |
| damageLimit = composeToPosition; |
| } |
| else |
| { |
| // We're composing the container, make sure the entire container is composed |
| var controller:ContainerController = getControllerAt(composeToControllerIndex); |
| if (controller.textLength != 0) |
| damageLimit = controller.absoluteStart+controller.textLength; |
| |
| // If we're composing the last container, and its scrollable, only require valid composition to the end of the scrolled position |
| if (composeToControllerIndex == numControllers - 1) |
| { |
| bp = rootElement.computedFormat.blockProgression; |
| |
| // skip it if damageAbsoluteStart is past the end of the controller. are there risks here? AND scrollpositions haven't changed since last composeToControllerIndex |
| var lastVisibleLine:TextFlowLine = controller.getLastVisibleLine(); |
| if (lastVisibleLine && getBPDirectionScrollPosition(bp,controller) == this.lastBPDirectionScrollPosition) |
| damageLimit = lastVisibleLine.absoluteStart+lastVisibleLine.textLength; |
| } |
| |
| } |
| } |
| |
| lastBPDirectionScrollPosition = Number.NEGATIVE_INFINITY; |
| |
| if (_damageAbsoluteStart < damageLimit) |
| { |
| startController = callTheComposer(composeToPosition, composeToControllerIndex); |
| if (startController) |
| { |
| var idx:int = this.getControllerIndex(startController); |
| while (idx < numControllers) |
| getControllerAt(idx++).shapesInvalid = true; |
| } |
| } |
| } |
| } |
| } |
| catch (e:Error) |
| { |
| _composing = false; |
| throw(e); |
| } |
| _composing = false; |
| |
| if (controller && composeToControllerIndex == numControllers - 1) |
| { |
| lastBPDirectionScrollPosition = getBPDirectionScrollPosition(bp,controller); |
| } |
| |
| return startController; |
| } |
| |
| |
| /** @private */ |
| tlf_internal function areShapesDamaged():Boolean |
| { |
| var cont:ContainerController; // scratch |
| // TODO: a flag on this? |
| for each (cont in _controllerList) |
| { |
| if (cont.shapesInvalid) |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Calculates how many lines are necessary to display the content in the root element of the flow and the positions of these |
| * lines in the flow's display containers. |
| * |
| * <p>The <code>compose()</code> method only composes content if it has changed since the last composition operation. |
| * Results are saved so that subsequent |
| * calls to <code>compose()</code> or <code>updateAllControllers()</code> do not perform an additional recomposition |
| * if the flow content has not changed.</p> |
| * |
| * <p>If the contents of any container have changed, the method returns <code>true</code>.</p> |
| * |
| * @return true if anything changed. |
| * |
| * @includeExample examples\StandardFlowComposer_compose.as -noswf |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| * |
| * @see #updateAllControllers() |
| * @see #updateToController() |
| */ |
| public function compose():Boolean |
| { |
| //CONFIG::debug { assert(!_composing,"compose: compose in process"); } |
| return _composing ? false : internalCompose() != null; |
| } |
| |
| /** @copy IFlowComposer#composeToPosition() |
| * |
| * @includeExample examples\StandardFlowComposer_composeToPosition.as -noswf |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| */ |
| public function composeToPosition(absolutePosition:int = int.MAX_VALUE):Boolean |
| { |
| //CONFIG::debug { assert(!_composing,"composeToPosition: compose in process"); } |
| return _composing ? false : internalCompose(absolutePosition, -1) != null; |
| } |
| |
| /** @copy IFlowComposer#composeToController() |
| * |
| * @includeExample examples\StandardFlowComposer_composeToController.as -noswf |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| */ |
| public function composeToController(index:int = int.MAX_VALUE):Boolean |
| { |
| //CONFIG::debug { assert(!_composing,"composeToController: compose in process"); } |
| return _composing ? false : internalCompose(-1, index) != null; |
| } |
| |
| /** @private */ |
| tlf_internal function createBackgroundManager():BackgroundManager |
| { return new BackgroundManager(); } |
| } |
| } |