//////////////////////////////////////////////////////////////////////////////// // // 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.elements { import flash.events.Event; import flash.events.EventDispatcher; import flash.events.IEventDispatcher; import flash.text.engine.TextLineValidity; import flash.utils.Dictionary; import flashx.textLayout.compose.FlowComposerBase; import flashx.textLayout.compose.IFlowComposer; import flashx.textLayout.compose.ISWFContext; import flashx.textLayout.compose.StandardFlowComposer; import flashx.textLayout.compose.TextFlowLine; import flashx.textLayout.container.ContainerController; import flashx.textLayout.debug.Debugging; import flashx.textLayout.debug.assert; import flashx.textLayout.edit.ISelectionManager; import flashx.textLayout.events.CompositionCompleteEvent; import flashx.textLayout.events.DamageEvent; import flashx.textLayout.events.ModelChange; import flashx.textLayout.events.StatusChangeEvent; import flashx.textLayout.external.WeakRef; import flashx.textLayout.formats.ITextLayoutFormat; import flashx.textLayout.formats.TextLayoutFormat; import flashx.textLayout.formats.TextLayoutFormatValueHolder; import flashx.textLayout.property.Property; import flashx.textLayout.tlf_internal; use namespace tlf_internal; /** * * @eventType flashx.textLayout.events.FlowOperationEvent.FLOW_OPERATION_BEGIN * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ [Event(name="flowOperationBegin", type="flashx.textLayout.events.FlowOperationEvent")] /** * * @eventType flashx.textLayout.events.FlowOperationEvent.FLOW_OPERATION_END * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ [Event(name="flowOperationEnd", type="flashx.textLayout.events.FlowOperationEvent")] /** * * @eventType flashx.textLayout.events.FlowOperationEvent.FLOW_OPERATION_COMPLETE * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ [Event(name="flowOperationComplete", type="flashx.textLayout.events.FlowOperationEvent")] /** Dispatched whenever the selection is changed. Primarily used to update selection-dependent user interface. * It can also be used to alter the selection, but cannot be used to alter the TextFlow itself. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ [Event(name="selectionChange", type="flashx.textLayout.events.SelectionEvent")] /** Dispatched after every recompose. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ [Event(name="compositionComplete", type="flashx.textLayout.events.CompositionCompleteEvent")] /** Dispatched when the mouse is pressed down over any link. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ [Event(name="mouseDown", type="flashx.textLayout.events.FlowElementMouseEvent")] /** Dispatched when the mouse is released over any link. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ [Event(name="mouseUp", type="flashx.textLayout.events.FlowElementMouseEvent")] /** Dispatched when the mouse passes over any link. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ [Event(name="mouseMove", type="flashx.textLayout.events.FlowElementMouseEvent")] /** Dispatched when the mouse first enters any link. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ [Event(name="rollOver", type="flashx.textLayout.events.FlowElementMouseEvent")] /** Dispatched when the mouse goes out of any link. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ [Event(name="rollOut", type="flashx.textLayout.events.FlowElementMouseEvent")] /** Dispatched when any link is clicked. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ [Event(name="click", type="flashx.textLayout.events.FlowElementMouseEvent")] /** Dispatched when a InlineGraphicElement is resized due to having width or height as auto or percent * and the graphic has finished loading. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ [Event(name="inlineGraphicStatusChanged", type="flashx.textLayout.events.StatusChangeEvent")] /** Dispatched by a TextFlow object after text is scrolled within a controller container. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ [Event(name="scroll", type="flashx.textLayout.events.TextLayoutEvent")] /** Dispatched by a TextFlow object each time it is damaged * * You can use this event to find out that the TextFlow has changed, but do not access the TextFlow itself when this event * is sent out. This event is sent when TextFlow changes are partially complete, so it can be in an inconsistent state: * some changes have been mad already, and other changes are still pending. Get the information you need from the event, and make * required changes after control returns to your application. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ [Event(name="damage", type="flashx.textLayout.events.DamageEvent")] /** Dispatched by a TextFlow object each time a container has had new DisplayObjects added or updated as a result of composition. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ [Event(name="updateComplete", type="flashx.textLayout.events.UpdateCompleteEvent")] /** * The TextFlow class is responsible for managing all the text content of a story. In TextLayout, text is stored in a * hierarchical tree of elements. TextFlow is the root object of the element tree. All elements on the tree * derive from the base class, FlowElement. * *
A TextFlow object can have ParagraphElement and DivElement objects as children. A div (DivElement object) * represents a group of paragraphs (ParagraphElement objects). A paragraph can have SpanElement, InlineGraphicElement, * LinkElement, and TCYElement objects as children.
* *A span (SpanElement) is a range of text in a paragraph that has the same attributes. An image
* (InlineGraphicElement) represents an arbitrary graphic that appears as a single character in a line of text. A
* LinkElement represents a hyperlink, or HTML a
tag, and it can contain multiple spans. A TCYElement object
* is used in Japanese text when there is a small run of text that appears perpendicular to the line, as in a horizontal
* run within a vertical line. A TCYElement also can contain multiple spans.
TextFlow also derives from the ContainerFormattedElement class, which is the root class for all container-level block * elements.
*The following illustration shows the relationship of other elements, such as spans and paragraphs, to the TextFlow * object.
* * *Each TextFlow object has a corresponding Configuration object that allows you to specify initial character and
* paragraph formats and the initial container format. It also allows you to specify attributes for selection, links,
* focus, and scrolling. When you supply a Configuration object as parameter to the TextFlow()
* constructor, it creates a read-only snapshot that you can access through the TextFlow.configuration
* property. After creation, you can't change the TextFlow's configuration. If you do not specify a Configuration, you
* can access the default configuration through the TextFlow.defaultConfiguration
property.
If you provide a config
parameter, the contents of the Configuration object are copied and
* you cannot make changes. You can access configuration settings, however, through the
* configuration
property. If the config
parameter is null, you can access the default
* configuration settings through the defaultConfiguration
property.
The Configuration object provides a mechanism for setting configurable default attributes on a TextFlow. * While you can't make changes to the Configuration object, you can override default attributes, if necessary, * by setting the attributes of TextFlow and its children.
* * @param config Specifies the configuration to use for this TextFlow object. If it's null, use *TextFlow.defaultConfiguration
to access configuration values.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see Configuration
* @see #configuration
* @see #defaultConfiguration
*
*/
public function TextFlow(config:IConfiguration = null)
{
super();
initializeForConstructor(config);
}
private function initializeForConstructor(config:IConfiguration):void
{
if (config == null)
config = defaultConfiguration;
// read only non changing copy of current state
_configuration = Configuration(config).getImmutableClone();
_eventDispatcher = new EventDispatcher(this);
format = _configuration.textFlowInitialFormat;
// initialize the flowComposer
if (_configuration.flowComposerClass)
flowComposer = new _configuration.flowComposerClass();
_generation = _nextGeneration++;
}
/** @private */
public override function shallowCopy(startPos:int = 0, endPos:int = -1):FlowElement
{
var retFlow:TextFlow = super.shallowCopy(startPos, endPos) as TextFlow;
retFlow._configuration = _configuration;
retFlow._generation = _nextGeneration++;
if (formatResolver)
retFlow.formatResolver = formatResolver.getResolverForNewFlow(this,retFlow);
// TODO: preserve the hostFormat??
// preserve the swfContext
if (retFlow.flowComposer && flowComposer)
retFlow.flowComposer.swfContext = flowComposer.swfContext;
return retFlow;
}
/**
* The Configuration object for this TextFlow object. The Configuration object specifies the initial character
* and paragraph formats, the initial container format, and attributes for selection highlighting,
* links, focus, and scrolling.
*
* If you do not specify a Configuration object, Text Layout Framework uses a default Configuration object, which
* is referenced by the defaultConfiguration
property.
Controls all selection and editing on the text. If the TextFlow is not selectable, * the interactionManager is null. To make the TextFlow editable, assign a interactionManager * that is both an ISelectionManager and an IEditManager. To make a TextFlow that is read-only * and allows selection, assign a interactionManager that is an ISelectionManager only.
* * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 * * @see flashx.textLayout.edit.ISelectionManager ISelectionManager * @see flashx.textLayout.edit.IEditManager IEditManager */ public function get interactionManager():ISelectionManager { return _interactionManager; } public function set interactionManager(newInteractionManager:ISelectionManager):void { // detatch old interactionManager if (_interactionManager != newInteractionManager) { if (_interactionManager) _interactionManager.textFlow = null; _interactionManager = newInteractionManager; if (_interactionManager) _interactionManager.textFlow = this; if (flowComposer) flowComposer.interactionManagerChanged(newInteractionManager); } } /** Manages the containers for this element. * *The TextLines that are created from the element appear as children of the container. * The flowComposer manages the containers, and as the text is edited it adds lines to and removes lines * from the containers. The flowComposer also keeps track of some critical attributes, such as the * width and height to compose to, whether scrolling is on, and so on.
* *The container and flowComposer
are closely related. If you reset flowComposer
,
* the container is reset to the new flowComposer's container. Likewise if the container is reset,
* flowComposer
is reset to the container's new flowComposer.
id
property matches the idName
parameter. Provides
* the ability to apply a style based on the id
.
*
* For example, the following line sets the style "color" to 0xFF0000 (red), for the
* element having the id
span1.
Note: In the following code, p.addChild(s)
removes s
* from its original parent and adds it to p
, the new parent.
id
value of the element to find.
*
* @return The element whose id matches idName
.
*
* @includeExample examples\TextFlow_getElementByIDExample.as -noswf
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see FlowElement#id
*/
public function getElementByID(idName:String):FlowElement
{
return getElementByIDHelper(idName);
}
/** Returns all elements that have styleName
set to styleNameValue
.
*
* @param styleNameValue The name of the style for which to find elements that have it set.
*
* @return An array of the elements whose styleName
value matches styleNameValue
. For example,
* all elements that have the style name "color".
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see FlowElement#styleName
*/
public function getElementsByStyleName(styleNameValue:String):Array
{
var a:Array = new Array;
getElementsByStyleNameHelper(a,styleNameValue);
return a;
}
/** @private */
override protected function get abstract():Boolean
{
return false;
}
/** @private */
tlf_internal override function updateLengths(startIdx:int,len:int,updateLines:Boolean):void
{
if (normalizeStart != -1)
{
var newNormalizeStart:int = startIdx < normalizeStart ? startIdx : normalizeStart;
if (newNormalizeStart < normalizeStart)
normalizeLen += (normalizeStart-newNormalizeStart);
normalizeLen += len;
normalizeStart = newNormalizeStart;
}
else
{
normalizeStart = startIdx;
normalizeLen = len;
}
// never go below zero
if (normalizeLen < 0)
normalizeLen = 0;
// fix the lines
if (updateLines && _flowComposer)
{
_flowComposer.updateLengths(startIdx,len);
super.updateLengths(startIdx,len, false);
}
else
super.updateLengths(startIdx,len,updateLines);
}
[RichTextContent]
/** @private NOTE: all FlowElement implementers and overrides of mxmlChildren must specify [RichTextContent] metadata */
public override function set mxmlChildren(array:Array):void
{
super.mxmlChildren = array;
normalize();
applyWhiteSpaceCollapse();
}
/** @private Update any elements that have a delayed updated. Normally used to stop and stop foreignelements when they
* are either displayed the first time or removed from the stage
*/
tlf_internal function applyUpdateElements(okToUnloadGraphics:Boolean):Boolean
{
if (_elemsToUpdate)
{
var hasController:Boolean = flowComposer && flowComposer.numControllers != 0;
for each (var child:FlowElement in _elemsToUpdate)
child.applyDelayedElementUpdate(this,okToUnloadGraphics,hasController);
_elemsToUpdate = null; // expect this array is transient
return true;
}
return false;
}
/** @private */
tlf_internal override function preCompose():void
{
// normalizes the flow
do
{
normalize();
}
// starts or stops any FEs that have been modified, removed or deleted
while (applyUpdateElements(true));
// need to call normalize again in case any of the element updates have modified the hierarchy
// normalize();
}
/**
* Mark the a range of text as invalid - needs to be recomposed.
* The text classes are self damaging. This is only used when modifying the container chain.
*Warning: Plan to evaulate a way to hide this method totally.
* @param start text index of first character to marked invalid * @param damageLen number of characters to mark invalid * @param needNormalize optional parameter (true is default) - normalize should include this range. * @private */ tlf_internal function damage(damageStart:int, damageLen:int, damageType:String, needNormalize:Boolean = true):void { CONFIG::debug { assert(damageLen > 0,"must have at least 1 char in damageLen"); } if (needNormalize) { if (normalizeStart == -1) { normalizeStart = damageStart; normalizeLen = damageLen; } else { if (damageStart < normalizeStart) { var newNormalizeLen:uint = normalizeLen; newNormalizeLen = normalizeStart+normalizeLen - damageStart; if (damageLen > newNormalizeLen) newNormalizeLen = damageLen; normalizeStart = damageStart; normalizeLen = newNormalizeLen; } else if ((normalizeStart+normalizeLen) > damageStart) { if (damageStart+damageLen > normalizeStart+normalizeLen) normalizeLen = damageStart+damageLen-normalizeStart; } else normalizeLen = damageStart+damageLen-normalizeStart; } // clamp to textLength CONFIG::debug { assert(normalizeStart <= textLength,"damage bad length"); } if (normalizeStart+normalizeLen > textLength) normalizeLen = textLength-normalizeStart; // trace("damage damageStart:" + damageStart.toString() + " damageLen:" + damageLen.toString() + " textLength:" + this.textLength + " normalizeStart:" + normalizeStart.toString() + " normalizeLen:" + normalizeLen.toString() ); } if (_flowComposer) _flowComposer.damage(damageStart, damageLen, damageType); if (hasEventListener(DamageEvent.DAMAGE)) dispatchEvent(new DamageEvent(DamageEvent.DAMAGE,false,false,this,damageStart,damageLen)); } /** * Find the paragraph at the specified absolute position * @private */ tlf_internal function findAbsoluteParagraph(pos:int):ParagraphElement { var elem:FlowElement = findLeaf(pos); return elem ? elem.getParagraph() : null; } /** * Find the FlowGroupElement at the absolute position, * could be synonymous with the paragraph OR a subBlockElement * @private */ tlf_internal function findAbsoluteFlowGroupElement(pos:int):FlowGroupElement { var elem:FlowElement = findLeaf(pos); return elem.parent; } /** @private */ CONFIG::debug public override function debugCheckFlowElement(depth:int = 0, extraData:String = ""):int { // debugging function that asserts if the flow element tree is in an invalid state var rslt:int = super.debugCheckFlowElement(depth,extraData); // describe the lines if (Debugging.verbose && flowComposer) { for ( var lineIdx:int = 0; lineIdx < flowComposer.numLines; lineIdx++) { var workLine:TextFlowLine = flowComposer.getLineAt(lineIdx); var containerIdx:int = flowComposer.getControllerIndex(workLine.controller); trace("line:",lineIdx,"controller:",containerIdx,workLine.toString()); } for (var idx:int = 0; idx < flowComposer.numControllers; idx++) { var controller:ContainerController = flowComposer.getControllerAt(idx); trace("controller:",idx,Debugging.getIdentity(controller),controller.absoluteStart,controller.textLength,controller.compositionWidth,controller.compositionHeight,controller.getContentBounds()); } } rslt += assert(parent == null, "TextFlow should not have a parent"); rslt += assert(parentRelativeStart == 0, "TextFlow start not zero"); return rslt; } /** * Check the internal consistency of the flow's FlowElement tree. * @private * Asserts if the data structures in the flow are invalid. */ CONFIG::debug public function debugCheckTextFlow(validateControllers:Boolean=true):int { var rslt:int = debugCheckFlowElement(); if (_flowComposer && validateControllers) { var idx:int; var endPrevController:int = 0; for (idx = 0; idx < flowComposer.numControllers; idx++) { var controller:ContainerController = flowComposer.getControllerAt(idx); if (Debugging.verbose) { trace("controller:",idx,"absoluteStart:",controller.absoluteStart,"textLength:",controller.textLength); } rslt += assert(controller.absoluteStart == endPrevController, "controller has bad start"); rslt += assert(controller.textLength >= 0, "controller has bad textLength"); endPrevController = controller.absoluteStart+controller.textLength; rslt += assert(endPrevController <= textLength, "textLength may not extend past end of root element!"); } } if (_flowComposer is StandardFlowComposer) rslt += StandardFlowComposer(_flowComposer).debugCheckTextFlowLines(validateControllers); return rslt; } /** * Called after an import to validate that the entire flow textLength needs normalize. * @private */ CONFIG::debug public function debugCheckNormalizeAll():void { assert(normalizeStart == 0,"normalizeStart: bad normailzeStart"); assert(normalizeLen == textLength,"debugCheckNormalizeAll: bad normalizeLen"); } /** * @copy flash.events.IEventDispatcher#addEventListener() * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false): void { _eventDispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference); } /** * @copy flash.events.IEventDispatcher#dispatchEvent() * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function dispatchEvent(event:Event):Boolean { return _eventDispatcher.dispatchEvent(event); } /** * @copy flash.events.IEventDispatcher#hasEventListener() * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function hasEventListener(type:String):Boolean { return _eventDispatcher.hasEventListener(type); } /** * @copy flash.events.IEventDispatcher#removeEventListener(). * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false): void { _eventDispatcher.removeEventListener(type, listener, useCapture); } /** * @copy flash.events.IEventDispatcher#willTrigger() * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function willTrigger(type:String):Boolean { return _eventDispatcher.willTrigger(type); } // elements which are images or blocks that *may* have children requiring activation or deactivation private var _elemsToUpdate:Array; /** @private */ tlf_internal function appendOneElementForUpdate(elem:FlowElement):void { if (_elemsToUpdate == null) _elemsToUpdate = [ elem ]; else _elemsToUpdate.push(elem); } /** @private */ tlf_internal function mustUseComposer():Boolean { if (_elemsToUpdate == null || _elemsToUpdate.length == 0) return false; normalize(); // anything that doesn't normalize completely forces use of the compser var rslt:Boolean = false; for each (var elem:FlowElement in _elemsToUpdate) { if (elem.updateForMustUseComposer(this)) rslt = true; } return rslt; } /** @private */ tlf_internal function processModelChanged(changeType:String, elem:FlowElement, changeStart:int, changeLen:int, needNormalize:Boolean, bumpGeneration:Boolean):void { // track added and removed elements while the flow is visible if (flowComposer) elem.appendElementsForDelayedUpdate(this); if (bumpGeneration) _generation = _nextGeneration++; if (changeLen > 0) damage(changeStart+elem.getAbsoluteStart(), changeLen, TextLineValidity.INVALID, needNormalize); if (formatResolver) { switch(changeType) { case ModelChange.ELEMENT_REMOVAL: case ModelChange.ELEMENT_ADDED: case ModelChange.STYLE_SELECTOR_CHANGED: formatResolver.invalidate(elem); elem.formatChanged(false); break; } } } /** * The generation number for this TextFlow object. The undo and redo operations use the generation number to validate that * it's legal to undo or redo an operation. The generation numbers must match. * *Each model change increments generation
so if the generation number changes, you know the
* TextFlow model has changed.
hostFormat
.
* For example, the following lines do not set the font size to 24 because
* the font size is set after the TextLayoutFormat object has been assigned to hostFormat
.
*
*