blob: 6ef50530a5f85df049bc4821c139b0b92f6da5ea [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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.display.DisplayObjectContainer;
import flash.events.IEventDispatcher;
import flash.utils.Dictionary;
import flash.utils.getDefinitionByName;
import flash.utils.getQualifiedClassName;
import flashx.textLayout.compose.IFlowComposer;
import flashx.textLayout.container.ContainerController;
import flashx.textLayout.debug.Debugging;
import flashx.textLayout.debug.assert;
import flashx.textLayout.events.ModelChange;
import flashx.textLayout.formats.FormatValue;
import flashx.textLayout.formats.ITextLayoutFormat;
import flashx.textLayout.formats.ListMarkerFormat;
import flashx.textLayout.formats.TextLayoutFormat;
import flashx.textLayout.property.Property;
import flashx.textLayout.tlf_internal;
use namespace tlf_internal;
[IMXMLObject]
/**
* The text in a flow is stored in tree form with the elements of the tree representing logical
* divisions within the text. The FlowElement class is the abstract base class of all the objects in this tree.
* FlowElement objects represent paragraphs, spans of text within paragraphs, and
* groups of paragraphs.
*
* <p>The root of a composable FlowElement tree is always a TextFlow object. Leaf elements of the tree are always
* subclasses of the FlowLeafElement class. All leaves arranged in a composable TextFlow have a ParagraphElement ancestor.
* </p>
*
* <p>You cannot create a FlowElement object directly. Invoking <code>new FlowElement()</code> throws an error
* exception.</p>
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see FlowGroupElement
* @see FlowLeafElement
* @see InlineGraphicElement
* @see ParagraphElement
* @see SpanElement
* @see TextFlow
*/
public class FlowElement implements ITextLayoutFormat
{
/** every FlowElement has at most one parent */
private var _parent:FlowGroupElement;
/** format settings on this FlowElement. @private */
private var _format:FlowValueHolder;
/** @private computed formats applied to the element */
protected var _computedFormat:TextLayoutFormat;
/** start, _start of element, relative to parent; tlf_internal to eliminate getter calls */
private var _parentRelativeStart:int = 0;
/** _textLength (number of chars) in the element, including child content; tlf_internal to eliminate getter calls */
private var _textLength:int = 0;
/** Base class - invoking <code>new FlowElement()</code> throws an error exception.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function FlowElement()
{
// not a valid FlowElement class
if (abstract)
throw new Error(GlobalSettings.resourceStringFunction("invalidFlowElementConstruct"));
}
/** Called for MXML objects after the implementing object has been created and all component properties specified on the MXML tag have been initialized.
* @param document The MXML document that created the object.
* @param id The identifier used by document to refer to this object.
**/
public function initialized(document:Object, id:String):void
{
this.id = id;
}
/** Returns true if the class is an abstract base class,
* false if its OK to construct. Attempting to instantiate an
* abstract FlowElement class will cause an exception.
* @return Boolean true if this is an abstract class
* @private
*/
protected function get abstract():Boolean
{
return true;
}
/** Allows you to read and write user styles on a FlowElement object. Note that reading this property
* makes a copy of the userStyles set in the format of this element.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
*/
public function get userStyles():Object
{ return _format ? _format.userStyles : null; }
public function set userStyles(styles:Object):void
{
var val:String;
// clear the existing userstyles
for (val in userStyles)
this.setStyle(val,undefined);
// set the new ones
for (val in styles)
{
if (!TextLayoutFormat.description.hasOwnProperty(val))
this.setStyle(val,styles[val]);
}
}
public function setBorderWidth(width:Number):void
{
this.borderBottomWidth = width;
this.borderLeftWidth = width;
this.borderRightWidth = width;
this.borderTopWidth = width;
}
public function setBorderColor(color:uint):void
{
this.borderBottomColor = color;
this.borderLeftColor = color;
this.borderRightColor = color;
this.borderTopColor = color;
}
/** Returns the <code>coreStyles</code> on this FlowElement. Note that the getter makes a copy of the core
* styles dictionary. The coreStyles object encapsulates the formats that are defined by TextLayoutFormat and are in TextLayoutFormat.description. The
* <code>coreStyles</code> object consists of an array of <em>stylename-value</em> pairs.
*
* @see flashx.textLayout.formats.TextLayoutFormat
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get coreStyles():Object
{ return _format ? _format.coreStyles : null; }
/** Returns the styles on this FlowElement. Note that the getter makes a copy of the
* styles dictionary. The returned object encapsulates all styles set in the format property including core and user styles. The
* returned object consists of an array of <em>stylename-value</em> pairs.
*
* @see flashx.textLayout.formats.TextLayoutFormat
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get styles():Object
{ return _format ? _format.styles : null; }
/** @private */
tlf_internal function setStylesInternal(styles:Object):void
{
if (styles)
writableTextLayoutFormat().setStyles(Property.shallowCopy(styles),false);
else if (_format)
_format.clearStyles();
formatChanged();
}
/** Compare the userStyles of this with otherElement's userStyles.
*
* @param otherElement the FlowElement object with which to compare user styles
*
* @return true if the user styles are equal; false otherwise.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @includeExample examples\FlowElement_equalUserStylesExample.as -noswf
*
* @see #getStyle()
* @see #setStyle()
* @see #userStyles
*/
public function equalUserStyles(otherElement:FlowElement):Boolean
{
return Property.equalStyles(this.userStyles,otherElement.userStyles,null);
}
/** @private Compare the styles of two elements for merging. Return true if they can be merged. */
tlf_internal function equalStylesForMerge(elem:FlowElement):Boolean
{
return this.id == elem.id && this.typeName == elem.typeName && TextLayoutFormat.isEqual(elem.format,format);
}
/**
* Makes a copy of this FlowElement object, copying the content between two specified character positions.
* It returns the copy as a new FlowElement object. Unlike <code>deepCopy()</code>, <code>shallowCopy()</code> does
* not copy any of the children of this FlowElement object.
*
* <p>With no arguments, <code>shallowCopy()</code> defaults to copying all of the content.</p>
*
* @param relativeStart The relative text position of the first character to copy. First position is 0.
* @param relativeEnd The relative text position of the last character to copy. A value of -1 indicates copy to end.
*
* @return the object created by the copy operation.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @includeExample examples\FlowElement_shallowCopyExample.as -noswf
*
* @see #deepCopy()
*/
public function shallowCopy(relativeStart:int = 0, relativeEnd:int = -1):FlowElement
{
var retFlow:FlowElement = new (getDefinitionByName(getQualifiedClassName(this)) as Class);
if (_format != null)
retFlow._format = new FlowValueHolder(_format);
CONFIG::debug { assert(retFlow.styleName == styleName,"Failed top copy styleName"); }
CONFIG::debug { assert(retFlow.typeName == typeName,"Failed top copy typeName"); }
CONFIG::debug { assert(retFlow.id == id,"Failed top copy id"); }
return retFlow;
}
/**
* Makes a deep copy of this FlowElement object, including any children, copying the content between the two specified
* character positions and returning the copy as a FlowElement object.
*
* <p>With no arguments, <code>deepCopy()</code> defaults to copying the entire element.</p>
*
* @param relativeStart relative text position of the first character to copy. First position is 0.
* @param relativeEnd relative text position of the last character to copy. A value of -1 indicates copy to end.
*
* @return the object created by the deep copy operation.
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @includeExample examples\FlowElement_deepCopyExample.as -noswf
*
* @see #shallowCopy()
*/
public function deepCopy(relativeStart:int = 0, relativeEnd:int = -1):FlowElement
{
if (relativeEnd == -1)
relativeEnd = _textLength;
return shallowCopy(relativeStart, relativeEnd);
}
/**
* Gets the specified range of text from a flow element.
*
* @param relativeStart The starting position of the range of text to be retrieved, relative to the start of the FlowElement
* @param relativeEnd The ending position of the range of text to be retrieved, relative to the start of the FlowElement, -1 for up to the end of the element
* @param paragraphSeparator character to put between paragraphs
*
* @return The requested text.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function getText(relativeStart:int=0, relativeEnd:int=-1, paragraphSeparator:String="\n"):String
{
return "";
}
/**
* Splits this FlowElement object at the position specified by the <code>relativePosition</code> parameter, which is
* a relative position in the text for this element. This method splits only SpanElement and FlowGroupElement
* objects.
*
* @param relativePosition the position at which to split the content of the original object, with the first position being 0.
*
* @return the new object, which contains the content of the original object, starting at the specified position.
*
* @throws RangeError if <code>relativePosition</code> is greater than <code>textLength</code>, or less than 0.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function splitAtPosition(relativePosition:int):FlowElement
{
if ((relativePosition < 0) || (relativePosition > _textLength))
throw RangeError(GlobalSettings.resourceStringFunction("invalidSplitAtPosition"));
return this;
}
/** @private Set to indicate the element may be "bound" in Flex - only used on FlowLeafElement and SubParagraphBlock elements. */
tlf_internal function get bindableElement():Boolean
{ return getPrivateStyle("bindable") == true; }
/** @private */
tlf_internal function set bindableElement(value:Boolean):void
{ setPrivateStyle("bindable", value); }
/** Merge flow element into previous flow element if possible. @private
* @Return true--> did the merge
*/
tlf_internal function mergeToPreviousIfPossible():Boolean
{
return false;
}
/** @private
* Create and initialize the FTE data structure that corresponds to the FlowElement
*/
tlf_internal function createContentElement():void
{
// overridden in the base class -- we should not get here
CONFIG::debug { assert(false,"invalid call to createContentElement"); }
}
/** @private
* Release the FTE data structure that corresponds to the FlowElement, so it can be gc'ed
*/
tlf_internal function releaseContentElement():void
{
// overridden in the base class -- we should not get here
CONFIG::debug { assert(false,"invalid call to createContentElement"); }
}
/** Returns the parent of this FlowElement object. Every FlowElement has at most one parent.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get parent():FlowGroupElement
{
// must be final to prevent overrides for safe internal access to _parent
return _parent;
}
/** @private parent setter. */
tlf_internal function setParentAndRelativeStart(newParent:FlowGroupElement,newStart:int):void
{ _parent = newParent; _parentRelativeStart = newStart; attributesChanged(false); }
/** @private Used when the textBlock.content is already correctly configured. */
tlf_internal function setParentAndRelativeStartOnly(newParent:FlowGroupElement,newStart:int):void
{ _parent = newParent; _parentRelativeStart = newStart; }
/**
* Returns the total length of text owned by this FlowElement object and its children. If an element has no text, the
* value of <code>textLength</code> is usually zero.
*
* <p>ParagraphElement objects have a final span with a paragraph terminator character for the last
* SpanElement object.The paragraph terminator is included in the value of the <code>textLength</code> of that
* SpanElement object and all its parents. It is not included in <code>text</code> property of the SpanElement
* object.</p>
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see #textLength
*/
public function get textLength():int
{ return _textLength; }
/** @private textLength setter. */
tlf_internal function setTextLength(newLength:int):void
{ _textLength = newLength; }
/** Returns the relative start of this FlowElement object in the parent. If parent is null, this value is always zero.
* If parent is not null, the value is the sum of the text lengths of all previous siblings.
*
* @return offset
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see #textLength
*/
public function get parentRelativeStart():int
{
// make final to prevent overrides and enable direct internal read access
return _parentRelativeStart;
}
/** @private start setter. */
tlf_internal function setParentRelativeStart(newStart:int):void
{ _parentRelativeStart = newStart; }
/** Returns the relative end of this FlowElement object in the parent. If the parent is null this is always equal to <code>textLength</code>. If
* the parent is not null, the value is the sum of the text lengths of this and all previous siblings, which is effectively
* the first character in the next FlowElement object.
*
* @return offset
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see FlowGroupElement
* @see #textLength
*/
public function get parentRelativeEnd():int
{ return _parentRelativeStart + _textLength; }
/**
* Tag for the item, used for debugging.
*/
CONFIG::debug public function get name():String
{
return flash.utils.getQualifiedClassName(this);
}
/** Returns the ContainerFormattedElement that specifies its containers for filling. This ContainerFormattedElement
* object has its own columns and can control ColumnDirection and BlockProgression.
*
* @return the ancestor, with its container, of this FlowElement object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @private
*/
tlf_internal function getAncestorWithContainer():ContainerFormattedElement
{
var elem:FlowElement = this;
while (elem)
{
var contElement:ContainerFormattedElement = elem as ContainerFormattedElement;
if (contElement)
{
if (!contElement._parent || contElement.flowComposer)
return contElement;
}
elem = elem._parent;
}
return null;
}
/**
* @private
* Generic mechanism for fetching private data from the element.
* @param styleName name of the property
*/
tlf_internal function getPrivateStyle(styleName:String):*
{ return _format ? _format.getPrivateData(styleName) : undefined; }
/**
* @private
* Generic mechanism for adding private data to the element.
* @param styleName name of the property
* @param val value of the property
*/
tlf_internal function setPrivateStyle(styleName:String, val:*):void
{
if (getPrivateStyle(styleName) != val)
{
writableTextLayoutFormat().setPrivateData(styleName,val);
modelChanged(ModelChange.STYLE_SELECTOR_CHANGED,this,0,this._textLength);
}
}
private static const idString:String ="id";
/**
* Assigns an identifying name to the element, making it possible to set a style for the element
* by referencing the <code>id</code>. For example, the following line sets the color for
* a SpanElement object that has an id of span1:
*
* <listing version="3.0" >
* textFlow.getElementByID("span1").setStyle("color", 0xFF0000);
* </listing>
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see TextFlow#getElementByID()
*/
public function get id():String
{ return getPrivateStyle(idString); }
public function set id(val:String):void
{ return setPrivateStyle(idString, val); }
private static const typeNameString:String ="typeName";
/**
* Each FlowElement has a <code>typeName</code>. <code>typeName</code> defaults to the string the <code>textLayoutFormat</code> TextConverter uses. This API
* can be used to set a different <code>typeName</code> to a <code>FlowElement</code>. Typically this is done to support <code>type</code> selectors in CSS.
*
* <p>See the <code>TEXT_FIELD_HTML_FORMAT</code> documentation for how this used..</p>
*
* @see flashx.textLayout.conversion.TextConverter
* @see flashx.textLayout.conversion.TextConverter#TEXT_FIELD_HTML_FORMAT
* @see flashx.textLayout.conversion.IHTMLImporter
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get typeName():String
{
var typeName:String = getPrivateStyle(typeNameString);
return typeName ? typeName : defaultTypeName;
}
public function set typeName(val:String):void
{
// extra testing here to avoid saving defaultTypeName in privateStyles
if (val != typeName)
setPrivateStyle(typeNameString, val == defaultTypeName ? undefined : val);
}
/** @private */
tlf_internal function get defaultTypeName():String
{ return null; }
private static const impliedElementString:String = "impliedElement";
/** @private */
tlf_internal function get impliedElement():Boolean
{
return getPrivateStyle(impliedElementString) !== undefined;
}
/** @private */
tlf_internal function set impliedElement(value:*):void
{
CONFIG::debug { assert(value === true || value === undefined,"Bad value to FlowElement.impliedElement"); }
setPrivateStyle(impliedElementString, value);
}
// ****************************************
// Begin TLFFormat Related code
// ****************************************
include "../formats/TextLayoutFormatInc.as"
/** TextLayoutFormat properties applied directly to this element.
* <p>Each element may have properties applied to it as part of its format. Properties applied to this element override properties inherited from the parent. Properties applied to this element will in turn be inherited by element's children if they are not overridden on the child. If no properties are applied to the element, this will be null.</p>
* @see flashx.textLayout.formats.ITextLayoutFormat
*/
public function get format():ITextLayoutFormat
{ return _format; }
public function set format(value:ITextLayoutFormat):void
{
if (value == _format)
return;
var oldStyleName:String = this.styleName;
if (value == null)
_format.clearStyles();
else
writableTextLayoutFormat().copy(value);
formatChanged();
if (oldStyleName != this.styleName)
styleSelectorChanged();
}
/** @private */
tlf_internal function writableTextLayoutFormat():FlowValueHolder
{
if (_format == null)
_format = new FlowValueHolder();
return _format;
}
/** This gets called when an element has changed its attributes. This may happen because an
* ancestor element changed it attributes.
* @private
*/
tlf_internal function formatChanged(notifyModelChanged:Boolean = true):void
{
if (notifyModelChanged)
modelChanged(ModelChange.TEXTLAYOUT_FORMAT_CHANGED,this,0,_textLength);
// require recompute of computedFormat
_computedFormat = null;
}
/** This gets called when an element has changed its style selection related attributes. This may happen because an
* ancestor element changed it attributes.
* @private
*/
tlf_internal function styleSelectorChanged():void
{
modelChanged(ModelChange.STYLE_SELECTOR_CHANGED,this,0,this._textLength);
_computedFormat = null;
}
/**
* Concatenates tlf attributes with any other tlf attributes
*
* Return the concatenated result
*
* @private
*/
tlf_internal function get formatForCascade():ITextLayoutFormat
{
var tf:TextFlow = getTextFlow();
if (tf)
{
var elemStyle:TextLayoutFormat = tf.getTextLayoutFormatStyle(this);
var explicitStyle:TextLayoutFormat = tf.getExplicitStyle(this);
if (elemStyle || explicitStyle)
{
var localFormat:ITextLayoutFormat = format;
var rslt:TextLayoutFormat = new TextLayoutFormat();
if (elemStyle)
rslt.apply(elemStyle);
if (localFormat)
rslt.apply(localFormat);
if (explicitStyle)
rslt.apply(explicitStyle);
return rslt;
}
}
return _format;
}
/** @private Shared scratch element for use in computedFormat methods only */
static tlf_internal var _scratchTextLayoutFormat:TextLayoutFormat = new TextLayoutFormat();
/**
* Returns the computed format attributes that are in effect for this element.
* Takes into account the inheritance of attributes from parent elements.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flashx.textLayout.formats.ITextLayoutFormat ITextLayoutFormat
*/
public function get computedFormat():ITextLayoutFormat
{
if (_computedFormat == null)
_computedFormat = doComputeTextLayoutFormat();
return _computedFormat;
}
/** @private */
tlf_internal function doComputeTextLayoutFormat():TextLayoutFormat
{
var parentPrototype:TextLayoutFormat = _parent ? TextLayoutFormat(_parent.computedFormat): null;
return FlowElement.createTextLayoutFormatPrototype(formatForCascade,parentPrototype);
}
// ****************************************
// End CharacterFormat Related code
// ****************************************
/** @private */
tlf_internal function attributesChanged(notifyModelChanged:Boolean = true):void
{
// TODO: REMOVE ME???
formatChanged(notifyModelChanged);
}
/** Returns the value of the style specified by the <code>styleProp</code> parameter, which specifies
* the style name and can include any user style name. Accesses an existing span, paragraph, text flow,
* or container style. Searches the parent tree if the style's value is <code>undefined</code> on a
* particular element.
*
* @param styleProp The name of the style whose value is to be retrieved.
*
* @return The value of the specified style. The type varies depending on the type of the style being
* accessed. Returns <code>undefined</code> if the style is not set.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @includeExample examples\FlowElement_getStyleExample.as -noswf
*
* @see #clearStyle()
* @see #setStyle()
* @see #userStyles
*/
public function getStyle(styleProp:String):*
{
if (TextLayoutFormat.description.hasOwnProperty(styleProp))
return computedFormat.getStyle(styleProp);
var tf:TextFlow = getTextFlow();
if (!tf || !tf.formatResolver)
return computedFormat.getStyle(styleProp);
return getUserStyleWorker(styleProp);
}
// { return TextLayoutFormat.description.hasOwnProperty(styleProp) ? computedFormat.getStyle(styleProp) : getUserStyleWorker(styleProp); } }
/** @private worker function - any styleProp */
tlf_internal function getUserStyleWorker(styleProp:String):*
{
// CONFIG::debug { assert(TextLayoutFormat.description[styleProp] === undefined,"bad call to getUserStyleWorker"); }
if (_format != null)
{
var userStyle:* = _format.getStyle(styleProp)
if (userStyle !== undefined)
return userStyle;
}
var tf:TextFlow = getTextFlow();
if (tf && tf.formatResolver)
{
userStyle = tf.formatResolver.resolveUserFormat(this,styleProp);
if (userStyle !== undefined)
return userStyle;
}
// TODO: does TextFlow need to ask a "psuedo parent"
return _parent ? _parent.getUserStyleWorker(styleProp) : undefined;
}
/** Sets the style specified by the <code>styleProp</code> parameter to the value specified by the
* <code>newValue</code> parameter. You can set a span, paragraph, text flow, or container style, including
* any user name-value pair.
*
* <p><strong>Note:</strong> If you assign a custom style, Text Layout Framework can import and export it but
* compiled MXML cannot support it.</p>
*
* @param styleProp The name of the style to set.
* @param newValue The value to which to set the style.
*.
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @includeExample examples\FlowElement_setStyleExample.as -noswf
*
* @see #clearStyle()
* @see #getStyle()
* @see #userStyles
*/
public function setStyle(styleProp:String,newValue:*):void
{
if (TextLayoutFormat.description[styleProp])
this[styleProp] = newValue;
else
{
writableTextLayoutFormat().setStyle(styleProp,newValue);
formatChanged();
}
}
/** Clears the style specified by the <code>styleProp</code> parameter from this FlowElement object. Sets
* the value to <code>undefined</code>.
*
* @param styleProp The name of the style to clear.
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @includeExample examples\FlowElement_clearStyleExample.as -noswf
*
* @see #getStyle()
* @see #setStyle()
* @see #userStyles
*
*/
public function clearStyle(styleProp:String):void
{ setStyle(styleProp,undefined); }
/**
* Called whenever the model is modified. Updates the TextFlow and notifies the selection manager - if it is set.
* This method has to be called while the element is still in the flow
* @param changeType - type of change
* @param element - the actual element that is modified
* @param start - elem relative offset of start of damage
* @param len - length of damage
* @see flow.model.ModelChange
* @private
*/
tlf_internal function modelChanged(changeType:String, element:FlowElement, changeStart:int, changeLen:int, needNormalize:Boolean = true, bumpGeneration:Boolean = true):void
{
var tf:TextFlow = this.getTextFlow();
if (tf)
tf.processModelChanged(changeType, element, getAbsoluteStart()+changeStart, changeLen, needNormalize, bumpGeneration);
}
/** @private */
tlf_internal function appendElementsForDelayedUpdate(tf:TextFlow,changeType:String):void
{ }
/** @private */
tlf_internal function applyDelayedElementUpdate(textFlow:TextFlow,okToUnloadGraphics:Boolean,hasController:Boolean):void
{ }
/** @private */
tlf_internal function getEffectivePaddingLeft():Number
{ return computedFormat.paddingLeft == FormatValue.AUTO ? 0 : computedFormat.paddingLeft; }
/** @private */
tlf_internal function getEffectivePaddingRight():Number
{ return computedFormat.paddingRight == FormatValue.AUTO ? 0 : computedFormat.paddingRight; }
/** @private */
tlf_internal function getEffectivePaddingTop():Number
{ return computedFormat.paddingTop == FormatValue.AUTO ? 0 : computedFormat.paddingTop; }
/** @private */
tlf_internal function getEffectivePaddingBottom():Number
{ return computedFormat.paddingBottom == FormatValue.AUTO ? 0 : computedFormat.paddingBottom; }
/** @private */
tlf_internal function getEffectiveBorderLeftWidth():Number
{ return computedFormat.borderLeftWidth; }
/** @private */
tlf_internal function getEffectiveBorderRightWidth():Number
{ return computedFormat.borderRightWidth; }
/** @private */
tlf_internal function getEffectiveBorderTopWidth():Number
{ return computedFormat.borderTopWidth; }
/** @private */
tlf_internal function getEffectiveBorderBottomWidth():Number
{ return computedFormat.borderBottomWidth; }
/** @private */
tlf_internal function getEffectiveMarginLeft():Number
{ return computedFormat.marginLeft; }
/** @private */
tlf_internal function getEffectiveMarginRight():Number
{ return computedFormat.marginRight; }
/** @private */
tlf_internal function getEffectiveMarginTop():Number
{ return computedFormat.marginTop; }
/** @private */
tlf_internal function getEffectiveMarginBottom():Number
{ return computedFormat.marginBottom; }
// ****************************************
// Begin tracking property Related code
// ****************************************
/**
* Sets the tracking and is synonymous with the <code>trackingRight</code> property. Specified as a number of
* pixels or a percent of <code>fontSize</code>.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see #trackingRight
*/
public function set tracking(trackingValue:Object):void
{
trackingRight = trackingValue;
}
// ****************************************
// End tracking property Related code
// ****************************************
// ****************************************
// Begin import helper code
// ****************************************
/** Strips white space from the element and its children, according to the whitespaceCollaspse
* value that has been applied to the element or inherited from its parent.
* If a FlowLeafElement's attrs are set to WhiteSpaceCollapse.PRESERVE, then collapse is
* skipped.
* @see text.formats.WhiteSpaceCollapse
* @private
*/
tlf_internal function applyWhiteSpaceCollapse(collapse:String):void
{
// clear it if its set
if (whiteSpaceCollapse !== undefined)
whiteSpaceCollapse = undefined;
setPrivateStyle(impliedElementString, undefined);
}
// ****************************************
// End import helper code
// ****************************************
// ****************************************
// Begin tree navigation code
// ****************************************
/**
* Returns the start location of the element in the text flow as an absolute index. The first character in the flow
* is position 0.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @includeExample examples\FlowElement_getAbsoluteStartExample.as -noswf
*
* @return The index of the start of the element from the start of the TextFlow object.
*
* @see #parentRelativeStart
* @see TextFlow
*/
public function getAbsoluteStart():int
{
var rslt:int = _parentRelativeStart;
for (var elem:FlowElement = _parent; elem; elem = elem._parent)
rslt += elem._parentRelativeStart;
return rslt;
}
/**
* Returns the start of this element relative to an ancestor element. Assumes that the
* ancestor element is in the parent chain. If the ancestor element is the parent, this is the
* same as <code>this.parentRelativeStart</code>. If the ancestor element is the grandparent, this is the same as
* <code>parentRelativeStart</code> plus <code>parent.parentRelativeStart</code> and so on.
*
* @param ancestorElement The element from which you want to find the relative start of this element.
*
* @return the offset of this element.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @includeExample examples\FlowElement_getElementRelativeStartExample.as -noswf
*
* @see #getAbsoluteStart()
*/
public function getElementRelativeStart(ancestorElement:FlowElement):int
{
var rslt:int = _parentRelativeStart;
for (var elem:FlowElement = _parent; elem && elem != ancestorElement; elem = elem._parent)
rslt += elem._parentRelativeStart;
return rslt;
}
/**
* Climbs the text flow hierarchy to return the root TextFlow object for the element.
*
* @return The root TextFlow object for this FlowElement object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @includeExample examples\FlowElement_getTextFlowExample.as -noswf
*
* @see TextFlow
*/
public function getTextFlow():TextFlow
{
// find the root element entry and either return null or the containing textFlow
var elem:FlowElement = this;
while (elem._parent != null)
elem = elem._parent;
return elem as TextFlow;
}
/**
* Returns the ParagraphElement object associated with this element. It looks up the text flow hierarchy and returns
* the first ParagraphElement object.
*
* @return the ParagraphElement object that's associated with this FlowElement object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @includeExample examples\FlowElement_getParagraphExample.as -noswf
*
* @see #getTextFlow()
* @see ParagraphElement
*/
public function getParagraph():ParagraphElement
{
var para:ParagraphElement = null;
var rslt:FlowElement = this;
while (rslt)
{
para = rslt as ParagraphElement;
if (para)
break;
rslt = rslt._parent;
}
return para;
}
/**
* Returns the FlowElement object that contains this FlowElement object, if this element is contained within
* an element of a particular type.
*
* Returns the FlowElement it is contained in. Otherwise, it returns null.
*
* @private
*
* @param elementType type of element for which to check
*/
public function getParentByType(elementType:Class):FlowElement
{
var curElement:FlowElement = _parent;
while (curElement)
{
if (curElement is elementType)
return curElement;
curElement = curElement._parent;
}
return null;
}
/** Returns the previous FlowElement sibling in the text flow hierarchy.
*
* @includeExample examples\FlowElement_getPreviousSiblingExample.as -noswf
*
* @return the previous FlowElement object of the same type, or null if there is no previous sibling.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see #getNextSibling()
*/
public function getPreviousSibling():FlowElement
{
//this can happen if FE is on the scrap and doesn't have a parent. - gak 06.25.08
if(!_parent)
return null;
var idx:int = _parent.getChildIndex(this);
return idx == 0 ? null : _parent.getChildAt(idx-1);
}
/** Returns the next FlowElement sibling in the text flow hierarchy.
*
* @return the next FlowElement object of the same type, or null if there is no sibling.
*
* @includeExample examples\FlowElement_getNextSiblingExample.as -noswf
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see #getPreviousSibling()
*/
public function getNextSibling():FlowElement
{
if (!_parent)
return null;
var idx:int = _parent.getChildIndex(this);
return idx == _parent.numChildren-1 ? null : _parent.getChildAt(idx+1);
}
/**
* Returns the character at the specified position, relative to this FlowElement object. The first
* character is at relative position 0.
*
* @param relativePosition The relative position of the character in this FlowElement object.
* @return String containing the character.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @includeExample examples\FlowElement_getCharAtPositionExample.as -noswf
*
* @see #getCharCodeAtPosition()
*/
public function getCharAtPosition(relativePosition:int):String
{
return null;
}
/** Returns the character code at the specified position, relative to this FlowElement. The first
* character is at relative position 0.
*
* @param relativePosition The relative position of the character in this FlowElement object.
*
* @return the Unicode value for the character at the specified position.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @includeExample examples\FlowElement_getCharCodeAtPositionExample.as -noswf
*
* @see #getCharAtPosition()
*/
public function getCharCodeAtPosition(relativePosition:int):int
{
var str:String = getCharAtPosition(relativePosition);
return str && str.length > 0 ? str.charCodeAt(0) : 0;
}
/** @private apply function to all elements until it says stop */
tlf_internal function applyFunctionToElements(func:Function):Boolean
{ return func(this); }
/** @private
* Gets the EventDispatcher associated with this FlowElement. Use the functions
* of EventDispatcher such as <code>setEventHandler()</code> and <code>removeEventHandler()</code>
* to capture events that happen over this FlowLeafElement object. The
* event handler that you specify will be called after this FlowElement object does
* the processing it needs to do. If the FlowElement cannot dispatch events, the return
* value is null.
*
* Note that the event dispatcher will only dispatch FlowElementMouseEvent events.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.events.EventDispatcher
* @see flashx.textLayout.events.FlowElementMouseEvent
*/
tlf_internal function getEventMirror():IEventDispatcher
{
return null;
}
/** @private
* Checks whether an event dispatcher is attached, and if so, if the event dispatcher
* has any active listeners.
*/
tlf_internal function hasActiveEventMirror():Boolean
{
return false;
}
// ****************************************
// End tree navigation code
// ****************************************
// ****************************************
// Begin tree modification support code
// ****************************************
/**
* Update the FlowElement to account for text added before it.
*
* @param len number of characters added (may be negative if deletion)
*/
private function updateRange(len:int): void
{
setParentRelativeStart(_parentRelativeStart + len);
}
/** Update the FlowElements in response to an insertion or deletion.
* The length of the element inserted to is updated, and the length of
* each of its ancestor element. Each of the elements following siblings
* start index is updated (start index is relative to parent).
* @private
* @param startIdx absolute index in flow where text was inserted
* @param len number of characters added (negative if removed)
* updateLines ?? true if lines should be damaged??
* @private
*/
tlf_internal function updateLengths(startIdx:int,len:int,updateLines:Boolean):void
{
setTextLength(_textLength + len);
var p:FlowGroupElement = _parent;
if (p)
{
// update the elements following this in parent's children
var idx:int = p.getChildIndex(this)+1;
CONFIG::debug { assert(idx != -1,"bad parent in FlowElement.updateLengths"); }
var pElementCount:int = p.numChildren;
while (idx < pElementCount)
{
var child:FlowElement = p.getChildAt(idx++);
child.updateRange(len);
}
// go up the tree to the root and update ancestor's lengths
p.updateLengths(startIdx,len,updateLines);
}
}
/** @private */
tlf_internal function getEnclosingController(relativePos:int) : ContainerController
{
// CONFIG::debug { assert(pos <= length,"getEnclosingController - bad pos"); }
var textFlow:TextFlow = getTextFlow();
//more than likely, we are building up spans for the very first time.
//the container has not yet been created
if (textFlow == null || textFlow.flowComposer == null)
return null;
var curItem:FlowElement = this;
while (curItem && (!(curItem is ContainerFormattedElement) || ContainerFormattedElement(curItem).flowComposer == null))
{
curItem = curItem._parent;
}
var flowComposer:IFlowComposer = ContainerFormattedElement(curItem).flowComposer;
if (!flowComposer)
return null;
var controllerIndex:int = ContainerFormattedElement(curItem).flowComposer.findControllerIndexAtPosition(getAbsoluteStart() + relativePos,false);
return controllerIndex != -1 ? flowComposer.getControllerAt(controllerIndex) : null;
}
/** @private */
tlf_internal function deleteContainerText(endPos:int,deleteTotal:int):void
{
// update container counts
if (getTextFlow()) // update container lengths only for elements that are attached to the rootElement
{
var absoluteEndPos:int = getAbsoluteStart() + endPos;
var absStartIdx:int = absoluteEndPos-deleteTotal;
while (deleteTotal > 0)
{
var charsDeletedFromCurContainer:int;
var enclosingController:ContainerController = getEnclosingController(endPos-1);
if (!enclosingController)
{
// The end of the deleted text may be overset, so it doesn't appear in a container.
// Find the last container that contains the deleted text.
enclosingController = getEnclosingController(endPos - deleteTotal);
if (enclosingController)
{
var flowComposer:IFlowComposer = enclosingController.flowComposer;
var myIdx:int = flowComposer.getControllerIndex(enclosingController);
var previousEnclosingWithContent:ContainerController = enclosingController;
CONFIG::debug { assert(myIdx >= 0,"FlowElement.deleteContainerText: bad return from getContainerControllerIndex"); }
while (myIdx+1 < flowComposer.numControllers && enclosingController.absoluteStart + enclosingController.textLength < endPos)
{
enclosingController = flowComposer.getControllerAt(myIdx+1);
if (enclosingController.textLength)
{
previousEnclosingWithContent = enclosingController;
break;
}
myIdx++;
}
}
if (!enclosingController || !enclosingController.textLength)
enclosingController = previousEnclosingWithContent;
if (!enclosingController)
break;
}
var enclosingControllerBeginningPos:int = enclosingController.absoluteStart;
if (absStartIdx < enclosingControllerBeginningPos)
{
charsDeletedFromCurContainer = absoluteEndPos - enclosingControllerBeginningPos + 1;
}
else if (absStartIdx < enclosingControllerBeginningPos + enclosingController.textLength)
{
charsDeletedFromCurContainer = deleteTotal;
}
// Container textFlowLength may contain fewer characters than the those to be deleted in case of overset text.
var containerTextLengthDelta:int = enclosingController.textLength < charsDeletedFromCurContainer ? enclosingController.textLength : charsDeletedFromCurContainer;
if (containerTextLengthDelta <= 0)
break; // overset text is not in the last container -- we've deleted the container count, so exit
// working backwards - can't call setTextLength as it examines previous controllers and gets confused in the composeCompleteRatio logic
ContainerController(enclosingController).setTextLengthOnly(enclosingController.textLength - containerTextLengthDelta);
deleteTotal -= containerTextLengthDelta;
absoluteEndPos -= containerTextLengthDelta;
endPos -= containerTextLengthDelta;
}
}
}
/** @private */
tlf_internal function normalizeRange(normalizeStart:uint,normalizeEnd:uint):void
{
// default is do nothing
}
/** Support for splitting FlowLeafElements. Does a quick copy of _characterFormat if necessary. @private */
tlf_internal function quickCloneTextLayoutFormat(sibling:FlowElement):void
{
_format = sibling._format ? new FlowValueHolder(sibling._format) : null;
_computedFormat = null;
}
// ****************************************
// End tree modification support code
// ****************************************
/** @private This API supports the inputmanager */
tlf_internal function updateForMustUseComposer(textFlow:TextFlow):Boolean
{ return false; }
// ****************************************
// Begin debug support code
// ****************************************
CONFIG::debug static private var debugDictionary:Dictionary = new Dictionary(true);
CONFIG::debug static private var nextKey:int = 1;
CONFIG::debug static public function getDebugIdentity(o:Object):String
{
if (debugDictionary[o] == null)
{
var s:String = flash.utils.getQualifiedClassName(o);
if (s)
s = s.substr( s.lastIndexOf(":")+1);
else
s = "";
debugDictionary[o] = s + "." + nextKey.toString();
nextKey++;
}
return debugDictionary[o];
}
/**
* Check FlowElement for internal consistency.
* @private
* Lots of internal checks are done on FlowElements, some are dependent
* on the type of element.
* @return Number of errors found
*/
CONFIG::debug public function debugCheckFlowElement(depth:int = 0, extraData:String = ""):int
{
if (Debugging.verbose)
{
if (depth == 0)
trace("----------------------------------");
trace("FlowElement:",depth.toString(),getDebugIdentity(this),"start:",_parentRelativeStart.toString(),"length:",_textLength.toString(),extraData);
}
return 0;
}
CONFIG::debug public static function elementPath(element:FlowElement):String
{
var result:String;
if (element != null)
{
if (element._parent != null)
result = elementPath(element._parent) + "." + element.name + "[" + element._parent.getChildIndex(element) + "]";
else
result = element.name;
}
return result;
}
/**
* debugging only - show element as string
*/
CONFIG::debug public function toString():String
{
return "flowElement:" + name + " start:" + _parentRelativeStart.toString() + " absStart:" + this.getAbsoluteStart().toString() + " textLength:" + _textLength.toString();
}
// ****************************************
// End debug support code
// ****************************************
// //////////////////////////////////////////////////////////////////////
// BEGIN PROTOTYPING CASCADE SUPPORT
// //////////////////////////////////////////////////////////////////////
/** @private */
tlf_internal static function createTextLayoutFormatPrototype(localStyles:ITextLayoutFormat,parentPrototype:TextLayoutFormat):TextLayoutFormat
{
var parentPrototypeUsable:Boolean = true;
var hasStylesSet:Boolean = false;
// the actual prototype to use
var parentStylesPrototype:Object;
// create a new stylesObject with a parentPrototype
if (parentPrototype)
{
parentStylesPrototype = parentPrototype.getStyles();
if (parentStylesPrototype.hasNonInheritedStyles !== undefined)
{
// its either a boolean or if its been used once an object that has the non-inheriting styles reset
if (parentStylesPrototype.hasNonInheritedStyles === true)
{
// create a modified prototype and give it all default non-inherited values
var noInheritParentStylesPrototype:Object = Property.createObjectWithPrototype(parentStylesPrototype);
TextLayoutFormat.resetModifiedNoninheritedStyles(noInheritParentStylesPrototype);
// now save it in the parent for reuse
parentStylesPrototype.hasNonInheritedStyles = noInheritParentStylesPrototype;
parentStylesPrototype = noInheritParentStylesPrototype;
}
else
{
parentStylesPrototype = parentStylesPrototype.hasNonInheritedStyles;
}
parentPrototypeUsable = false; // can't use it
}
}
else
{
parentPrototype = TextLayoutFormat.defaultFormat as TextLayoutFormat;
parentStylesPrototype = parentPrototype.getStyles();
}
var stylesObject:Object = Property.createObjectWithPrototype(parentStylesPrototype);
var key:String;
var val:*;
var prop:Property;
// has nonInherited Styles that are *different* from the default
var hasNonInheritedStyles:Boolean = false;
// two cases depending on how localStyles are supplied
if (localStyles != null)
{
var lvh:TextLayoutFormat = localStyles as TextLayoutFormat;
if (lvh)
{
var coreStyles:Object = lvh.getStyles();
for (key in coreStyles)
{
val = coreStyles[key];
if (val == FormatValue.INHERIT)
{
if (parentPrototype)
{
prop = TextLayoutFormat.description[key];
if (prop && !prop.inherited)
{
// actually do the inheritance - might have been wiped out above!
val = parentPrototype[key];
if (stylesObject[key] != val)
{
stylesObject[key] = val;
hasNonInheritedStyles = true;
hasStylesSet = true;
// CONFIG::debug { assert(val != prop.defaultValue,"Unexpected non-inheritance"); }
}
}
}
}
else
{
if (stylesObject[key] != val)
{
prop = TextLayoutFormat.description[key];
if (prop && !prop.inherited)
{
// CONFIG::debug { assert(val != prop.defaultValue,"Unexpected non-inheritance"); }
hasNonInheritedStyles = true;
}
stylesObject[key] = val;
hasStylesSet = true; // doesn't matter if inherited or not
}
}
}
}
else
{
for each (prop in TextLayoutFormat.description)
{
key = prop.name;
val = localStyles[key];
if (val !== undefined)
{
if (val == FormatValue.INHERIT)
{
if (parentPrototype)
{
if (!prop.inherited)
{
// actually do the inheritance - might have been wiped out above!
val = parentPrototype[key];
if (stylesObject[key] != val)
{
stylesObject[key] = val;
hasNonInheritedStyles = true;
hasStylesSet = true;
// CONFIG::debug { assert(val != prop.defaultValue,"Unexpected non-inheritance"); }
}
}
}
}
else
{
if (stylesObject[key] != val)
{
if (!prop.inherited)
{
// CONFIG::debug { assert(val != prop.defaultValue,"Unexpected non-inheritance"); }
hasNonInheritedStyles = true;
}
stylesObject[key] = val;
hasStylesSet = true; // doesn't matter if inherited or not
}
}
}
}
}
}
var rslt:TextLayoutFormat;
if (!hasStylesSet)
{
// nothing has changed from the parent so just reuse it
CONFIG::debug { assert(hasNonInheritedStyles == false,"stylesCount mismatch with hasNonInheritedStyles"); }
if (parentPrototypeUsable)
return parentPrototype;
// we can use the parentStylesPrototype but not the parentPrototype
rslt = new TextLayoutFormat();
rslt.setStyles(stylesObject,true);
return rslt;
}
if (hasNonInheritedStyles)
{
// if its not identical in stylesObject need to set it
CONFIG::debug { assert(stylesObject.hasNonInheritedStyles !== hasNonInheritedStyles,"unexpected nonInheritedStyles"); }
stylesObject.hasNonInheritedStyles = true;
stylesObject.setPropertyIsEnumerable("hasNonInheritedStyles",false);
}
else if (stylesObject.hasNonInheritedStyles !== undefined)
{
stylesObject.hasNonInheritedStyles = undefined;
stylesObject.setPropertyIsEnumerable("hasNonInheritedStyles",false);
}
rslt = new TextLayoutFormat();
rslt.setStyles(stylesObject,false);
return rslt;
}
}
}