blob: 11b8511ac5b07a749d722b63bfce52ce8e1c858c [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.compose
{
import flash.display.DisplayObject;
import flash.display.GraphicsPathCommand;
import flash.display.GraphicsPathWinding;
import flash.display.Shape;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.text.engine.ElementFormat;
import flash.text.engine.FontMetrics;
import flash.text.engine.TextBlock;
import flash.text.engine.TextElement;
import flash.text.engine.TextLine;
import flash.text.engine.TextLineValidity;
import flash.text.engine.TextRotation;
import flash.utils.Dictionary;
import flashx.textLayout.container.ContainerController;
import flashx.textLayout.debug.Debugging;
import flashx.textLayout.debug.assert;
import flashx.textLayout.edit.ISelectionManager;
import flashx.textLayout.edit.SelectionFormat;
import flashx.textLayout.elements.BackgroundManager;
import flashx.textLayout.elements.ContainerFormattedElement;
import flashx.textLayout.elements.FlowElement;
import flashx.textLayout.elements.FlowLeafElement;
import flashx.textLayout.elements.FlowValueHolder;
import flashx.textLayout.elements.InlineGraphicElement;
import flashx.textLayout.elements.LinkElement;
import flashx.textLayout.elements.LinkState;
import flashx.textLayout.elements.ListElement;
import flashx.textLayout.elements.ListItemElement;
import flashx.textLayout.elements.ParagraphElement;
import flashx.textLayout.elements.SpanElement;
import flashx.textLayout.elements.SubParagraphGroupElementBase;
import flashx.textLayout.elements.TCYElement;
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.factory.StringTextLineFactory;
import flashx.textLayout.formats.BackgroundColor;
import flashx.textLayout.formats.BlockProgression;
import flashx.textLayout.formats.Direction;
import flashx.textLayout.formats.Float;
import flashx.textLayout.formats.FormatValue;
import flashx.textLayout.formats.IListMarkerFormat;
import flashx.textLayout.formats.ITextLayoutFormat;
import flashx.textLayout.formats.JustificationRule;
import flashx.textLayout.formats.LeadingModel;
import flashx.textLayout.formats.LineBreak;
import flashx.textLayout.formats.ListMarkerFormat;
import flashx.textLayout.formats.ListStylePosition;
import flashx.textLayout.formats.TextAlign;
import flashx.textLayout.formats.TextDecoration;
import flashx.textLayout.formats.TextLayoutFormat;
import flashx.textLayout.tlf_internal;
import flashx.textLayout.utils.CharacterUtil;
import flashx.textLayout.utils.Twips;
use namespace tlf_internal;
/**
* The TextFlowLine class represents a single line of text in a text flow.
*
* <p>Use this class to access information about how a line of text has been composed: its position,
* height, width, and so on. When the text flow (TextFlow) is modified, the lines immediately before and at the
* site of the modification are marked as invalid because they need to be recomposed. Lines after
* the site of the modification might not be damaged immediately, but they might be regenerated once the
* text is composed. You can access a TextFlowLine that has been damaged, but any values you access
* reflect the old state of the TextFlow. When the TextFlow is recomposed, it generates new lines and you can
* get the new line for a given position by calling <code>TextFlow.flowComposer.findLineAtPosition()</code>.</p>
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public final class TextFlowLine implements IVerticalJustificationLine
{
/** @private - the selection block cache */
static private var _selectionBlockCache:Dictionary = new Dictionary(true);
private var _absoluteStart:int; // text-offset of start of line - from beginning of the TextFlow
private var _textLength:int; // number of chars to next line (incl trailing spaces, etc.)
private var _x:Number = 0; // left edge of line; left as Number because it is user-settable
private var _y:Number = 0; // top edge of line; left as Number because it is user-settable
private var _height:Number = 0; // y advance
private var _outerTargetWidth:Number = 0; // width line is composed to, excluding indents
private var _boundsLeftTW:int = 2; // text line bounds: logical left
private var _boundsRightTW:int = 1; // text line bounds: logical right (if left > right, then it is not set)
private var _para:ParagraphElement; // owning paragraph
private var _controller:ContainerController; // what frame the line was composed into
private var _columnIndex:int; // column number in the container
private var _adornCount:int = 0;
static private const VALIDITY_MASK:uint = 7; // 3 bits
static private const ALIGNMENT_SHIFT:uint = 3;
static private const ALIGNMENT_MASK:uint = 24; // 2 bits
static private const NUMBERLINE_MASK:uint = 32; // 1 bit
static private const GRAPHICELEMENT_MASK:uint = 64; // 1 bit
private var _flags:uint;
// added to support TextFlowLine when TextLine not available
private var _ascent:Number;
private var _descent:Number;
private var _targetWidth:Number;
private var _lineOffset:Number;
private var _lineExtent:Number; // content bounds logical width for the line
private var _accumulatedLineExtent:Number;
private var _accumulatedMinimumStart:Number;
private var _numberLinePosition:int;
/** Constructor - creates a new TextFlowLine instance.
* <p><strong>Note</strong>: No client should call this. It's exposed for writing your own composer.</p>
*
* @param textLine The TextLine display object to use for this line.
* @param paragraph The paragraph (ParagraphElement) in which to place the line.
* @param outerTargetWidth The width the line is composed to, excluding indents.
* @param lineOffset The line's offset in pixels from the appropriate container inset (as dictated by paragraph direction and container block progression), prior to alignment of lines in the paragraph.
* @param absoluteStart The character position in the text flow at which the line begins.
* @param numChars The number of characters in the line.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.text.engine.TextLine
* @see flashx.textLayout.elements.ParagraphElement
* @see #absoluteStart
*/
public function TextFlowLine(textLine:TextLine, paragraph:ParagraphElement, outerTargetWidth:Number = 0, lineOffset:Number = 0, absoluteStart:int = 0, numChars:int = 0)
{
initialize(paragraph, outerTargetWidth, lineOffset, absoluteStart,numChars,textLine);
}
/** @private */
tlf_internal function initialize(paragraph:ParagraphElement, outerTargetWidth:Number = 0, lineOffset:Number = 0, absoluteStart:int = 0, numChars:int = 0, textLine:TextLine = null):void
{
_para = paragraph;
_outerTargetWidth = outerTargetWidth;
_absoluteStart = absoluteStart;
_textLength = numChars;
_adornCount = 0;
_lineExtent = 0;
_accumulatedLineExtent = 0;
_accumulatedMinimumStart = TextLine.MAX_LINE_WIDTH;
_flags = 0;
_controller = null;
if (textLine)
{
textLine.userData = this;
_targetWidth = textLine.specifiedWidth;
_ascent = textLine.ascent;
_descent = textLine.descent;
// the docs say this is true
CONFIG::debug { assert(textLine.textHeight == textLine.ascent+textLine.descent,"bad textheight"); }
_lineOffset = lineOffset;
setValidity(textLine.validity);
CONFIG::debug { assert (textLine.validity == TextLineValidity.VALID, "Initializing TextFlowLine to invalid TextLine"); }
setFlag(textLine.hasGraphicElement?GRAPHICELEMENT_MASK:0,GRAPHICELEMENT_MASK);
}
else
setValidity(TextLineValidity.INVALID);
}
private function setFlag(value:uint,mask:uint):void
{
CONFIG::debug { assert((value|mask) == mask,"TFL:setFlag bad value"); }
_flags = (_flags & ~mask) | value;
}
private function getFlag(mask:uint):uint
{
return _flags & mask;
}
/** @private */
tlf_internal function get heightTW():int
{ return Twips.to(_height); }
/** @private */
tlf_internal function get outerTargetWidthTW():int
{ return Twips.to(_outerTargetWidth); }
/** @private */
tlf_internal function get ascentTW():int
{ return Twips.to(_ascent); }
/** @private */
tlf_internal function get targetWidthTW():int
{ return Twips.to(_targetWidth); }
/** @private */
tlf_internal function get textHeightTW():int
{ return Twips.to(textHeight); }
/** @private */
tlf_internal function get lineOffsetTW():int
{ return Twips.to(_lineOffset); }
/** @private */
tlf_internal function get lineExtentTW():int
{ return Twips.to(_lineExtent); }
/** @private */
tlf_internal function get hasGraphicElement():Boolean
{ return getFlag(GRAPHICELEMENT_MASK) != 0; }
/** @private */
tlf_internal function get hasNumberLine():Boolean
{ return getFlag(NUMBERLINE_MASK) != 0; }
/** @private */
tlf_internal function get numberLinePosition():Number
{ return Twips.from(_numberLinePosition); }
/** @private */
tlf_internal function set numberLinePosition(position:Number):void
{
CONFIG::debug { assert(Twips.from(Twips.to(position)) == position,"Bad numberLinePosition"); }
_numberLinePosition = Twips.to(position);
}
/**
* The height of the text line, which is equal to <code>ascent</code> plus <code>descent</code>. The
* value is calculated based on the difference between the baselines that bound the line, either
* ideographic top and bottom or ascent and descent depending on whether the baseline at y=0
* is ideographic (for example, TextBaseline.IDEOGRAPHIC_TOP) or not.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.text.engine.TextBaseline TextBaseline
*/
public function get textHeight():Number
{ return _ascent+_descent; }
/**
* The horizontal position of the line relative to its container, expressed as the offset in pixels from the
* left of the container.
* <p><strong>Note: </strong>Although this property is technically <code>read-write</code>,
* you should treat it as <code>read-only</code>. The setter exists only to satisfy the
* requirements of the IVerticalJustificationLine interface that defines both a getter and setter for this property.
* Use of the setter, though possible, will lead to unpredictable results.
* </p>
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see #y
*/
public function get x():Number
{ return _x; }
/**
* This comment is ignored, but the setter should not be used and exists only to satisfy
* the IVerticalJustificationLine interface.
* @see flashx.textLayout.compose.IVerticalJustificationLine
* @private
*/
public function set x(lineX:Number):void
{
_x = lineX;
// invalidate bounds
_boundsLeftTW = 2;
_boundsRightTW = 1;
}
/** @private */
tlf_internal function get xTW():int
{ return Twips.to(_x); }
/**
* The vertical position of the line relative to its container, expressed as the offset in pixels from the top
* of the container.
* <p><strong>Note: </strong>Although this property is technically <code>read-write</code>,
* you should treat it as <code>read-only</code>. The setter exists only to satisfy the
* requirements of the IVerticalJustificationLine interface that defines both a getter and setter for this property.
* Use of the setter, though possible, will lead to unpredictable results.
* </p>
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see #x
*/
public function get y():Number
{ return _y; }
/** @private */
tlf_internal function get yTW():int
{ return Twips.to(_y); }
/** This comment is ignored, but the setter should not be used and exists only to satisfy
* the IVerticalJustificationLine interface.
* @see flashx.textLayout.compose.IVerticalJustificationLine
* @private
*/
public function set y(lineY:Number):void
{
_y = lineY;
// invalidate bounds
_boundsLeftTW = 2;
_boundsRightTW = 1;
}
/** @private */
tlf_internal function setXYAndHeight(lineX:Number,lineY:Number,lineHeight:Number):void
{
_x = lineX;
_y = lineY;
_height = lineHeight;
// invalidate bounds
_boundsLeftTW = 2;
_boundsRightTW = 1;
}
/**
* One of the values from TextFlowLineLocation for specifying a line's location within a paragraph.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flashx.textLayout.elements.ParagraphElement
* @see TextFlowLineLocation
*/
public function get location():int
{
if (_para)
{
var lineStart:int = _absoluteStart - _para.getAbsoluteStart();
// Initialize settings for location
if (lineStart == 0) // we're at the start of the paragraph
return _textLength == _para.textLength ? TextFlowLineLocation.ONLY : TextFlowLineLocation.FIRST;
if (lineStart + textLength == _para.textLength) // we're at the end of the para
return TextFlowLineLocation.LAST;
}
return TextFlowLineLocation.MIDDLE;
}
/**
* The controller (ContainerController object) for the container in which the line has been placed.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flashx.textLayout.container.ContainerController
*/
public function get controller():ContainerController
{ return _controller; }
/** The number of the column in which the line has been placed, with the first column being 0.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get columnIndex():int
{ return _columnIndex; }
/** @private */
tlf_internal function setController(cont:ContainerController,colNumber:int):void
{
_controller = cont as ContainerController;
_columnIndex = colNumber;
}
/** The height of the line in pixels.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
*/
public function get height():Number
{ return _height; }
/**
* @copy flash.text.engine.TextLine#ascent
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get ascent():Number
{ return _ascent; }
/**
* @copy flash.text.engine.TextLine#descent
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get descent():Number
{ return _descent; }
/**
* The line's offset in pixels from the appropriate container inset (as dictated by paragraph direction and container block progression),
* prior to alignment of lines in the paragraph.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get lineOffset():Number
{
return _lineOffset;
}
/**
* The paragraph (ParagraphElement) in which the line resides.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flashx.textLayout.elements.ParagraphElement
*/
public function get paragraph():ParagraphElement
{ return _para; }
/**
* The location of the line as an absolute character position in the TextFlow object.
*
* @return the character position in the text flow at which the line begins.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flashx.textLayout.elements.TextFlow
*/
public function get absoluteStart():int
{ return _absoluteStart; }
/** @private */
tlf_internal function setAbsoluteStart(val:int):void
{ _absoluteStart = val; }
/**
* The number of characters to the next line, including trailing spaces.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get textLength():int
{ return _textLength; }
/** @private */
tlf_internal function setTextLength(val:int):void
{
_textLength = val;
// assert(_validity == TextLineValidity.INVALID, "not already damaged");
damage(TextLineValidity.INVALID);
}
/**
* The amount of space to leave before the line.
* <p>If the line is the first line of a paragraph that has a space-before applied, the line will have
* a <code>spaceBefore</code> value. If the line comes at the top of a column, <code>spaceBefore</code> is ignored.
* Otherwise, the line follows another line in the column, and it is positioned vertically to insure that there is
* at least this much space left between this line and the last line of the preceding paragraph.</p>
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flashx.textLayout.formats.TextLayoutFormat#paragraphSpaceBefore TextLayoutFormat.paragraphSpaceBefore
*/
public function get spaceBefore():Number
{
return (this.location & TextFlowLineLocation.FIRST)? _para.computedFormat.paragraphSpaceBefore : 0;
}
/**
* The amount of space to leave after the line.
* <p>If the line is the last line of a paragraph that has a space-after, the line will have
* a <code>spaceAfter</code> value. If the line comes at the bottom of a column, then the <code>spaceAfter</code>
* is ignored. Otherwise, the line comes before another line in the column, and the following line must be positioned vertically to
* insure that there is at least this much space left between this last line of the paragraph and the first
* line of the following paragraph.</p>
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flashx.textLayout.formats.TextLayoutFormat#paragraphSpaceAfter TextLayoutFormat.paragraphSpaceAfter
*/
public function get spaceAfter():Number
{
return ((this.location & TextFlowLineLocation.LAST) ? _para.computedFormat.paragraphSpaceAfter : 0);
}
/** @private
* Target width not including paragraph indents */
tlf_internal function get outerTargetWidth():Number
{ return _outerTargetWidth; }
/** @private */
tlf_internal function set outerTargetWidth(val:Number):void
{ _outerTargetWidth = val; }
/** @private
* Amount of space used to break the line
* <p>The target width is the amount of space allowed for the line, including the space required for indents.</p>
*/
tlf_internal function get targetWidth():Number
{ return _targetWidth; }
/**
* Returns the bounds of the line as a rectangle.
*
* @return a rectangle that represents the boundaries of the line.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function getBounds():Rectangle
{
var textLine:TextLine = getTextLine(true);
if (!textLine)
return new Rectangle();
// TODO: just use the textLine.x and textLine.y - after all getTextLine now sets them.
// not going to change this right now though
var bp:String = paragraph.getAncestorWithContainer().computedFormat.blockProgression;
var shapeX:Number = this.x;
var shapeY:Number = createShapeY(bp);
if (bp == BlockProgression.TB)
shapeY += descent-textLine.height;
return new Rectangle(shapeX, shapeY, textLine.width, textLine.height);
}
static private const _validities:Array = [ TextLineValidity.INVALID, TextLineValidity.POSSIBLY_INVALID, TextLineValidity.STATIC, TextLineValidity.VALID, FlowDamageType.GEOMETRY ];
private function setValidity(value:String):void
{
CONFIG::debug { assert(_validities.indexOf(value) != -1,"Bad alignment passed to TextFlowLine"); }
setFlag(_validities.indexOf(value),VALIDITY_MASK);
}
/** The validity of the line.
* <p>A line can be invalid if the text, the attributes applied to it, or the controller settings have
* changed since the line was created. An invalid line can still be displayed, and you can use it, but the values
* used will be the values at the time it was created. The line returned by <code>getTextLine()</code> also will be in an
* invalid state. </p>
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see #getTextLine()
* @see flash.text.engine.TextLine#validity TextLine.validity
* @see FlowDamageType#GEOMETRY
*/
public function get validity():String
{
return _validities[getFlag(VALIDITY_MASK)];
}
/**
* The width of the line if it was not justified. For unjustified text, this value is the same as <code>textLength</code>.
* For justified text, this value is what the length would have been without justification, and <code>textLength</code>
* represents the actual line width. For example, when the following String is justified and assigned a width of 500, it
* has an actual width of 500 but an unjustified width of 268.9921875.
*
* @internal TBD: add graphic of justified line
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get unjustifiedTextWidth():Number
{
// hack - outerTargetWidth holds value from the factory
var textLine:TextLine = getTextLine(true);
return textLine.unjustifiedTextWidth + (_outerTargetWidth - targetWidth);
}
/** @private */
tlf_internal function get lineExtent():Number
{
return _lineExtent;
}
/** @private */
tlf_internal function set lineExtent(value:Number):void
{
_lineExtent = value;
}
/** @private */
tlf_internal function get accumulatedLineExtent():Number
{
return _accumulatedLineExtent;
}
/** @private */
tlf_internal function set accumulatedLineExtent(value:Number):void
{
_accumulatedLineExtent = value;
}
/** @private */
tlf_internal function get accumulatedMinimumStart():Number
{
return _accumulatedMinimumStart;
}
/** @private */
tlf_internal function set accumulatedMinimumStart(value:Number):void
{
_accumulatedMinimumStart = value;
}
static private const _alignments:Array = [ TextAlign.LEFT, TextAlign.CENTER, TextAlign.RIGHT ];
/** @private */
tlf_internal function get alignment():String
{
return _alignments[getFlag(ALIGNMENT_MASK)>>ALIGNMENT_SHIFT];
}
/** @private */
tlf_internal function set alignment(value:String):void
{
CONFIG::debug { assert(_alignments.indexOf(value) != -1,"Bad alignment passed to TextFlowLine"); }
setFlag(_alignments.indexOf(value)<<ALIGNMENT_SHIFT,ALIGNMENT_MASK);
}
/** @private
* True if the line needs composing. */
tlf_internal function isDamaged():Boolean
{
return (validity != TextLineValidity.VALID)
}
/** @private
* Mark the line as valid */
tlf_internal function clearDamage():void
{
CONFIG::debug { assert(validity == FlowDamageType.GEOMETRY, "can't clear damage other than geometry"); }
setValidity(TextLineValidity.VALID);
}
/** @private
* Mark the line as damaged */
tlf_internal function damage(damageType:String):void
{
// trace("TextFlowLine.damage ", this.start.toString(), this.textLength.toString());
var current:String = validity;
if (current == damageType || current == TextLineValidity.INVALID)
return; // totally damaged
setValidity(damageType);
}
/** @private */
/**
* Check if the line is visible by comparing a set rectangle to the supplied
* rectangle (all values in Twips).
* -1 BEFORE visible bounds
* 0 Visible
* 1 AFTER visible bounds
* @private
*/
tlf_internal function testLineVisible(wmode:String, x:int, y:int, w:int, h:int):int
{
CONFIG::debug{ assert(hasLineBounds(),"Bad call to TextFlowLine.isLineVisible"); }
if (wmode == BlockProgression.RL)
{
if ( _boundsRightTW >= x && _boundsLeftTW < x + w)
return 0;
return x < _boundsRightTW ? 1 : -1;
}
if (_boundsRightTW >= y && _boundsLeftTW < y + h)
return 0;
return y < _boundsRightTW ? -1 : 1;
}
tlf_internal function oldTestLineVisible(wmode:String, x:int, y:int, w:int, h:int):Boolean
{
CONFIG::debug{ assert(hasLineBounds(),"Bad call to TextFlowLine.isLineVisible"); }
if (wmode == BlockProgression.RL)
return _boundsRightTW >= x && _boundsLeftTW < x + w;
return _boundsRightTW >= y && _boundsLeftTW < y + h;
}
/** @private
* Set the text line bounds rectangle, all values in Twips.
* If left > right, the rectangle is considered not to be set.
* @private
*/
tlf_internal function cacheLineBounds(wmode:String, bndsx:Number, bndsy:Number, bndsw:Number, bndsh:Number):void
{
if (wmode == BlockProgression.RL)
{
_boundsLeftTW = Twips.to(bndsx);
_boundsRightTW = Twips.to(bndsx + bndsw);
}
else
{
_boundsLeftTW = Twips.to(bndsy);
_boundsRightTW = Twips.to(bndsy + bndsh);
}
}
/** @private
* Check if the text line bounds are set. If the stored left
* value is > the right value, then the rectangle is not set.
* @private
*/
tlf_internal function hasLineBounds():Boolean
{
return (_boundsLeftTW <= _boundsRightTW);
}
/** @private */
CONFIG::debug public function toString():String
{
return "x:" + x + " y: " + y + " absoluteStart:" + absoluteStart + " textLength:" + textLength + " location: " + location + " validity: " + validity;
}
/**
* Indicates whether the <code>flash.text.engine.TextLine</code> object for this TextFlowLine exists.
* The value is <code>true</code> if the TextLine object has <em>not</em> been garbage collected and
* <code>false</code> if it has been.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.text.engine.TextLine TextLine
*/
public function get textLineExists():Boolean
{
return peekTextLine() != null;
}
/** @private
* Returns the associated TextLine if there is one. Finds it by looking up in the TextBlock.
*/
tlf_internal function peekTextLine():TextLine
{
var textLine:TextLine;
if (!paragraph)
return null;
// Look it up in the textBlock
var textBlock:TextBlock = paragraph.peekTextBlock();
if (textBlock)
{
for (textLine = textBlock.firstLine; textLine; textLine = textLine.nextLine)
{
if (textLine.userData == this) // found it
return textLine;
}
}
return null;
}
/**
* Returns the <code>flash.text.engine.TextLine</code> object for this line, which might be recreated
* if it does not exist due to garbage collection. Set <code>forceValid</code> to <code>true</code>
* to cause the TextLine to be regenerated. Returns null if the TextLine cannot be recreated.
*.
* @param forceValid if true, the TextLine is regenerated, if it exists but is invalid.
*
* @return object for this line or <code>null</code> if the TextLine object cannot be
* recreated.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.text.engine.TextLine TextLine
*/
public function getTextLine(forceValid:Boolean = false):TextLine
{
var textLine:TextLine = peekTextLine();
if (textLine && textLine.validity == FlowDamageType.GEOMETRY)
createShape(paragraph.getTextFlow().computedFormat.blockProgression, textLine);
else if (!textLine || (textLine.validity == TextLineValidity.INVALID && forceValid))
{
/* if (!textLine)
{
trace("text line does not exist: regenerating");
textLine = peekTextLine();
}
else
trace("textline is invalid: regenerated"); */
if (isDamaged() && validity != FlowDamageType.GEOMETRY)
return null;
textLine = getTextLineInternal();
}
return textLine;
}
private function getTextLineInternal():TextLine
{
// Look it up in the textBlock
var paraAbsStart:int = paragraph.getAbsoluteStart();
// If we haven't found it yet, we need to regenerate it.
// Regenerate the whole paragraph at once, up to the current position.
var textBlock:TextBlock = paragraph.getTextBlock();
var currentLine:TextLine = textBlock.firstLine;
var flowComposer:IFlowComposer = paragraph.getTextFlow().flowComposer;
var lineIndex:int = flowComposer.findLineIndexAtPosition(paraAbsStart);
var previousLine:TextLine = null;
var textLine:TextLine;
do
{
var line:TextFlowLine = flowComposer.getLineAt(lineIndex);
CONFIG::debug { assert (line && line.paragraph == paragraph, "Expecting line in same paragraph"); }
if (currentLine != null && currentLine.validity == TextLineValidity.VALID && (line != this || currentLine.userData == line))
{
textLine = currentLine;
currentLine = currentLine.nextLine;
}
else
{
textLine = line.recreateTextLine(textBlock, previousLine);
currentLine = null;
}
previousLine = textLine;
++lineIndex;
} while (line != this);
// Put it in the cache, so we can find it there next time
// textLineCache[this] = textLine;
return textLine;
}
/** @private Regenerate the TextLine -- called when textLine has been gc'ed */
tlf_internal function recreateTextLine(textBlock:TextBlock, previousLine:TextLine):TextLine
{
var textLine:TextLine;
var textFlow:TextFlow = _para.getTextFlow();
var bp:String = textFlow.computedFormat.blockProgression;
var flowComposer:IFlowComposer = textFlow.flowComposer;
var swfContext:ISWFContext = flowComposer.swfContext ? flowComposer.swfContext : BaseCompose.globalSWFContext;
var numberLine:TextLine;
var effLineOffset:Number = _lineOffset;
if (hasNumberLine)
{
var boxStartTotalIndent:Number = this._lineOffset - _para.computedFormat.textIndent;
numberLine = TextFlowLine.createNumberLine(_para.getParentByType(ListItemElement) as ListItemElement, _para, flowComposer.swfContext, boxStartTotalIndent);
if (numberLine)
{
if (getNumberLineListStylePosition(numberLine) == ListStylePosition.INSIDE)
effLineOffset += getNumberLineInsideLineWidth(numberLine);
}
}
// trace("Recreating line from", absoluteStart, "to", absoluteStart + textLength);
textLine = TextLineRecycler.getLineForReuse();
if (textLine)
{
CONFIG::debug { assert(textFlow.backgroundManager == null || textFlow.backgroundManager..getEntry(textLine) === undefined,"Bad TextLine in recycler cache"); }
textLine = swfContext.callInContext(textBlock["recreateTextLine"], textBlock, [ textLine, previousLine, _targetWidth, effLineOffset, true ]);
}
else
textLine = swfContext.callInContext(textBlock.createTextLine, textBlock, [ previousLine, _targetWidth, effLineOffset, true ]);
textLine.x = this.x;
CONFIG::debug { Debugging.traceFTEAssign(textLine,"x", this.x); }
textLine.y = createShapeY(bp);
CONFIG::debug { Debugging.traceFTEAssign(textLine,"y", createShapeY(bp)); }
textLine.doubleClickEnabled = true;
textLine.userData = this;
// Regenerate adornments (e.g., underline & strikethru)
if (_adornCount > 0)
{
var paraStart:int = _para.getAbsoluteStart();
var elem:FlowLeafElement = _para.findLeaf(this.absoluteStart - paraStart);
var elemStart:int = elem.getAbsoluteStart();
CONFIG::debug { assert(textLine.userData == this, "textLine doesn't point back to TextFlowLine"); }
if (numberLine)
{
var listItemElement:ListItemElement = _para.getParentByType(ListItemElement) as ListItemElement;
TextFlowLine.initializeNumberLinePosition(numberLine, listItemElement, _para, textLine.textWidth);
}
createAdornments(_para.getAncestorWithContainer().computedFormat.blockProgression, elem, elemStart, textLine, numberLine);
if (numberLine && getNumberLineListStylePosition(numberLine) == ListStylePosition.OUTSIDE)
{
if (bp == BlockProgression.TB)
numberLine.x = this.numberLinePosition;
else
numberLine.y = this.numberLinePosition;
}
}
return textLine;
}
/** @private */
tlf_internal function createShape(bp:String, textLine:TextLine):void
{
var newX:Number = this.x;
//if (int(newX*20) != int(textLine.x*20))
{
textLine.x = newX;
CONFIG::debug { Debugging.traceFTEAssign(textLine,"x", newX); }
}
var newY:Number = createShapeY(bp);
//if (int(newY*20) != int(textLine.y*20))
{
textLine.y = newY;
CONFIG::debug { Debugging.traceFTEAssign(textLine,"y", newY); }
}
}
private function createShapeY(bp:String):Number
{ return bp == BlockProgression.RL ? y : y + _ascent; }
/** @private
* Scan through the format runs within the line, and draw any underline or strikethrough that might need it
*/
tlf_internal function createAdornments(blockProgression:String,elem:FlowLeafElement,elemStart:int, textLine:TextLine, numberLine:TextLine):void
{
CONFIG::debug { assert(elemStart == elem.getAbsoluteStart(),"bad elemStart passed to createAdornments"); }
var endPos:int = _absoluteStart + _textLength;
CONFIG::debug { assert(textLine.validity == TextLineValidity.VALID,"createAdornments: bad TextLine validity"); }
/*if (textLine.validity != TextLineValidity.VALID)
{
// This can happen if we are scrolling through text and lines have been released,
// then scrolled back into view before they're gc'ed. Then we have an invalid line in the TextFlowLine
// cache, so we create a valid text line to calculate where the adornment shapes should go. The
// actual shapes will be added to the invalid (original) TextLine in the TextFlowLine cache.
textLine = getTextLineInternal();
}*/
//init adornments back to 0 - may be redoing the line in a new position
// This can happen if there was damage earlier in the paragraph, so that the TextLine was damaged (because all lines in block were damaged)
// but the TextFlowLine is still considered OK (didn't cause a line break change).
// CONFIG::debug { assert(_adornCount == 0 && _hasNumberLine == false,"createAdornments: adornments applied twice?"); }
_adornCount = 0;
if (numberLine)
{
_adornCount++;
setFlag(NUMBERLINE_MASK,NUMBERLINE_MASK);
textLine.addChild(numberLine);
CONFIG::debug { Debugging.traceFTECall(null,textLine,"addChildNumberLine",numberLine); }
// handle background on the numberLine
if (getNumberLineBackground(numberLine) != null)
{
var bgm:BackgroundManager = elem.getTextFlow().getBackgroundManager();
if (bgm)
bgm.addNumberLine(textLine,numberLine);
}
}
else
setFlag(0,NUMBERLINE_MASK);
for (;;)
{
_adornCount += elem.updateAdornments(textLine, blockProgression);
var elemFormat:ITextLayoutFormat = elem.format;
var imeStatus:* = elemFormat ? elemFormat.getStyle("imeStatus") : undefined;
if(imeStatus)
{
elem.updateIMEAdornments(textLine, blockProgression, imeStatus as String);
}
elemStart += elem.textLength;
if (elemStart >= endPos)
break;
elem = elem.getNextLeaf(_para);
CONFIG::debug { assert(elem != null,"bad nextLeaf"); }
}
}
/** @private */
static tlf_internal function initializeNumberLinePosition(numberLine:TextLine,listItemElement:ListItemElement,curParaElement:ParagraphElement,totalWidth:Number):void
{
// use the listStylePosition on the ListItem (not the list)
var listMarkerFormat:IListMarkerFormat = listItemElement.computedListMarkerFormat();
var paragraphFormat:ITextLayoutFormat = curParaElement.computedFormat;
// only applies on outside list markers
var listEndIndent:Number = listMarkerFormat.paragraphEndIndent === undefined || listItemElement.computedFormat.listStylePosition == ListStylePosition.INSIDE ? 0
: (listMarkerFormat.paragraphEndIndent == FormatValue.INHERIT ? paragraphFormat.paragraphEndIndent : listMarkerFormat.paragraphEndIndent);
TextFlowLine.setListEndIndent(numberLine,listEndIndent);
// no more work needed for OUTSIDE positioning - its all done in the applyTextAlign code
if (listItemElement.computedFormat.listStylePosition == ListStylePosition.OUTSIDE)
{
numberLine.x = numberLine.y = 0;
return;
}
var bp:String = curParaElement.getTextFlow().computedFormat.blockProgression;
var numberLineWidth:Number = TextFlowLine.getNumberLineInsideLineWidth(numberLine);
if (bp == BlockProgression.TB)
{
if (paragraphFormat.direction == Direction.LTR)
numberLine.x = -numberLineWidth;
else
numberLine.x = totalWidth + numberLineWidth-numberLine.textWidth;
numberLine.y = 0; // assumes same baseline as parent!!
}
else
{
if (paragraphFormat.direction == Direction.LTR)
numberLine.y = -numberLineWidth;
else
numberLine.y = totalWidth + numberLineWidth-numberLine.textWidth;
numberLine.x = 0; // assumes same baseline as parent!!
}
}
static private var numberLineFactory:NumberLineFactory;
/** @private Logic to generate and position the TextLine containing the numbering for a listElement's first line */
static tlf_internal function createNumberLine(listItemElement:ListItemElement,curParaElement:ParagraphElement,swfContext:ISWFContext, totalStartIndent:Number):TextLine
{
CONFIG::debug { assert(swfContext != BaseCompose.globalSWFContext, "TextFlowLine.createNumberLine: don't pass globalswfcontext"); }
if (numberLineFactory == null)
{
numberLineFactory = new NumberLineFactory();
numberLineFactory.compositionBounds = new Rectangle(0,0,NaN,NaN);
}
numberLineFactory.swfContext = swfContext;
var listMarkerFormat:IListMarkerFormat = listItemElement.computedListMarkerFormat();
// use the listStylePosition on the ListItem (not the list)
numberLineFactory.listStylePosition = listItemElement.computedFormat.listStylePosition;
var listElement:ListElement = listItemElement.parent as ListElement;
var paragraphFormat:TextLayoutFormat = new TextLayoutFormat(curParaElement.computedFormat);
var boxStartIndent:Number = paragraphFormat.direction == Direction.LTR ? listElement.getEffectivePaddingLeft() + listElement.getEffectiveBorderLeftWidth() + listElement.getEffectiveMarginLeft(): listElement.getEffectivePaddingRight() + listElement.getEffectiveBorderRightWidth() + listElement.getEffectiveMarginRight();
// this just gets the first line but that's the only one we use. could have used paragraphStartIndent or padding/margins.
// do it this way so that negative indents are supported. TODO revisit when box model work is complete
paragraphFormat.apply(listMarkerFormat);
// Fix bug 2800975 ListMarkerFormat.paragraphStartIndent not applied properly in Inside lists.
paragraphFormat.textIndent += totalStartIndent;
if (numberLineFactory.listStylePosition == ListStylePosition.OUTSIDE)
paragraphFormat.textIndent -= boxStartIndent;
numberLineFactory.paragraphFormat = paragraphFormat; // curParaElement.computedFormat;
numberLineFactory.textFlowFormat = curParaElement.getTextFlow().computedFormat;
// suppress the formatting of any links
var firstLeaf:FlowLeafElement = curParaElement.getFirstLeaf();
var parentLink:LinkElement = firstLeaf.getParentByType(LinkElement) as LinkElement;
// record the topmost parent link
var highestParentLinkLinkElement:LinkElement;
var linkStateArray:Array = [];
while (parentLink)
{
highestParentLinkLinkElement = parentLink;
linkStateArray.push(parentLink.linkState);
parentLink.chgLinkState(LinkState.SUPPRESSED);
parentLink = parentLink.getParentByType(LinkElement) as LinkElement;
}
// spanFormat to use for the markers
var spanFormat:TextLayoutFormat = new TextLayoutFormat(firstLeaf.computedFormat);
// now restore the formatting of any links
parentLink = firstLeaf.getParentByType(LinkElement) as LinkElement;
while (parentLink)
{
linkStateArray.push(parentLink.linkState);
parentLink.chgLinkState(linkStateArray.shift());
parentLink = parentLink.getParentByType(LinkElement) as LinkElement;
}
// forces recompute of computedFormat of all leaf nodes of highestParentLinkLinkElement
if (highestParentLinkLinkElement)
{
var leaf:FlowLeafElement = highestParentLinkLinkElement.getFirstLeaf();
while (leaf)
{
leaf.computedFormat;
leaf = leaf.getNextLeaf(highestParentLinkLinkElement);
}
}
// finalize the spanFormat for the marker
var markerFormat:TextLayoutFormat = new TextLayoutFormat(spanFormat);
TextLayoutFormat.resetModifiedNoninheritedStyles(markerFormat);
var holderStyles:Object = (listMarkerFormat as TextLayoutFormat).getStyles();
for (var key:String in holderStyles)
{
// only copy TextLayoutFormat properties
if (TextLayoutFormat.description[key] !== undefined)
{
var val:* = holderStyles[key];
markerFormat[key] = (val !== FormatValue.INHERIT) ? val : spanFormat[key];
}
}
numberLineFactory.markerFormat = markerFormat;
numberLineFactory.text = listElement.computeListItemText(listItemElement,listMarkerFormat);
// expect one or zero lines - technically with beforeContent and afterContent more than one line can be generated. This could be more like a float!!
// also need to expect a backgroundColor
var rslt:Array = [];
numberLineFactory.createTextLines(function (o:DisplayObject):void { rslt.push(o); });
// position it relative to the parent line - later need to take inside/outside into account
var numberLine:TextLine = rslt[0] as TextLine;
if (numberLine)
{
CONFIG::debug { assert(numberLine.validity == TextLineValidity.STATIC,"Invalid validity on numberLine"); }
numberLine.mouseEnabled = false;
numberLine.mouseChildren = false;
setNumberLineBackground(numberLine,numberLineFactory.backgroundManager);
}
numberLineFactory.clearBackgroundManager();
return numberLine;
}
/** @private
* Scan through the format runs within the line, and figure out what the leading for the overall line is.
* The line's leading is equal to the maximum leading of any individual run within the line.
* The leading in an individual format run is calculated by looking at the leading attribute in the
* CharacterFormat. If it is set to a value, we just use that value. Otherwise, if it is set to AUTO,
* we calculate the leading based on the point size and the auto leading percentage from the ParagraphFormat.
*/
tlf_internal function getLineLeading(bp:String,elem:FlowLeafElement,elemStart:int):Number
{
CONFIG::debug { assert(elemStart == elem.getAbsoluteStart(),"bad elemStart passed to getLineLeading"); }
var endPos:int = _absoluteStart + _textLength;
var totalLeading:Number = 0;
CONFIG::debug { assert(elem.getAncestorWithContainer() != null,"element with no container"); }
for (;;)
{
// If there's only one element in the line, and it has 0 leading, try resetting the leading using the default algorithm. Important
// for lines that contain only a float (which can otherwise recurse infinitely), and TCY spans in vertical text.
var elemLeading:Number = elem.getEffectiveLineHeight(bp);
if (!elemLeading && elem.textLength == this.textLength)
elemLeading = TextLayoutFormat.lineHeightProperty.computeActualPropertyValue(elem.computedFormat.lineHeight, elem.computedFormat.fontSize);
totalLeading = Math.max(totalLeading, elemLeading);
elemStart += elem.textLength;
if (elemStart >= endPos)
break;
elem = elem.getNextLeaf(_para);
CONFIG::debug { assert(elem != null,"bad nextLeaf"); }
}
return totalLeading;
}
/** @private
* Scan through the format runs within the line, and figure out what the typographic ascent (i.e. ascent relative to the
* Roman baseline) for the overall line is. Normally it is the distance between the Roman and Ascent baselines,
* but it may be adjusted upwards by the width/height of the GraphicElement.
*/
tlf_internal function getLineTypographicAscent(elem:FlowLeafElement,elemStart:int,textLine:TextLine):Number
{
CONFIG::debug { assert(elemStart == elem.getAbsoluteStart(),"bad elemStart passed to getLineTypographicAscent"); }
return getTextLineTypographicAscent(textLine ? textLine: getTextLine(), elem, elemStart, absoluteStart+textLength);
}
/** @private
* Scan through the format runs within the line, and figure out what the typographic ascent (i.e. ascent relative to the
* Roman baseline) for the overall line is. Normally it is the distance between the Roman and Ascent baselines,
* but it may be adjusted upwards by the width/height of the GraphicElement.
*/
static tlf_internal function getTextLineTypographicAscent(textLine:TextLine, elem:FlowLeafElement,elemStart:int, textLineEnd:int):Number
{
CONFIG::debug { assert(!elem || elemStart == elem.getAbsoluteStart(),"bad elemStart passed to getTextLineTypographicAscent"); }
var rslt:Number = textLine.getBaselinePosition(flash.text.engine.TextBaseline.ROMAN) - textLine.getBaselinePosition(flash.text.engine.TextBaseline.ASCENT);
if (textLine.hasGraphicElement)
{
for (;;)
{
if (elem is InlineGraphicElement)
rslt = Math.max(rslt,InlineGraphicElement(elem).getTypographicAscent(textLine));
elemStart += elem.textLength;
if (elemStart >= textLineEnd)
break;
elem = elem.getNextLeaf();
CONFIG::debug { assert(elem != null,"bad nextLeaf"); }
}
}
return rslt;
}
/** @private
* Get the "line box" for the line as defined by the CSS visual formatting model (http://www.w3.org/TR/CSS2/visuren.html)
* Essentially, the union of all "inline boxes" on the line.
* @return A rectangle representing the line box. Top and Bottom are relative to the Roman baseline. Left and Right are ignored.
* May return null, for example, if the line only contains a float.
*/
tlf_internal function getCSSLineBox(bp:String, elem:FlowLeafElement, elemStart:int, swfContext:ISWFContext, effectiveListMarkerFormat:ITextLayoutFormat=null, numberLine:TextLine=null):Rectangle
{
CONFIG::debug { assert(elem.getAncestorWithContainer() != null,"element with no container"); }
CONFIG::debug { assert(elemStart == elem.getAbsoluteStart(),"bad elemStart passed to getCSSLineBox"); }
var lineBox:Rectangle;
var endPos:int = _absoluteStart + _textLength;
var textLine:TextLine = getTextLine();
for (;;)
{
addToLineBox(elem.getCSSInlineBox(bp, textLine, _para, swfContext));
elemStart += elem.textLength;
if (elemStart >= endPos)
break;
elem = elem.getNextLeaf(_para);
CONFIG::debug { assert(elem != null,"bad nextLeaf"); }
}
if (effectiveListMarkerFormat && numberLine)
{
// Para not available for number line, but the methods below handle null para correctly.
var para:ParagraphElement = null;
var ef:ElementFormat = FlowLeafElement.computeElementFormatHelper (effectiveListMarkerFormat, para, swfContext);
var metrics:FontMetrics = swfContext ? swfContext.callInContext(ef.getFontMetrics, ef, null, true) : ef.getFontMetrics();
addToLineBox (FlowLeafElement.getCSSInlineBoxHelper(effectiveListMarkerFormat, metrics, numberLine, para));
}
function addToLineBox (inlineBox:Rectangle):void
{
if (inlineBox)
lineBox = lineBox ? lineBox.union(inlineBox) : inlineBox;
}
return lineBox;
}
//helper method to determine which subset of line is underlined
//I believe this will be replaced by the eventSink mechanism
private function isTextlineSubsetOfSpan(element:FlowLeafElement): Boolean
{
var spanStart:int = element.getAbsoluteStart();
var spanEnd:int = spanStart + element.textLength;
var lineStart:int = this.absoluteStart;
var lineEnd:int = this.absoluteStart + this._textLength;
return spanStart <= lineStart && spanEnd >= lineEnd;
}
/** Create a rectangle for selection */
static private function createSelectionRect(selObj:Shape, color:uint, x:Number, y:Number, width:Number, height:Number):DisplayObject
{
selObj.graphics.beginFill(color);
var cmds:Vector.<int> = new Vector.<int>();
var pathPoints:Vector.<Number> = new Vector.<Number>();
//set the start point - topLeft
cmds.push(GraphicsPathCommand.MOVE_TO);
pathPoints.push(x);
pathPoints.push(y);
//line to topRight
cmds.push(GraphicsPathCommand.LINE_TO);
pathPoints.push(x + width);
pathPoints.push(y);
//line to botRight
cmds.push(GraphicsPathCommand.LINE_TO);
pathPoints.push(x + width);
pathPoints.push(y + height);
//line to botLeft
cmds.push(GraphicsPathCommand.LINE_TO);
pathPoints.push(x);
pathPoints.push(y + height);
//line to close the path - topLeft
cmds.push(GraphicsPathCommand.LINE_TO);
pathPoints.push(x);
pathPoints.push(y);
selObj.graphics.drawPath(cmds, pathPoints, flash.display.GraphicsPathWinding.NON_ZERO);
return selObj;
}
/** @private getSelectionShapesCacheEntry
*
* creates and adds block selection(s) to the text container. In most circumstances,
* this method will produce and add a single DisplayObject, but in certain circumstances,
* such as TCY in TTB text, will need to make multiple selection rectangles.
*
* Examples:
* 1) horizontal - ABCDE
* 2) vertical - ABCDE
* 3) horizontal - ABcdE
* 4) vertical: A
* B
* cde
* F
*
*/
private function getSelectionShapesCacheEntry(begIdx:int, endIdx:int, prevLine:TextFlowLine, nextLine:TextFlowLine, blockProgression:String):SelectionCache
{
if (isDamaged())
return null;
//get the absolute start of the paragraph. Calculation is expensive, so just do this once.
var paraAbsStart:int = _para.getAbsoluteStart();
//if the indexes are identical and are equal to the start of the line, then
//don't draw anything. This prevents a bar being drawn on a following line when
//selecting accross line boundaries
//with exception for a selection that includes just the first character of an empty last line in the TextFlow
if (begIdx == endIdx && paraAbsStart + begIdx == absoluteStart)
{
if (absoluteStart != _para.getTextFlow().textLength-1)
return null;
endIdx++;
}
//the cached selection bounds and rects
var selectionCache:SelectionCache = _selectionBlockCache[this];
if (selectionCache && selectionCache.begIdx == begIdx && selectionCache.endIdx == endIdx)
return selectionCache;
var drawRects:Array = new Array();
//an array to store any tcy rectangles which need separate processing and may not exist
var tcyDrawRects:Array = new Array();
if(selectionCache == null)
{
selectionCache = new SelectionCache();
_selectionBlockCache[this] = selectionCache;
}
else
{
selectionCache.clear();
}
selectionCache.begIdx = begIdx;
selectionCache.endIdx = endIdx;
var textLine:TextLine = getTextLine();
var heightAndAdj:Array = getRomanSelectionHeightAndVerticalAdjustment(prevLine, nextLine);
calculateSelectionBounds(textLine, drawRects, begIdx, endIdx, blockProgression, heightAndAdj);
//iterate the blocks and create DisplayObjects to draw...
for each(var drawRect:Rectangle in drawRects)
{
CONFIG::debug{ assert(selectionCache != null, "If we're caching, selectionArray should never be null!"); }
//we have to make new rectangles or the convertLineRectToGlobal will alter the cached ones!
selectionCache.pushSelectionBlock(drawRect);
}
//allow the atoms to be garbage collected.
if (textLine)
textLine.flushAtomData();
return selectionCache;
}
/** @private - helper method to calculate all selection blocks within a line.*/
tlf_internal function calculateSelectionBounds(textLine:TextLine, rectArray:Array, begIdx:int, endIdx:int, blockProgression:String, heightAndAdj:Array):void
{
//the direction of the text
var direction:String = _para.computedFormat.direction;
//get the absolute start of the paragraph. Calculation is expensive, so just do this once.
var paraAbsStart:int = _para.getAbsoluteStart();
//the current index. used to iterate to the next element
var curIdx:int = begIdx;
//the current FlowLeafElement as determined by curIdx
var curElem:FlowLeafElement = null;
//the hightest glyph. Needed to normalize the rectangles we'll be building
var largestRise:Number = 0;
//blockRectArray holds each leaf's blocks which could be 1 or more
var blockRectArray:Array = new Array();
//floatRectArray holds the selection rects for any floats in the range.
var floatRectArray:Array = null;
//tcyDrawRects:Array
var tcyDrawRects:Array = null;
//do this loop and only afterwards perform the normalization and addition to the rectArr
while(curIdx < endIdx)
{
curElem = _para.findLeaf(curIdx);
//if we somehow got a 0 length element, then increment the index and continue
if(curElem.textLength == 0)
{
++curIdx;
continue;
}
else if(curElem is InlineGraphicElement && (curElem as InlineGraphicElement).computedFloat != Float.NONE)
{
if(floatRectArray == null)
floatRectArray = new Array();
// var blockRect:Rectangle = (curElem as InlineGraphicElement).graphic.getBounds(textLine);
var ilg:InlineGraphicElement = (curElem as InlineGraphicElement);
var floatInfo:FloatCompositionData = controller.getFloatAtPosition(paraAbsStart + curIdx);
if (floatInfo)
{
var blockRect:Rectangle = new Rectangle(floatInfo.x - textLine.x, floatInfo.y - textLine.y, ilg.elementWidth, ilg.elementHeight);
floatRectArray.push(blockRect);
}
++curIdx;
continue;
}
//the number of potential glyphs to hilite. Could larger than needs be if we are only selecting part of it.
var numCharsSelecting:int = curElem.textLength + curElem.getElementRelativeStart(_para) - curIdx;
//the index of the last glyph to hilite. If a partial selection, use endIdx
var endPos:int = (numCharsSelecting + curIdx) > endIdx ? endIdx : (numCharsSelecting + curIdx);
//if this is not a TCY in vertical, the blocks should all be running in the same direction
if (blockProgression != BlockProgression.RL ||
(textLine.getAtomTextRotation(textLine.getAtomIndexAtCharIndex(curIdx)) != TextRotation.ROTATE_0))
{
var leafBlockArray:Array = makeSelectionBlocks(textLine, curIdx, endPos, paraAbsStart, blockProgression, direction, heightAndAdj);
//copy all the blocks into the blockRectArray - we'll normalize them later
for(var leafBlockIter:int = 0; leafBlockIter < leafBlockArray.length; ++leafBlockIter)
{
blockRectArray.push(leafBlockArray[leafBlockIter]);
}
}
else
{
var tcyBlock:FlowElement = curElem.getParentByType(TCYElement);
CONFIG::debug{ assert(tcyBlock != null, "What kind of object is this that is ROTATE_0, but not TCY?");}
//if this element is still encompassed by a SubParagraphGroupElementBase of some kind (either a link or a TCYBlock)
//keep moving up to the parent. Otherwise, the below code will go into an infinite loop. bug 1905734
var tcyParentRelativeEnd:int = tcyBlock.parentRelativeEnd;
var subParBlock:SubParagraphGroupElementBase = tcyBlock.getParentByType(SubParagraphGroupElementBase) as SubParagraphGroupElementBase;
while (subParBlock)
{
tcyParentRelativeEnd += subParBlock.parentRelativeStart;
subParBlock = subParBlock.getParentByType(SubParagraphGroupElementBase) as SubParagraphGroupElementBase;
}
var largestTCYRise:Number = 0;
var lastTCYIdx:int = endIdx < tcyParentRelativeEnd ? endIdx : tcyParentRelativeEnd;
var tcyRects:Array = new Array();
while(curIdx < lastTCYIdx)
{
curElem = _para.findLeaf(curIdx);
numCharsSelecting = curElem.textLength + curElem.getElementRelativeStart(_para) - curIdx;
endPos = numCharsSelecting + curIdx > endIdx ? endIdx : numCharsSelecting + curIdx;
var tcyRectArray:Array = makeSelectionBlocks(textLine, curIdx, endPos, paraAbsStart, blockProgression, direction, heightAndAdj);
for(var tcyBlockIter:int = 0; tcyBlockIter < tcyRectArray.length; ++tcyBlockIter)
{
var tcyRect:Rectangle = tcyRectArray[tcyBlockIter];
if(tcyRect.height > largestTCYRise)
{
largestTCYRise = tcyRect.height;
}
tcyRects.push(tcyRect);
}
curIdx = endPos;
}
if(!tcyDrawRects)
tcyDrawRects = new Array();
normalizeRects(tcyRects, tcyDrawRects, largestTCYRise, BlockProgression.TB, direction);
continue;
}
//set the curIdx to the last char in the block
curIdx = endPos;
}
//adding check for an empty set of draw rects. If there are not recangles, skip this.
//this can happen is there are ONLY TCY blocks and the whole line is selected.
//Watson 2273832. - gak 02.09.09
//if the whole line is selected
if(blockRectArray.length > 0 && (paraAbsStart + begIdx) == absoluteStart && (paraAbsStart + endIdx) == (absoluteStart + textLength))
{
curElem = _para.findLeaf(begIdx);
//if we have the entire line selected, but the first element is NOT the last, then
//we will land up with a selection which is 1 character wider than it should be.
if(((curElem.getAbsoluteStart() + curElem.textLength) < (absoluteStart + textLength)) && endPos >= 2)
{
//make sure that this is a white char and that we aren't deselecting the last
//char in a line - esp important for scripts which don't use spaces ie Japanese
var charCode:int = _para.getCharCodeAtPosition(endPos - 1);
if(charCode != SpanElement.kParagraphTerminator.charCodeAt(0) && CharacterUtil.isWhitespace(charCode))
{
var lastElemBlockArray:Array = makeSelectionBlocks(textLine, endPos - 1, endPos - 1, paraAbsStart, blockProgression, direction, heightAndAdj);
var lastRect:Rectangle = lastElemBlockArray[lastElemBlockArray.length - 1];
var modifyRect:Rectangle = blockRectArray[blockRectArray.length - 1] as Rectangle;
if (blockProgression != BlockProgression.RL)
{
//if they have the same width, simply remove the last block
if(modifyRect.width == lastRect.width)
{
blockRectArray.pop();
}
else
{
modifyRect.width -= lastRect.width;
//if this is RTL, we need to shift the selection block over by the amount
//we reduced it.
if(direction == Direction.RTL)
modifyRect.left -= lastRect.width;
}
}
else
{
//if they have the same height, simply remove the last block
if(modifyRect.height == lastRect.height)
{
blockRectArray.pop();
}
else
{
modifyRect.height -= lastRect.height;
//if this is RTL, we need to shift the selection block down by the amount
//we reduced it.
if(direction == Direction.RTL)
modifyRect.top += lastRect.height;
}
}
}
}
}
normalizeRects(blockRectArray, rectArray, largestRise, blockProgression, direction);
//add in the TCY Rects
if(tcyDrawRects && tcyDrawRects.length > 0)
{
for(var tcyIter:int = 0; tcyIter < tcyDrawRects.length; ++tcyIter)
{
rectArray.push(tcyDrawRects[tcyIter]);
}
}
//float selections do not normalize, put them into the rect array now
if(floatRectArray)
{
for(var floatIter:int = 0; floatIter < floatRectArray.length; ++floatIter)
{
rectArray.push(floatRectArray[floatIter]);
}
}
}
private function createSelectionShapes(selObj:Shape, selFormat:SelectionFormat, container:DisplayObject, begIdx:int, endIdx:int, prevLine:TextFlowLine, nextLine:TextFlowLine):void
{
var contElement:ContainerFormattedElement = _para.getAncestorWithContainer();
CONFIG::debug { assert(contElement != null,"para with no container"); }
var blockProgression:String = contElement.computedFormat.blockProgression;
var selCache:SelectionCache = getSelectionShapesCacheEntry(begIdx, endIdx, prevLine, nextLine, blockProgression);
if (!selCache)
return;
//iterate the blocks and create DisplayObjects to draw...
var drawRect:Rectangle;
var color:uint = selFormat.rangeColor;
if (_para && _para.getTextFlow()) {
var selMgr:ISelectionManager = _para.getTextFlow().interactionManager;
if (selMgr && (selMgr.anchorPosition == selMgr.activePosition))
color = selFormat.pointColor;
}
for each (drawRect in selCache.selectionBlocks)
{
drawRect = drawRect.clone();
convertLineRectToContainer(drawRect, true);
createSelectionRect(selObj, color, drawRect.x, drawRect.y, drawRect.width, drawRect.height);
}
}
/** @private
* Get the height and vertical adjustment for the line's selection shape, assuming Western typographic rules
* where leading is included in selection.
* @return An array with two elements
* [0] height
* [1] vertical adjustment to counter 'align bottom' behavior. The remainder of the selection code assumes selection shape
* bottom is to be aligned with line descent. If this is not the case, vertical adjustment is set to an appropriate non-zero value.
*/
tlf_internal function getRomanSelectionHeightAndVerticalAdjustment (prevLine:TextFlowLine, nextLine:TextFlowLine):Array
{
var rectHeight:Number = 0;
var verticalAdj:Number = 0; // Default to 'align bottom'.
//This code erroneously assumed that it would only be called with a SPACE justifier and that AUTO would be up. That is incorrect
//because some scripts, like Korean, use an up leading model and the EAST_ASIAN justifier. New code just performs the check
if(ParagraphElement.useUpLeadingDirection(_para.getEffectiveLeadingModel()))
{
// "Space above, align bottom"
// 1) Space above as dictated by first baseline offset for the first line or line leading otherwise (both obtained from the 'height' data member)
// 2) Selection rectangle must at least include all of the text area
rectHeight = Math.max(height, textHeight);
// 3) Selection rectangle's bottom aligned with line descent; verticalAdj remains 0
}
else
{
// TODO-9/4/08-Is this the right way to check for first/last lines?
var isFirstLine:Boolean = !prevLine || prevLine.controller != controller || prevLine.columnIndex != columnIndex;
var isLastLine:Boolean = !nextLine || nextLine.controller != controller || nextLine.columnIndex != columnIndex
|| nextLine.paragraph.getEffectiveLeadingModel() == LeadingModel.ROMAN_UP;
//I'm removing this line as it makes the assumption that AUTO leading dir is UP only for Roman text, which is incorrect.
//Korean also uses UP leading but uses the EastAsian justifier. - gak 01.22.09
//||(nextLine.paragraph.computedFormat.leadingDirection == LeadingDirection.AUTO && nextLine.paragraph.computedFormat.justificationRule == JustificationRule.SPACE);
if (isLastLine)
{
// There is no line after this one, or there is one which uses leading UP, so leading DOWN does not apply
if (!isFirstLine)
{
// "Space above None, align bottom" (eqivalently, "Space below None, align top"):
// 1) Only the text area should be selected
rectHeight = textHeight;
// 2) Selection rectangle's bottom aligned with line descent; verticalAdj remains 0
}
else
{
// "Space above, align bottom"
// 1) Space above as dictated by first baseline offset
// 2) Selection rectangle must at least include all of the text area
rectHeight = Math.max(height, textHeight);
// 3) Selection rectangle's bottom aligned with line descent; verticalAdj remains 0
}
}
else
{
// There is a line after this one, so leading DOWN applies
if (!isFirstLine)
{
// "Space below, align top"
// 1) Space below as dictated by line leading (obtained from 'height' member of next line)
// 2) Selection rectangle must at least include all of the text area
rectHeight = Math.max(nextLine.height,textHeight);
// 3) Selection rectangle's top to be aligned with line ascent, so its bottom to be at rectHeight - textLine.ascent,
// not textLine.descent, set verticalAdj accordingly
verticalAdj = rectHeight - textHeight; // same as rectHeight - textLine.ascent - textLine.descent
}
else
{
// Union of
// 1) first line, leading up: In this case, rectangle height is the larger of line height and text height,
// and the rectangle is shifted down by descent amount to align bottoms. So, top of rectangle is at:
var top:Number = _descent - Math.max(height,textHeight);
// 2) interior line, leading down: In this case, rectangle height is the larger of line leading and text height,
// and the rectangle is shifted up by ascent amount to align tops. So, bottom of rectangle is at:
var bottom:Number = Math.max(nextLine.height,textHeight) - _ascent;
rectHeight = bottom - top;
// 3) Selection rectangle's bottom to be at 'bottom', not the line's descent; set verticalAdj accordingly
verticalAdj = bottom - _descent;
}
}
}
//If we don't have a line above us, then we need to pad the line a bit as well as make it shift up.
//If we don't, then it overlaps the line below too much OR clips the top of the glyphs.
if(!prevLine || prevLine.columnIndex != this.columnIndex || prevLine.controller != this.controller)
{
//make it taller - this is kinda a fudge, but we have no info to determine a good top.
//if we don't do this, the selection rectangle will clip to the top of the glyphs and even
//let parts stick out a bit. So, re-add the descent and offset the rect by 50% so that
//it appears to balance the top and bottom.
rectHeight += this.descent;
verticalAdj = Math.floor(this.descent/2);
}
return [rectHeight, verticalAdj];
}
/** @private
*
*
*/
private function makeSelectionBlocks(textLine:TextLine, begIdx:int, endIdx:int, paraAbsStart:int, blockProgression:String, direction:String, heightAndAdj:Array):Array
{
CONFIG::debug{ assert(begIdx <= endIdx, "Selection indexes are reversed! How can this happen?!"); }
var blockArray:Array = new Array();
var blockRect:Rectangle = new Rectangle();
var startElem:FlowLeafElement = _para.findLeaf(begIdx);
var startMetrics:Rectangle = startElem.getComputedFontMetrics().emBox;
if (!textLine)
textLine = getTextLine(true);
//++makeBlockPassCounter;
//trace(makeBlockPassCounter + ") direction = " + direction + " blockProgression = " + blockProgression);
// CNW: removed whole line optimization 5/18/10 - was yielding different results than code below and was no faster
//trace(makeBlockPassCounter + ") begIdx = " + begIdx.toString() + " endIdx = " + endIdx.toString());
var begAtomIndex:int = textLine.getAtomIndexAtCharIndex(begIdx);
var endAtomIndex:int = adjustEndElementForBidi(textLine, begIdx, endIdx, begAtomIndex, direction);
//trace(makeBlockPassCounter + ") begAtomIndex = " + begAtomIndex.toString() + " endAtomIndex = " + endAtomIndex.toString());
CONFIG::debug{ assert(begAtomIndex >= 0, "Invalid start index! begIdx = " + begIdx)};
CONFIG::debug{ assert(endAtomIndex >= 0, "Invalid end index! begIdx = " + endIdx)};
if(direction == Direction.RTL && textLine.getAtomBidiLevel(endAtomIndex)%2 != 0)
{
//if we are in RTL, anchoring the LTR text gets tricky. Because the endElement is before the first
//element - which is why we're in this code - the result can be a zero-width rectangle if the span of LTR
//text breaks across line boundaries. If that is the case, then the endAtomIndex value will be 0. As
//this is the less common case, assume that it isn't and make all other cases come first
if (endAtomIndex == 0 && begIdx < endIdx-1)
{
//since the endAtomIndex is 0, meaning that the LTR spans lines,
//we want to grab the glyph before the endIdx which represents the last LTR glyph for the selection.
//Make a recursive call into makeSelectionBlocks using and endIdx decremented by 1.
blockArray = makeSelectionBlocks(textLine, begIdx, endIdx - 1, paraAbsStart, blockProgression, direction, heightAndAdj);
var bidiBlock:Array = makeSelectionBlocks(textLine, endIdx - 1, endIdx - 1, paraAbsStart, blockProgression, direction, heightAndAdj)
var bidiBlockIter:int = 0;
while(bidiBlockIter < bidiBlock.length)
{
blockArray.push(bidiBlock[bidiBlockIter]);
++bidiBlockIter;
}
return blockArray;
}
}
var begIsBidi:Boolean = begAtomIndex != -1 ? isAtomBidi(textLine, begAtomIndex, direction) : false;
var endIsBidi:Boolean = endAtomIndex != -1 ? isAtomBidi(textLine, endAtomIndex, direction) : false;
//trace("begAtomIndex is bidi = " + begIsBidi.toString());
//trace("endAtomIndex is bidi = " + endIsBidi.toString());
if(begIsBidi || endIsBidi)
{
//this code needs to iterate over the glyphs starting at the begAtomIndex and going forward.
//It doesn't matter is beg is bidi or not, we need to find a boundary, create a rect on it, then proceded.
//use the value of begIsBidi for testing the consistency of the selection.
//Example bidi text. Note that the period comes at the left end of the line:
//
// Bidi state: f f t t t t t (true/false)
// Element Index:0 1 2 3 4 5 6 (0 is the para terminator)
// Chars: . t o _ b e
// Flow Index: 6 0 1 2 3 4 (5) Note that these numbers represent the space between glyphs AND
// 5(f) that index 5 is both the space after the e and before the period.
// but, the position 5 is not a valid cursor location.
//the original code I implemented used the beg and endElement indexes however that fails because when the text
//is mixed bidi/non-bidi, the indexes are only 1 char apart. This resulted in, for example, only the period in
//a line getting selected when the text was bidi. Instead, we're going to use the begIdx and endIdx and
//recalculate the element indexes each time. This is expensive, but I don't see an alternative. - gak 09.05.08
var curIdx:int = begIdx;
var incrementor:int = (begIdx != endIdx ? 1 : 0);
//the indexes used to draw the seleciton. activeStart/End represent the
//beginning of the selection shape atoms, while cur is the one we are testing.
var activeStartIndex:int = begAtomIndex;
var activeEndIndex:int = begAtomIndex;
var curElementIndex:int = begAtomIndex;
//when activeEndIsBidi no longer matches the bidi setting for the activeStartIndex, we will create the shape
var activeEndIsBidi:Boolean = begIsBidi;
do
{
//increment the index
curIdx += incrementor;
//get the next atom index
curElementIndex = textLine.getAtomIndexAtCharIndex(curIdx);
//calculate the bidi level for the - kinda cludgy, but if the bidi-text wraps, curElementIndex == -1
//so just set it to false if this is the case. It will get ignored in the subsequent check and curIdx
//will == endIdx as this is the last glyph in the line - which is why the next is -1 - gak 09.12.08
var curIsBidi:Boolean = (curElementIndex != -1) ? isAtomBidi(textLine, curElementIndex, direction) : false;
if(curElementIndex != -1 && curIsBidi != activeEndIsBidi)
{
blockRect = makeBlock(textLine, curIdx, activeStartIndex, activeEndIndex, startMetrics, blockProgression, direction, heightAndAdj);
blockArray.push(blockRect);
//shift the activeStart/End indexes to the current
activeStartIndex = curElementIndex;
activeEndIndex = curElementIndex;
//update the bidi setting
activeEndIsBidi = curIsBidi;
}
else
{
//we don't get another chance to make a block, so if this is the last char, make the block before we bail out.
//we have to check both equality and equality plus the incrementor because if we don't, then we'll miss a
//character in the selection.
if(curIdx == endIdx)
{
blockRect = makeBlock(textLine, curIdx, activeStartIndex, activeEndIndex, startMetrics, blockProgression, direction, heightAndAdj);
blockArray.push(blockRect);
}
activeEndIndex = curElementIndex;
}
}while(curIdx < endIdx)
}
else
{
var testILG:InlineGraphicElement = startElem as InlineGraphicElement;
if(!testILG || testILG.effectiveFloat == Float.NONE || begIdx == endIdx )
{
blockRect = makeBlock(textLine, begIdx, begAtomIndex, endAtomIndex, startMetrics, blockProgression, direction, heightAndAdj);
if (testILG && testILG.elementWidthWithMarginsAndPadding() != testILG.elementWidth)
{ // Don't include margins or padding around inlines in the bounds
var verticalText:Boolean = testILG.getTextFlow().computedFormat.blockProgression == BlockProgression.RL;
var ilgFormat:ITextLayoutFormat = testILG.computedFormat;
if (verticalText)
{
var paddingTop:Number = testILG.getEffectivePaddingTop();
blockRect.top += paddingTop; // don't include the left side indent in the selected area
var paddingBottom:Number = testILG.getEffectivePaddingBottom();
blockRect.bottom -= paddingBottom;
}
else
{
var paddingLeft:Number = testILG.getEffectivePaddingLeft();
blockRect.left += paddingLeft; // don't include the left side indent in the selected area
var paddingRight:Number = testILG.getEffectivePaddingRight();
blockRect.right -= paddingRight;
}
}
}
else
{
blockRect = testILG.graphic.getBounds(textLine);
}
blockArray.push(blockRect);
}
return blockArray;
}
/** @private
*
*
*/
private function makeBlock(textLine:TextLine, begTextIndex:int, begAtomIndex:int, endAtomIndex:int, startMetrics:Rectangle, blockProgression:String, direction:String, heightAndAdj:Array):Rectangle
{
var blockRect:Rectangle = new Rectangle();
var globalStart:Point = new Point(0,0);
if(begAtomIndex > endAtomIndex)
{
// swap the start and end
var tempEndIdx:int = endAtomIndex;
endAtomIndex = begAtomIndex;
begAtomIndex = tempEndIdx;
}
if (!textLine)
textLine = getTextLine(true);
//now that we have elements and they are in the right order for drawing, get their rectangles
var begCharRect:Rectangle = textLine.getAtomBounds(begAtomIndex);
//trace(makeBlockPassCounter + ") begCharRect = " + begCharRect.toString());
var endCharRect:Rectangle = textLine.getAtomBounds(endAtomIndex);
//trace(makeBlockPassCounter + ") endCharRect = " + endCharRect.toString());
//Calculate the justificationRule value
var justRule:String = _para.getEffectiveJustificationRule();
//If this is TTB text and NOT TCY, as indicated by TextRotation.rotate0...
if(blockProgression == BlockProgression.RL && textLine.getAtomTextRotation(begAtomIndex) != TextRotation.ROTATE_0)
{
globalStart.y = begCharRect.y;
blockRect.height = begAtomIndex != endAtomIndex ? endCharRect.bottom - begCharRect.top : begCharRect.height;
//re-ordered this code. EAST_ASIAN is more common in vertical and should be the first option.
if(justRule == JustificationRule.EAST_ASIAN)
{
blockRect.width = begCharRect.width;
}
else
{
blockRect.width = heightAndAdj[0];
globalStart.x -= heightAndAdj[1];
}
}
else
{
//given bidi text alternations, the endCharRect could be left of the begCharRect,
//use whichever is farther left.
globalStart.x = Math.min(begCharRect.x,endCharRect.x);
//if we're here and the BlockProgression is TTB, then we're TCY. Less frequent case, so make non-TCY
//the first option...
//NB - Never use baseline adjustments for TCY. They don't make sense here.(I think) - gak 06.03.08
if(blockProgression == BlockProgression.RL)
globalStart.y = begCharRect.y + (startMetrics.width /2); // TODO-9/5/8:Behavior for leading down TBD
if(justRule != JustificationRule.EAST_ASIAN)
{
blockRect.height = heightAndAdj[0];
if(blockProgression == BlockProgression.RL)
globalStart.x -= heightAndAdj[1];
else
globalStart.y += heightAndAdj[1];
//changed the width from a default of 2 to use the begCharRect.width so that point seletion
//can choose to use the right or left side of the glyph when drawing a caret Watson 1876415/1876953- gak 08.19.09
blockRect.width = begAtomIndex != endAtomIndex ? Math.abs(endCharRect.right - begCharRect.left) : begCharRect.width;
}
else
{
blockRect.height = begCharRect.height;
//changed the width from a default of 2 to use the begCharRect.width so that point seletion
//can choose to use the right or left side of the glyph when drawing a caret Watson 1876415/1876953- gak 08.19.09
blockRect.width = begAtomIndex != endAtomIndex ? Math.abs(endCharRect.right - begCharRect.left) : begCharRect.width;
}
}
blockRect.x = globalStart.x;
blockRect.y = globalStart.y;
if(blockProgression == BlockProgression.RL)
{
if(textLine.getAtomTextRotation(begAtomIndex) != TextRotation.ROTATE_0)
blockRect.x -= textLine.descent;
else //it's TCY
blockRect.y -= (blockRect.height / 2)
}
else
{
blockRect.y += (textLine.descent - blockRect.height);
}
var tfl:TextFlowLine = textLine.userData as TextFlowLine;
var curElem:FlowLeafElement = _para.findLeaf(begTextIndex);
var rotation:String;
if (!curElem)
{
if (begTextIndex < 0)
curElem = _para.getFirstLeaf();
else if (begTextIndex >= _para.textLength)
curElem = _para.getLastLeaf();
rotation = curElem ? curElem.computedFormat.textRotation : TextRotation.ROTATE_0;
}
else
rotation = curElem.computedFormat.textRotation;
//handle rotation. For horizontal text, rotations of 90 or 180 cause the text
//to draw under the baseline in a cosistent location. Vertical text is a bit more complicated
//in that a 90 rotation puts it immediately to the left of the Em Box, whereas 180 is one quarter
//of the way in the Em Box. Fix for Watson 1915930 - gak 02.17.09
if(rotation == TextRotation.ROTATE_180 ||
rotation == TextRotation.ROTATE_90)
{
if(blockProgression != BlockProgression.RL)
blockRect.y += (blockRect.height / 2);
else
{
if(curElem.getParentByType(TCYElement) == null)
{
if(rotation == TextRotation.ROTATE_90)
blockRect.x -= blockRect.width;
else
blockRect.x -= (blockRect.width * .75);
}
else
{
if(rotation == TextRotation.ROTATE_90)
blockRect.y += blockRect.height;
else
blockRect.y += (blockRect.height * .75);
}
}
}
return blockRect;
}
/** @private
*
*
*/
tlf_internal function convertLineRectToContainer(rect:Rectangle, constrainShape:Boolean):void
{
var textLine:TextLine = getTextLine();
/* var globalStart:Point = new Point(rect.x, rect.y);
//convert to controller coordinates...
////trace(makeBlockPassCounter + ") globalStart = " + globalStart.toString());
globalStart = textLine.localToGlobal(globalStart);
////trace(makeBlockPassCounter + ") localToGlobal.globalStart = " + globalStart.toString());
globalStart = container.globalToLocal(globalStart);
////trace(makeBlockPassCounter + ") globalToLocal.globalStart = " + globalStart.toString());
rect.x = globalStart.x;
rect.y = globalStart.y; */
// this is much simpler and actually more accurate - localToGlobal/globalToLocal does some rounding
rect.x += textLine.x;
rect.y += textLine.y;
if (constrainShape)
{
var tf:TextFlow = _para.getTextFlow();
var columnRect:Rectangle = controller.columnState.getColumnAt(this.columnIndex);
constrainRectToColumn(tf,rect,columnRect,controller.horizontalScrollPosition,controller.verticalScrollPosition,controller.compositionWidth,controller.compositionHeight);
}
}
/** @private */
static tlf_internal function constrainRectToColumn(tf:TextFlow,rect:Rectangle,columnRect:Rectangle,hScrollPos:Number,vScrollPos:Number,compositionWidth:Number,compositionHeight:Number):void
{
if ( columnRect == null )
return;
if(tf.computedFormat.lineBreak == LineBreak.EXPLICIT)
return;
var bp:String = tf.computedFormat.blockProgression;
var direction:String = tf.computedFormat.direction;
if(bp == BlockProgression.TB && !isNaN(compositionWidth))
{
if(direction == Direction.LTR)
{
//make sure is doesn't go past the end of the container
if(rect.left > (columnRect.x + columnRect.width + hScrollPos))
rect.left = (columnRect.x + columnRect.width + hScrollPos);
//make sure that if this is a selection and not a point selection, that
//we don't go beyond the end of the container...
if(rect.right > (columnRect.x + columnRect.width + hScrollPos))
rect.right = (columnRect.x + columnRect.width + hScrollPos);
}
else
{
if(rect.right < (columnRect.x + hScrollPos))
rect.right = (columnRect.x + hScrollPos);
if(rect.left < (columnRect.x + hScrollPos))
rect.left = (columnRect.x + hScrollPos);
}
}
else if (bp == BlockProgression.RL && !isNaN(compositionHeight))
{
if(direction == Direction.LTR)
{
//make sure is doesn't go past the end of the container
if(rect.top > (columnRect.y + columnRect.height + vScrollPos))
rect.top = (columnRect.y + columnRect.height + vScrollPos);
//make sure that if this is a selection and not a point selection, that
//we don't go beyond the end of the container...
if(rect.bottom > (columnRect.y + columnRect.height + vScrollPos))
rect.bottom = (columnRect.y + columnRect.height + vScrollPos);
}
else
{
if(rect.bottom < (columnRect.y + vScrollPos))
rect.bottom = (columnRect.y + vScrollPos);
if(rect.top < (columnRect.y + vScrollPos))
rect.top = (columnRect.y + vScrollPos);
}
}
}
/** @private
* Helper method to hilight the portion of a block selection on this TextLine. A selection display is created and added to the line's TextFrame with ContainerController addSelectionShape.
* @param begIdx absolute index of start of selection on this line.
* @param endIdx absolute index of end of selection on this line.
*/
tlf_internal function hiliteBlockSelection(selObj:Shape, selFormat:SelectionFormat, container:DisplayObject, begIdx:int,endIdx:int, prevLine:TextFlowLine, nextLine:TextFlowLine):void
{
// no container for overflow lines, or lines scrolled out
if (isDamaged() || !_controller)
return;
var textLine:TextLine = peekTextLine();
if (!textLine || !textLine.parent)
return;
var paraStart:int = _para.getAbsoluteStart();
begIdx -= paraStart;
endIdx -= paraStart;
createSelectionShapes(selObj, selFormat, container, begIdx, endIdx, prevLine, nextLine);
}
/** @private
* Helper method to hilight a point selection on this TextLine. x,y,w,h of the selection are calculated and ContainerController.drawPointSelection is called
* @param idx absolute index of the point selection.
*/
tlf_internal function hilitePointSelection(selFormat:SelectionFormat, idx:int, container:DisplayObject, prevLine:TextFlowLine, nextLine:TextFlowLine):void
{
var rect:Rectangle = computePointSelectionRectangle(idx,container,prevLine,nextLine, true);
if (rect)
_controller.drawPointSelection(selFormat, rect.x, rect.y, rect.width, rect.height)
}
static private function setRectangleValues(rect:Rectangle,x:Number,y:Number,width:Number,height:Number):void
{
rect.x = x;
rect.y = y;
rect.width = width;
rect.height = height;
}
static private const localZeroPoint:Point = new Point(0,0);
static private const localOnePoint:Point = new Point(1,0);
static private const rlLocalOnePoint:Point = new Point(0,1);
/** @private */
tlf_internal function computePointSelectionRectangle(idx:int, container:DisplayObject, prevLine:TextFlowLine, nextLine:TextFlowLine, constrainSelRect:Boolean):Rectangle
{
if (isDamaged() || !_controller)
return null;
// no container for overflow lines, or lines scrolled out
var textLine:TextLine = peekTextLine();
if (!textLine || !textLine.parent)
return null;
// adjust to this paragraph's TextBlock
idx -= _para.getAbsoluteStart();
textLine = getTextLine(true);
//endIdx will only differ if idx is altered when detecting TCY bounds
var endIdx:int = idx;
var elementIndex:int = textLine.getAtomIndexAtCharIndex(idx);
CONFIG::debug{ assert(elementIndex != -1, "Invalid point selection index! idx = " + idx); }
var isTCYBounds:Boolean = false;
var paraLeadingTCY:Boolean = false;
var contElement:ContainerFormattedElement = _para.getAncestorWithContainer();
CONFIG::debug { assert(contElement != null,"para with no container"); }
var blockProgression:String = contElement.computedFormat.blockProgression;
var direction:String = _para.computedFormat.direction;
//need to check for TCY. TCY cannot take input into it's head, but can in it's tail.
if(blockProgression == BlockProgression.RL)
{
if (idx == 0)
{
if(textLine.getAtomTextRotation(0) == TextRotation.ROTATE_0)
paraLeadingTCY = true;
}
else
{
var prevElementIndex:int = textLine.getAtomIndexAtCharIndex(idx - 1);
if(prevElementIndex != -1)
{
//if this elem is TCY, then we need to back up one space and use the right bounds
if(textLine.getAtomTextRotation(elementIndex) == TextRotation.ROTATE_0 &&
textLine.getAtomTextRotation(prevElementIndex) != TextRotation.ROTATE_0)
{
elementIndex = prevElementIndex;
--idx;
isTCYBounds = true;
}
else if(textLine.getAtomTextRotation(prevElementIndex) == TextRotation.ROTATE_0)
{
elementIndex = prevElementIndex;
--idx;
isTCYBounds = true;
}
}
}
}
var heightAndAdj:Array = getRomanSelectionHeightAndVerticalAdjustment(prevLine, nextLine);
var blockRectArray:Array = makeSelectionBlocks(textLine, idx, endIdx, _para.getAbsoluteStart(), blockProgression, direction, heightAndAdj);
CONFIG::debug{ assert(blockRectArray.length == 1, "A point selection should return a single selection rectangle!"); }
var rect:Rectangle = blockRectArray[0];
convertLineRectToContainer(rect, constrainSelRect);
var drawOnRight:Boolean = (direction == Direction.RTL);
if((drawOnRight && textLine.getAtomBidiLevel(elementIndex) % 2 == 0) ||
(!drawOnRight && textLine.getAtomBidiLevel(elementIndex) % 2 != 0))
{
drawOnRight = !drawOnRight;
}
// compute a width so that cursor is visually one pixel wide independent of scaling
var zeroPoint:Point = container.localToGlobal(localZeroPoint);
var cursorWidth:Number;
if(blockProgression == BlockProgression.RL && textLine.getAtomTextRotation(elementIndex) != TextRotation.ROTATE_0)
{
var rlOnePoint:Point = container.localToGlobal(rlLocalOnePoint);
cursorWidth = zeroPoint.y - rlOnePoint.y;
cursorWidth = cursorWidth == 0 ? 1 : Math.abs(1.0/cursorWidth);
// trace(zeroPoint,onePoint,cursorSize);
if(!drawOnRight)
setRectangleValues(rect, rect.x, !isTCYBounds ? rect.y : rect.y + rect.height,rect.width,cursorWidth);
else
setRectangleValues(rect, rect.x, !isTCYBounds ? rect.y + rect.height : rect.y ,rect.width,cursorWidth);
}
else
{
var onePoint:Point = container.localToGlobal(localOnePoint);
cursorWidth = zeroPoint.x - onePoint.x;
cursorWidth = cursorWidth == 0 ? 1 : Math.abs(1.0/cursorWidth);
// trace(zeroPoint,onePoint,cursorSize);
//choose to use the right or left side of the glyph based on Direction when drawing a caret Watson 1876415/1876953
//if the direction is ltr, then the cursor should be on the left side
if(!drawOnRight)
setRectangleValues(rect, !isTCYBounds ? rect.x : rect.x + rect.width, rect.y, cursorWidth, rect.height);
else //otherwise, it should be on the right, unless it is TCY
setRectangleValues(rect, !isTCYBounds ? rect.x + rect.width : rect.x, rect.y, cursorWidth, rect.height);
}
//allow the atoms to be garbage collected.
textLine.flushAtomData();
return rect;
}
/** @private
* Three states. Disjoint(0), Intersects(1), HeightContainedIn(2),
*/
tlf_internal function selectionWillIntersectScrollRect(scrollRect:Rectangle, begIdx:int, endIdx:int, prevLine:TextFlowLine, nextLine:TextFlowLine):int
{
var contElement:ContainerFormattedElement = _para.getAncestorWithContainer();
CONFIG::debug { assert(contElement != null,"para with no container"); }
var blockProgression:String = contElement.computedFormat.blockProgression;
var textLine:TextLine = getTextLine(true);
if (begIdx == endIdx)
{
var pointSelRect:Rectangle = computePointSelectionRectangle(begIdx, DisplayObject(controller.container), prevLine, nextLine, false);
if (pointSelRect)
{
if (scrollRect.containsRect(pointSelRect))
return 2;
if (scrollRect.intersects(pointSelRect))
return 1;
}
}
else
{
var paraStart:int = _para.getAbsoluteStart();
var selCache:SelectionCache = this.getSelectionShapesCacheEntry(begIdx-paraStart,endIdx-paraStart,prevLine,nextLine,blockProgression);
if (selCache)
{
//iterate the blocks and check for intersections
var drawRect:Rectangle;
for each (drawRect in selCache.selectionBlocks)
{
drawRect = drawRect.clone();
// convertLineRectToContainer(container, drawRect);
drawRect.x += textLine.x;
drawRect.y += textLine.y;
if (scrollRect.intersects(drawRect))
{
if(blockProgression == BlockProgression.RL)
{
// see if width is entirely contained in scrollRect
if (drawRect.left >= scrollRect.left && drawRect.right <= scrollRect.right)
return 2;
}
else
{
if (drawRect.top >= scrollRect.top && drawRect.bottom <= scrollRect.bottom)
return 2;
}
return 1;
}
}
}
}
return 0;
}
/** @private */
CONFIG::debug private static function dumpAttribute(result:String, attributeName:String, attributeValue:Object):String
{
if (attributeValue)
{
result += " ";
result += attributeName;
result += "=\"";
result += attributeValue.toString();
result += "\""
}
return result;
}
/** @private
*/
private function normalizeRects(srcRects:Array, dstRects:Array, largestRise:Number, blockProgression:String, direction:String):void
{
//the last rectangle in the list with a potential to merge
var lastRect:Rectangle = null;
var rectIter:int = 0;
while(rectIter < srcRects.length)
{
//get the current rect and advance the iterator
var rect:Rectangle = srcRects[rectIter++];
//apply a new height if needed.
if(blockProgression == BlockProgression.RL)
{
if(rect.width < largestRise)
{
rect.width = largestRise;
}
}
else
{
if(rect.height < largestRise)
{
rect.height = largestRise;
}
}
//if the lastRect is null, no need to perform calculation
if(lastRect == null)
{
lastRect = rect;
}
else
{
//TCY has already been excluded, so no need to worry about it here...
if(blockProgression == BlockProgression.RL)
{
//trace(normalCounter + ") lastRect = " + lastRect.toString());
//trace(normalCounter + ") rect = " + rect.toString());
//merge it in to the last rect
if(lastRect.y < rect.y && (lastRect.y + lastRect.height) >= rect.top && lastRect.x == rect.x)
{
lastRect.height += rect.height;
}
else if(rect.y < lastRect.y && lastRect.y <= rect.bottom && lastRect.x == rect.x)
{
lastRect.height += rect.height;
lastRect.y = rect.y;
}
else
{
//we have a break in the rectangles and should push last rect onto the draw list before continuing
dstRects.push(lastRect);
lastRect = rect;
}
}
else
{
if(lastRect.x < rect.x && (lastRect.x + lastRect.width) >= rect.left && lastRect.y == rect.y)
{
lastRect.width += rect.width;
}
else if(rect.x < lastRect.x && lastRect.x <= rect.right && lastRect.y == rect.y)
{
lastRect.width += rect.width;
lastRect.x = rect.x;
}
else
{
//we have a break in the rectangles and should push last rect onto the draw list before continuing
dstRects.push(lastRect);
lastRect = rect;
}
}
}
//if this is the last rectangle, we haven't added it, do so now.
if(rectIter == srcRects.length)
dstRects.push(lastRect);
}
}
/** @private */
private function adjustEndElementForBidi(textLine:TextLine, begIdx:int, endIdx:int, begAtomIndex:int, direction:String):int
{
var endAtomIndex:int = begAtomIndex;
if(endIdx != begIdx)
{
if(((direction == Direction.LTR && textLine.getAtomBidiLevel(begAtomIndex)%2 != 0)
|| (direction == Direction.RTL && textLine.getAtomBidiLevel(begAtomIndex)%2 == 0))
&& textLine.getAtomTextRotation(begAtomIndex) != TextRotation.ROTATE_0)
endAtomIndex = textLine.getAtomIndexAtCharIndex(endIdx);
else
{
endAtomIndex = textLine.getAtomIndexAtCharIndex(endIdx - 1);
}
}
if(endAtomIndex == -1 && endIdx > 0)
{
return adjustEndElementForBidi(textLine, begIdx, endIdx - 1, begAtomIndex, direction);
}
return endAtomIndex;
}
/** @private */
private function isAtomBidi(textLine:TextLine, elementIdx:int, direction:String):Boolean
{
if (!textLine)
textLine = getTextLine(true);
return (textLine.getAtomBidiLevel(elementIdx)%2 != 0 && direction == Direction.LTR) || (textLine.getAtomBidiLevel(elementIdx)%2 == 0 && direction == Direction.RTL);
}
/** @private */
tlf_internal function get adornCount():int
{ return _adornCount; }
/** @private */
static tlf_internal function findNumberLine(textLine:TextLine):TextLine
{
// not always going to be a numberLine - listStyleType may be "none"
// have to hunt for it because inlinegraphics get pushed at the beginning
// risk here is that clients decorate TextLines with other TextLines.
for (var idx:int = 0; idx < textLine.numChildren; idx++)
{
var numberLine:TextLine = textLine.getChildAt(idx) as TextLine;
if (numberLine && (numberLine.userData is NumberLineUserData))
break;
}
return numberLine;
}
/** @private */
static tlf_internal function getNumberLineListStylePosition(numberLine:TextLine):String
{ return (numberLine.userData as NumberLineUserData).listStylePosition; }
/** @private */
static tlf_internal function getNumberLineInsideLineWidth(numberLine:TextLine):Number
{ return (numberLine.userData as NumberLineUserData).insideLineWidth; }
/** @private */
static tlf_internal function getNumberLineSpanFormat(numberLine:TextLine):ITextLayoutFormat
{ return (numberLine.userData as NumberLineUserData).spanFormat; }
/** @private */
static tlf_internal function getNumberLineParagraphDirection(numberLine:TextLine):String
{ return (numberLine.userData as NumberLineUserData).paragraphDirection; }
/** @private */
static tlf_internal function setListEndIndent(numberLine:TextLine,listEndIndent:Number):void
{ (numberLine.userData as NumberLineUserData).listEndIndent = listEndIndent; }
/** @private */
static tlf_internal function getListEndIndent(numberLine:TextLine):Number
{ return (numberLine.userData as NumberLineUserData).listEndIndent; }
/** @private */
static tlf_internal function setNumberLineBackground(numberLine:TextLine,background:BackgroundManager):void
{ (numberLine.userData as NumberLineUserData).backgroundManager = background; }
/** @private */
static tlf_internal function getNumberLineBackground(numberLine:TextLine):BackgroundManager
{ return (numberLine.userData as NumberLineUserData).backgroundManager; }
/** @private */
CONFIG::debug public function dumpToXML():String
{
var result:String = new String("<line");
result = dumpAttribute(result, "absoluteStart", absoluteStart);
result = dumpAttribute(result, "textLength", textLength);
result = dumpAttribute(result, "height", height);
result = dumpAttribute(result, "spaceBefore", spaceBefore);
result = dumpAttribute(result, "spaceAfter", spaceAfter);
result = dumpAttribute(result, "location", location);
result = dumpAttribute(result, "x", x);
result = dumpAttribute(result, "y", y);
result = dumpAttribute(result, "targetWidth", targetWidth);
result = dumpAttribute(result, "lineOffset", _lineOffset);
result += ">\n";
var textLine:TextLine = getTextLine(true);
result += "<TextBlock>";
result += textLine.textBlock.dump();
result += "</TextBlock>";
result += "<TextLine>"
result += textLine.dump();
result += "</TextLine>"
result += "</line>";
return result;
}
};
}
import flash.display.Shape;
import flash.geom.Rectangle;
import flashx.textLayout.edit.ISelectionManager;
import flashx.textLayout.formats.ITextLayoutFormat;
/** @private - I would have defined this as tlf_internal, but that is not an option, so
* making it private.
*
* The SelectionCache is a structure designed to hold a few key data points needed to quickly
* reconstruct a selection on a line:
*
* a) the beginning and end indicies of the selection on the line
* b) the regular selection rectangles
* c) the irregular selection rectangles, such as TCY selection rects in vertical text
*
**/
final class SelectionCache
{
private var _begIdx:int = -1;
private var _endIdx:int = -1;
private var _selectionBlocks:Array = null;
public function SelectionCache()
{
}
public function get begIdx():int
{ return _begIdx; }
public function set begIdx(val:int):void
{ _begIdx = val; }
public function get endIdx():int
{ return _endIdx; }
public function set endIdx(val:int):void
{ _endIdx = val; }
public function pushSelectionBlock(drawRect:Rectangle):void
{
if(!_selectionBlocks)
_selectionBlocks = new Array();
_selectionBlocks.push(drawRect.clone());
}
public function get selectionBlocks():Array
{ return _selectionBlocks; }
public function clear():void
{
_selectionBlocks = null;
_begIdx = -1;
_endIdx = -1;
}
}
import flashx.textLayout.elements.BackgroundManager;
class NumberLineUserData
{
public function NumberLineUserData(listStylePosition:String,insideLineWidth:Number,spanFormat:ITextLayoutFormat,paraDirection:String)
{
this.listStylePosition = listStylePosition;
//added by yong
this.insideLineWidth = insideLineWidth;
this.spanFormat = spanFormat;
this.paragraphDirection = paraDirection;
}
public var listStylePosition:String;
public var insideLineWidth:Number;
public var spanFormat:ITextLayoutFormat;
public var paragraphDirection:String;
public var listEndIndent:Number;
public var backgroundManager:BackgroundManager;
}
import flash.text.engine.TextBlock;
import flash.text.engine.TextLine;
import flash.text.engine.TextLineValidity;
import flashx.textLayout.debug.Debugging;
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.factory.StringTextLineFactory;
import flashx.textLayout.formats.BlockProgression;
import flashx.textLayout.tlf_internal;
import flashx.textLayout.formats.TextLayoutFormat;
use namespace tlf_internal;
class NumberLineFactory extends StringTextLineFactory
{
private var _listStylePosition:String;
private var _markerFormat:ITextLayoutFormat;
private var _backgroundManager:BackgroundManager;
public function get listStylePosition():String
{ return _listStylePosition; }
public function set listStylePosition(val:String):void
{ _listStylePosition = val; }
public function get markerFormat():ITextLayoutFormat
{ return _markerFormat; }
public function set markerFormat(val:ITextLayoutFormat):void
{
_markerFormat = val;
spanFormat = val;
}
public function get backgroundManager():BackgroundManager
{ return _backgroundManager; }
public function clearBackgroundManager():void
{ _backgroundManager = null; }
/** @private */
static tlf_internal function calculateInsideNumberLineWidth(numberLine:TextLine,bp:String):Number
{
var minVal:Number = Number.MAX_VALUE;
var maxVal:Number = Number.MIN_VALUE;
var idx:int = 0;
var rect:Rectangle;
if (bp == BlockProgression.TB)
{
for (; idx < numberLine.atomCount; idx++)
{
//trace(idx,numberLine.getAtomTextBlockBeginIndex(idx),numberLine.getAtomBounds(idx));
if (numberLine.getAtomTextBlockBeginIndex(idx) != numberLine.rawTextLength-1)
{
rect = numberLine.getAtomBounds(idx);
minVal = Math.min(minVal,rect.x);
maxVal = Math.max(maxVal,rect.right);
}
}
}
else
{
for (; idx < numberLine.atomCount; idx++)
{
//trace(idx,numberLine.getAtomTextBlockBeginIndex(idx),numberLine.getAtomBounds(idx));
if (numberLine.getAtomTextBlockBeginIndex(idx) != numberLine.rawTextLength-1)
{
rect = numberLine.getAtomBounds(idx);
minVal = Math.min(minVal,rect.top);
maxVal = Math.max(maxVal,rect.bottom);
}
}
}
numberLine.flushAtomData();
//trace("textWidth",numberLine.textWidth,maxVal-minVal);
return maxVal > minVal ? maxVal-minVal : 0;
}
protected override function callbackWithTextLines(callback:Function,delx:Number,dely:Number):void
{
for each (var textLine:TextLine in _factoryComposer._lines)
{
textLine.userData = new NumberLineUserData(listStylePosition,calculateInsideNumberLineWidth(textLine,textFlowFormat.blockProgression),_markerFormat,paragraphFormat.direction);
var textBlock:TextBlock = textLine.textBlock;
if (textBlock)
{
CONFIG::debug { Debugging.traceFTECall(null,textBlock,"releaseLines",textBlock.firstLine, textBlock.lastLine); }
textBlock.releaseLines(textBlock.firstLine,textBlock.lastLine);
}
textLine.x += delx;
textLine.y += dely;
textLine.validity = TextLineValidity.STATIC;
CONFIG::debug { Debugging.traceFTEAssign(textLine,"validity",TextLineValidity.STATIC); }
callback(textLine);
}
}
// save the backgroundManager for later use. generate the background when the TextLine is placed
tlf_internal override function processBackgroundColors(textFlow:TextFlow,callback:Function,x:Number,y:Number,constrainWidth:Number,constrainHeight:Number):*
{
_backgroundManager = textFlow.backgroundManager;
textFlow.clearBackgroundManager();
return;
}
}