scaffold.libs_as.feathers.controls.text.TextBlockTextRenderer.as Maven / Gradle / Ivy
/*
Feathers
Copyright 2012-2015 Bowler Hat LLC. All Rights Reserved.
This program is free software. You can redistribute and/or modify it in
accordance with the terms of the accompanying license agreement.
*/
package feathers.controls.text
{
import feathers.core.FeathersControl;
import feathers.core.IStateContext;
import feathers.core.IStateObserver;
import feathers.core.ITextRenderer;
import feathers.core.IToggle;
import feathers.events.FeathersEventType;
import feathers.skins.IStyleProvider;
import feathers.utils.display.stageToStarling;
import feathers.utils.geom.matrixToScaleX;
import feathers.utils.geom.matrixToScaleY;
import flash.display.BitmapData;
import flash.display.DisplayObjectContainer;
import flash.display.Sprite;
import flash.display3D.Context3DProfile;
import flash.filters.BitmapFilter;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.text.engine.ContentElement;
import flash.text.engine.ElementFormat;
import flash.text.engine.FontDescription;
import flash.text.engine.SpaceJustifier;
import flash.text.engine.TabStop;
import flash.text.engine.TextBaseline;
import flash.text.engine.TextBlock;
import flash.text.engine.TextElement;
import flash.text.engine.TextJustifier;
import flash.text.engine.TextLine;
import flash.text.engine.TextLineValidity;
import flash.text.engine.TextRotation;
import starling.core.Starling;
import starling.display.Image;
import starling.events.Event;
import starling.rendering.Painter;
import starling.textures.ConcreteTexture;
import starling.textures.Texture;
import starling.utils.MathUtil;
/**
* Renders text with a native flash.text.engine.TextBlock
from
* Flash Text Engine
* (sometimes abbreviated as FTE), and draws it to BitmapData
* before uploading it to a texture on the GPU. Textures are managed
* internally by this component, and they will be automatically disposed
* when the component is disposed.
*
* For longer passages of text, this component will stitch together
* multiple individual textures both horizontally and vertically, as a grid,
* if required. This may require quite a lot of texture memory, possibly
* exceeding the limits of some mobile devices, so use this component with
* caution when displaying a lot of text.
*
* The following example shows how to use
* TextBlockTextRenderer
with a Label
:
*
*
* var label:Label = new Label();
* label.text = "I am the very model of a modern Major General";
* label.textRendererFactory = function():ITextRenderer
* {
* return new TextBlockTextRenderer();
* };
* this.addChild( label );
*
* @see ../../../../help/text-renderers.html Introduction to Feathers text renderers
* @see ../../../../help/text-block-text-renderer.html How to use the Feathers TextBlockTextRenderer component
* @see http://help.adobe.com/en_US/as3/dev/WS9dd7ed846a005b294b857bfa122bd808ea6-8000.html Using the Flash Text Engine in ActionScript 3.0 Developer's Guide
* @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/engine/TextBlock.html flash.text.engine.TextBlock
*/
public class TextBlockTextRenderer extends FeathersControl implements ITextRenderer, IStateObserver
{
/**
* @private
*/
private static const HELPER_POINT:Point = new Point();
/**
* @private
*/
private static const HELPER_MATRIX:Matrix = new Matrix();
/**
* @private
*/
private static const HELPER_RECTANGLE:Rectangle = new Rectangle();
/**
* @private
*/
private static var HELPER_TEXT_LINES:Vector. = new [];
/**
* @private
* This is enforced by the runtime.
*/
protected static const MAX_TEXT_LINE_WIDTH:Number = 1000000;
/**
* @private
*/
protected static const LINE_FEED:String = "\n";
/**
* @private
*/
protected static const CARRIAGE_RETURN:String = "\r";
/**
* @private
*/
protected static const FUZZY_TRUNCATION_DIFFERENCE:Number = 0.000001;
/**
* The text will be positioned to the left edge.
*
* @see #textAlign
*/
public static const TEXT_ALIGN_LEFT:String = "left";
/**
* The text will be centered horizontally.
*
* @see #textAlign
*/
public static const TEXT_ALIGN_CENTER:String = "center";
/**
* The text will be positioned to the right edge.
*
* @see #textAlign
*/
public static const TEXT_ALIGN_RIGHT:String = "right";
/**
* The default IStyleProvider
for all TextBlockTextRenderer
* components.
*
* @default null
* @see feathers.core.FeathersControl#styleProvider
*/
public static var globalStyleProvider:IStyleProvider;
/**
* Constructor.
*/
public function TextBlockTextRenderer()
{
super();
this.isQuickHitAreaEnabled = true;
}
/**
* The TextBlock instance used to render the text before taking a
* texture snapshot.
*/
protected var textBlock:TextBlock;
/**
* An image that displays a snapshot of the native TextBlock
* in the Starling display list when the editor doesn't have focus.
*/
protected var textSnapshot:Image;
/**
* If multiple snapshots are needed due to texture size limits, the
* snapshots appearing after the first are stored here.
*/
protected var textSnapshots:Vector.;
/**
* @private
*/
protected var _textSnapshotScrollX:Number = 0;
/**
* @private
*/
protected var _textSnapshotScrollY:Number = 0;
/**
* @private
*/
protected var _textSnapshotOffsetX:Number = 0;
/**
* @private
*/
protected var _textSnapshotOffsetY:Number = 0;
/**
* @private
*/
protected var _lastGlobalScaleX:Number = 0;
/**
* @private
*/
protected var _lastGlobalScaleY:Number = 0;
/**
* @private
*/
protected var _measuredHeight:Number = 0;
/**
* @private
*/
protected var _textLineContainer:Sprite;
/**
* @private
*/
protected var _textLines:Vector. = new [];
/**
* @private
*/
protected var _measurementTextLineContainer:Sprite;
/**
* @private
*/
protected var _measurementTextLines:Vector. = new [];
/**
* @private
*/
protected var _previousContentWidth:Number = NaN;
/**
* @private
*/
protected var _previousContentHeight:Number = NaN;
/**
* @private
*/
protected var _snapshotWidth:int = 0;
/**
* @private
*/
protected var _snapshotHeight:int = 0;
/**
* @private
*/
protected var _snapshotVisibleWidth:int = 0;
/**
* @private
*/
protected var _snapshotVisibleHeight:int = 0;
/**
* @private
*/
protected var _needsNewTexture:Boolean = false;
/**
* @private
*/
protected var _truncationOffset:int = 0;
/**
* @private
*/
protected var _textElement:TextElement;
/**
* @private
*/
override protected function get defaultStyleProvider():IStyleProvider
{
return TextBlockTextRenderer.globalStyleProvider;
}
/**
* @private
*/
protected var _text:String;
/**
* @inheritDoc
*
* In the following example, the text is changed:
*
*
* textRenderer.text = "Lorem ipsum";
*
* @default null
*/
public function get text():String
{
return this._textElement ? this._text : null;
}
/**
* @private
*/
public function set text(value:String):void
{
if(this._text == value)
{
return;
}
this._text = value;
if(!this._textElement)
{
this._textElement = new TextElement(value);
}
this._textElement.text = value;
this.content = this._textElement;
this.invalidate(INVALIDATION_FLAG_DATA);
}
/**
* @private
*/
protected var _content:ContentElement;
/**
* Sets the contents of the TextBlock
to a complex value
* that is more than simple text. If the text
property is
* set after the content
property, the content
* property will be replaced with a TextElement
.
*
* In the following example, the content is changed to a
* GroupElement
:
*
*
* textRenderer.content = new GroupElement( element );
*
* To simply display a string value, use the text
property
* instead:
*
*
* textRenderer.text = "Lorem Ipsum";
*
* @default null
*
* @see #text
*/
public function get content():ContentElement
{
return this._content;
}
/**
* @private
*/
public function set content(value:ContentElement):void
{
if(this._content == value)
{
return;
}
if(value is TextElement)
{
this._textElement = TextElement(value);
}
else
{
this._textElement = null;
}
this._content = value;
this.invalidate(INVALIDATION_FLAG_DATA);
}
/**
* @private
*/
protected var _elementFormatForState:Object;
/**
* @private
*/
protected var _elementFormat:ElementFormat;
/**
* The font and styles used to draw the text. This property will be
* ignored if the content is not a TextElement
instance.
*
* In the following example, the element format is changed:
*
*
* textRenderer.elementFormat = new ElementFormat( new FontDescription( "Source Sans Pro" ) );
*
* @default null
*
* @see #setElementFormatForState()
* @see #disabledElementFormat
* @see #selectedElementFormat
* @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/engine/ElementFormat.html flash.text.engine.ElementFormat
*/
public function get elementFormat():ElementFormat
{
return this._elementFormat;
}
/**
* @private
*/
public function set elementFormat(value:ElementFormat):void
{
if(this._elementFormat == value)
{
return;
}
this._elementFormat = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _disabledElementFormat:ElementFormat;
/**
* The font and styles used to draw the text when the component is
* disabled. This property will be ignored if the content is not a
* TextElement
instance.
*
* In the following example, the disabled element format is changed:
*
*
* textRenderer.isEnabled = false;
* textRenderer.disabledElementFormat = new ElementFormat( new FontDescription( "Source Sans Pro" ) );
*
* @default null
*
* @see #elementFormat
* @see #selectedElementFormat
* @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/engine/ElementFormat.html flash.text.engine.ElementFormat
*/
public function get disabledElementFormat():ElementFormat
{
return this._disabledElementFormat;
}
/**
* @private
*/
public function set disabledElementFormat(value:ElementFormat):void
{
if(this._disabledElementFormat == value)
{
return;
}
this._disabledElementFormat = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _selectedElementFormat:ElementFormat;
/**
* The font and styles used to draw the text when the
* stateContext
implements the IToggle
* interface, and it is selected. This property will be ignored if the
* content is not a TextElement
instance.
*
* In the following example, the selected element format is changed:
*
*
* textRenderer.selectedElementFormat = new ElementFormat( new FontDescription( "Source Sans Pro" ) );
*
* @default null
*
* @see #stateContext
* @see feathers.core.IToggle
* @see #elementFormat
* @see #disabledElementFormat
* @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/engine/ElementFormat.html flash.text.engine.ElementFormat
*/
public function get selectedElementFormat():ElementFormat
{
return this._selectedElementFormat;
}
/**
* @private
*/
public function set selectedElementFormat(value:ElementFormat):void
{
if(this._selectedElementFormat == value)
{
return;
}
this._selectedElementFormat = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _leading:Number = 0;
/**
* The amount of vertical space, in pixels, between lines.
*
* In the following example, the leading is changed to 20 pixels:
*
*
* textRenderer.leading = 20;
*
* @default 0
*/
public function get leading():Number
{
return this._leading;
}
/**
* @private
*/
public function set leading(value:Number):void
{
if(this._leading == value)
{
return;
}
this._leading = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _textAlign:String = TEXT_ALIGN_LEFT;
/**
* The alignment of the text. For justified text, see the
* textJustifier
property.
*
* In the following example, the leading is changed to 20 pixels:
*
*
* textRenderer.textAlign = TextBlockTextRenderer.TEXT_ALIGN_CENTER;
*
* @default TextBlockTextRenderer.TEXT_ALIGN_LEFT
*
* @see #TEXT_ALIGN_LEFT
* @see #TEXT_ALIGN_CENTER
* @see #TEXT_ALIGN_RIGHT
* @see #textJustifier
*/
public function get textAlign():String
{
return this._textAlign;
}
/**
* @private
*/
public function set textAlign(value:String):void
{
if(this._textAlign == value)
{
return;
}
this._textAlign = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _wordWrap:Boolean = false;
/**
* @inheritDoc
*
* In the following example, word wrap is enabled:
*
*
* textRenderer.wordWrap = true;
*
* @default false
*/
public function get wordWrap():Boolean
{
return this._wordWrap;
}
/**
* @private
*/
public function set wordWrap(value:Boolean):void
{
if(this._wordWrap == value)
{
return;
}
this._wordWrap = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @inheritDoc
*/
public function get baseline():Number
{
if(this._textLines.length == 0)
{
return 0;
}
return this.calculateLineAscent(this._textLines[0]);
}
/**
* @private
*/
protected var _applyNonLinearFontScaling:Boolean = true;
/**
* Specifies that you want to enhance screen appearance at the expense
* of what-you-see-is-what-you-get (WYSIWYG) print fidelity.
*
* In the following example, this property is changed to false:
*
*
* textRenderer.applyNonLinearFontScaling = false;
*
* @default true
*
* @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/engine/TextBlock.html#applyNonLinearFontScaling Full description of flash.text.engine.TextBlock.applyNonLinearFontScaling in Adobe's Flash Platform API Reference
*/
public function get applyNonLinearFontScaling():Boolean
{
return this._applyNonLinearFontScaling;
}
/**
* @private
*/
public function set applyNonLinearFontScaling(value:Boolean):void
{
if(this._applyNonLinearFontScaling == value)
{
return;
}
this._applyNonLinearFontScaling = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _baselineFontDescription:FontDescription;
/**
* The font used to determine the baselines for all the lines created from the block, independent of their content.
*
* In the following example, the baseline font description is changed:
*
*
* textRenderer.baselineFontDescription = new FontDescription( "Source Sans Pro", FontWeight.BOLD );
*
* @default null
*
* @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/engine/TextBlock.html#baselineFontDescription Full description of flash.text.engine.TextBlock.baselineFontDescription in Adobe's Flash Platform API Reference
* @see #baselineFontSize
*/
public function get baselineFontDescription():FontDescription
{
return this._baselineFontDescription;
}
/**
* @private
*/
public function set baselineFontDescription(value:FontDescription):void
{
if(this._baselineFontDescription == value)
{
return;
}
this._baselineFontDescription = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _baselineFontSize:Number = 12;
/**
* The font size used to calculate the baselines for the lines created
* from the block.
*
* In the following example, the baseline font size is changed:
*
*
* textRenderer.baselineFontSize = 20;
*
* @default 12
*
* @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/engine/TextBlock.html#baselineFontSize Full description of flash.text.engine.TextBlock.baselineFontSize in Adobe's Flash Platform API Reference
* @see #baselineFontDescription
*/
public function get baselineFontSize():Number
{
return this._baselineFontSize;
}
/**
* @private
*/
public function set baselineFontSize(value:Number):void
{
if(this._baselineFontSize == value)
{
return;
}
this._baselineFontSize = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _baselineZero:String = TextBaseline.ROMAN;
/**
* Specifies which baseline is at y=0 for lines created from this block.
*
* In the following example, the baseline zero is changed:
*
*
* textRenderer.baselineZero = TextBaseline.ASCENT;
*
* @default TextBaseline.ROMAN
*
* @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/engine/TextBlock.html#baselineZero Full description of flash.text.engine.TextBlock.baselineZero in Adobe's Flash Platform API Reference
* @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/engine/TextBaseline.html flash.text.engine.TextBaseline
*/
public function get baselineZero():String
{
return this._baselineZero;
}
/**
* @private
*/
public function set baselineZero(value:String):void
{
if(this._baselineZero == value)
{
return;
}
this._baselineZero = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _bidiLevel:int = 0;
/**
* Specifies the bidirectional paragraph embedding level of the text
* block.
*
* In the following example, the bidi level is changed:
*
*
* textRenderer.bidiLevel = 1;
*
* @default 0
*
* @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/engine/TextBlock.html#bidiLevel Full description of flash.text.engine.TextBlock.bidiLevel in Adobe's Flash Platform API Reference
*/
public function get bidiLevel():int
{
return this._bidiLevel;
}
/**
* @private
*/
public function set bidiLevel(value:int):void
{
if(this._bidiLevel == value)
{
return;
}
this._bidiLevel = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _lineRotation:String = TextRotation.ROTATE_0;
/**
* Rotates the text lines in the text block as a unit.
*
* In the following example, the line rotation is changed:
*
*
* textRenderer.lineRotation = TextRotation.ROTATE_90;
*
* @default TextRotation.ROTATE_0
*
* @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/engine/TextBlock.html#lineRotation Full description of flash.text.engine.TextBlock.lineRotation in Adobe's Flash Platform API Reference
* @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/engine/TextRotation.html flash.text.engine.TextRotation
*/
public function get lineRotation():String
{
return this._lineRotation;
}
/**
* @private
*/
public function set lineRotation(value:String):void
{
if(this._lineRotation == value)
{
return;
}
this._lineRotation = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _tabStops:Vector.;
/**
* Specifies the tab stops for the text in the text block, in the form
* of a Vector
of TabStop
objects.
*
* In the following example, the tab stops changed:
*
*
* textRenderer.tabStops = new <TabStop>[ new TabStop( TabAlignment.CENTER ) ];
*
* @default null
*
* @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/engine/TextBlock.html#tabStops Full description of flash.text.engine.TextBlock.tabStops in Adobe's Flash Platform API Reference
*/
public function get tabStops():Vector.
{
return this._tabStops;
}
/**
* @private
*/
public function set tabStops(value:Vector.):void
{
if(this._tabStops == value)
{
return;
}
this._tabStops = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _textJustifier:TextJustifier = new SpaceJustifier();
/**
* Specifies the TextJustifier
to use during line creation.
*
* In the following example, the text justifier is changed:
*
*
* textRenderer.textJustifier = new SpaceJustifier( "en", LineJustification.ALL_BUT_LAST );
*
* @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/engine/TextBlock.html#textJustifier Full description of flash.text.engine.TextBlock.textJustifier in Adobe's Flash Platform API Reference
*/
public function get textJustifier():TextJustifier
{
return this._textJustifier;
}
/**
* @private
*/
public function set textJustifier(value:TextJustifier):void
{
if(this._textJustifier == value)
{
return;
}
this._textJustifier = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _userData:*;
/**
* Provides a way for the application to associate arbitrary data with
* the text block.
*
* In the following example, the user data is changed:
*
*
* textRenderer.userData = { author: "William Shakespeare", title: "Much Ado About Nothing" };
*
* @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/engine/TextBlock.html#userData Full description of flash.text.engine.TextBlock.userData in Adobe's Flash Platform API Reference
*/
public function get userData():*
{
return this._userData;
}
/**
* @private
*/
public function set userData(value:*):void
{
if(this._userData === value)
{
return;
}
this._userData = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _maxTextureDimensions:int = 2048;
/**
* The maximum size of individual textures that are managed by this text
* renderer. Must be a power of 2. A larger value will create fewer
* individual textures, but a smaller value may use less overall texture
* memory by incrementing over smaller powers of two.
*
* In the following example, the maximum size of the textures is
* changed:
*
*
* renderer.maxTextureDimensions = 4096;
*
* @default 2048
*/
public function get maxTextureDimensions():int
{
return this._maxTextureDimensions;
}
/**
* @private
*/
public function set maxTextureDimensions(value:int):void
{
//check if we can use rectangle textures or not
if(Starling.current.profile == Context3DProfile.BASELINE_CONSTRAINED)
{
value = MathUtil.getNextPowerOfTwo(value);
}
if(this._maxTextureDimensions == value)
{
return;
}
this._maxTextureDimensions = value;
this._needsNewTexture = true;
this.invalidate(INVALIDATION_FLAG_SIZE);
}
/**
* @private
*/
protected var _nativeFilters:Array;
/**
* Native filters to pass to the flash.text.engine.TextLine
* instances before creating the texture snapshot.
*
* In the following example, the native filters are changed:
*
*
* renderer.nativeFilters = [ new GlowFilter() ];
*
* @default null
*
* @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/DisplayObject.html#filters Full description of flash.display.DisplayObject.filters in Adobe's Flash Platform API Reference
*/
public function get nativeFilters():Array
{
return this._nativeFilters;
}
/**
* @private
*/
public function set nativeFilters(value:Array):void
{
if(this._nativeFilters == value)
{
return;
}
this._nativeFilters = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _truncationText:String = "...";
/**
* The text to display at the end of the label if it is truncated.
*
* In the following example, the truncation text is changed:
*
*
* textRenderer.truncationText = " [more]";
*
* @default "..."
*
* @see #truncateToFit
*/
public function get truncationText():String
{
return _truncationText;
}
/**
* @private
*/
public function set truncationText(value:String):void
{
if(this._truncationText == value)
{
return;
}
this._truncationText = value;
this.invalidate(INVALIDATION_FLAG_DATA);
}
/**
* @private
*/
protected var _truncateToFit:Boolean = true;
/**
* If word wrap is disabled, and the text is longer than the width of
* the label, the text may be truncated using truncationText
.
*
* This feature may be disabled to improve performance.
*
* This feature only works when the text
property is
* set to a string value. If the content
property is set
* instead, then the content will not be truncated.
*
* This feature does not currently support the truncation of text
* displayed on multiple lines.
*
* In the following example, truncation is disabled:
*
*
* textRenderer.truncateToFit = false;
*
* @default true
*
* @see #truncationText
*/
public function get truncateToFit():Boolean
{
return _truncateToFit;
}
/**
* @private
*/
public function set truncateToFit(value:Boolean):void
{
if(this._truncateToFit == value)
{
return;
}
this._truncateToFit = value;
this.invalidate(INVALIDATION_FLAG_DATA);
}
/**
* @private
*/
protected var _stateContext:IStateContext;
/**
* When the text renderer observes a state context, the text renderer
* may change its ElementFormat
based on the current state
* of that context. Typically, a relevant component will automatically
* assign itself as the state context of a text renderer, so this
* property is typically meant for internal use only.
*
* @default null
*
* @see #setElementFormatForState()
*/
public function get stateContext():IStateContext
{
return this._stateContext;
}
/**
* @private
*/
public function set stateContext(value:IStateContext):void
{
if(this._stateContext === value)
{
return;
}
if(this._stateContext)
{
this._stateContext.removeEventListener(FeathersEventType.STATE_CHANGE, stateContext_stateChangeHandler);
}
this._stateContext = value;
if(this._stateContext)
{
this._stateContext.addEventListener(FeathersEventType.STATE_CHANGE, stateContext_stateChangeHandler);
}
this.invalidate(INVALIDATION_FLAG_STATE);
}
/**
* @private
*/
protected var _updateSnapshotOnScaleChange:Boolean = false;
/**
* Refreshes the texture snapshot every time that the text renderer is
* scaled. Based on the scale in global coordinates, so scaling the
* parent will require a new snapshot.
*
* Warning: setting this property to true may result in reduced
* performance because every change of the scale requires uploading a
* new texture to the GPU. Use with caution. Consider setting this
* property to false temporarily during animations that modify the
* scale.
*
* In the following example, the snapshot will be updated when the
* text renderer is scaled:
*
*
* textRenderer.updateSnapshotOnScaleChange = true;
*
* @default false
*/
public function get updateSnapshotOnScaleChange():Boolean
{
return this._updateSnapshotOnScaleChange;
}
/**
* @private
*/
public function set updateSnapshotOnScaleChange(value:Boolean):void
{
if(this._updateSnapshotOnScaleChange == value)
{
return;
}
this._updateSnapshotOnScaleChange = value;
this.invalidate(INVALIDATION_FLAG_DATA);
}
/**
* @private
*/
override public function dispose():void
{
this.stateContext = null;
if(this.textSnapshot)
{
this.textSnapshot.texture.dispose();
this.removeChild(this.textSnapshot, true);
this.textSnapshot = null;
}
if(this.textSnapshots)
{
var snapshotCount:int = this.textSnapshots.length;
for(var i:int = 0; i < snapshotCount; i++)
{
var snapshot:Image = this.textSnapshots[i];
snapshot.texture.dispose();
this.removeChild(snapshot, true);
}
this.textSnapshots = null;
}
//this isn't necessary, but if a memory leak keeps the text renderer
//from being garbage collected, freeing up these things may help
//ease memory pressure from native filters and other expensive stuff
this.textBlock = null;
this._textLineContainer = null;
this._textLines = null;
this._measurementTextLineContainer = null;
this._measurementTextLines = null;
this._textElement = null;
this._content = null;
this._previousContentWidth = NaN;
this._previousContentHeight = NaN;
this._needsNewTexture = false;
this._snapshotWidth = 0;
this._snapshotHeight = 0;
super.dispose();
}
/**
* @private
*/
override public function render(painter:Painter):void
{
if(this.textSnapshot)
{
this.getTransformationMatrix(this.stage, HELPER_MATRIX);
if(this._updateSnapshotOnScaleChange)
{
var globalScaleX:Number = matrixToScaleX(HELPER_MATRIX);
var globalScaleY:Number = matrixToScaleY(HELPER_MATRIX);
if(globalScaleX != this._lastGlobalScaleX || globalScaleY != this._lastGlobalScaleY)
{
//the snapshot needs to be updated because the scale has
//changed since the last snapshot was taken.
this.invalidate(INVALIDATION_FLAG_SIZE);
this.validate();
}
}
var scaleFactor:Number = Starling.current.contentScaleFactor;
if(!this._nativeFilters || this._nativeFilters.length === 0)
{
var offsetX:Number = 0;
var offsetY:Number = 0;
}
else
{
offsetX = this._textSnapshotOffsetX / scaleFactor;
offsetY = this._textSnapshotOffsetY / scaleFactor;
}
var snapshotIndex:int = -1;
var totalBitmapWidth:Number = this._snapshotWidth;
var totalBitmapHeight:Number = this._snapshotHeight;
var xPosition:Number = offsetX;
var yPosition:Number = offsetY;
do
{
var currentBitmapWidth:Number = totalBitmapWidth;
if(currentBitmapWidth > this._maxTextureDimensions)
{
currentBitmapWidth = this._maxTextureDimensions;
}
do
{
var currentBitmapHeight:Number = totalBitmapHeight;
if(currentBitmapHeight > this._maxTextureDimensions)
{
currentBitmapHeight = this._maxTextureDimensions;
}
if(snapshotIndex < 0)
{
var snapshot:Image = this.textSnapshot;
}
else
{
snapshot = this.textSnapshots[snapshotIndex];
}
snapshot.x = xPosition / scaleFactor;
snapshot.y = yPosition / scaleFactor;
if(this._updateSnapshotOnScaleChange)
{
snapshot.x /= this._lastGlobalScaleX;
snapshot.y /= this._lastGlobalScaleX;
}
snapshotIndex++;
yPosition += currentBitmapHeight;
totalBitmapHeight -= currentBitmapHeight;
}
while(totalBitmapHeight > 0)
xPosition += currentBitmapWidth;
totalBitmapWidth -= currentBitmapWidth;
yPosition = offsetY;
totalBitmapHeight = this._snapshotHeight;
}
while(totalBitmapWidth > 0)
}
super.render(painter);
}
/**
* @inheritDoc
*/
public function measureText(result:Point = null):Point
{
if(!result)
{
result = new Point();
}
var needsWidth:Boolean = this._explicitWidth !== this._explicitWidth; //isNaN
var needsHeight:Boolean = this._explicitHeight !== this._explicitHeight; //isNaN
if(!needsWidth && !needsHeight)
{
result.x = this._explicitWidth;
result.y = this._explicitHeight;
return result;
}
//if a parent component validates before we're added to the stage,
//measureText() may be called before initialization, so we need to
//force it.
if(!this._isInitialized)
{
this.initializeInternal();
}
this.commit();
result = this.measure(result);
return result;
}
/**
* Sets the ElementFormat
to be used by the text renderer
* when the currentState
property of the
* stateContext
matches the specified state value.
*
* If an ElementFormat
is not defined for a specific
* state, the value of the elementFormat
property will be
* used instead.
*
* If the disabledElementFormat
property is not
* null
and the isEnabled
property is
* false
, all other element formats will be ignored.
*
* @see #stateContext
* @see #elementFormat
*/
public function setElementFormatForState(state:String, elementFormat:ElementFormat):void
{
if(elementFormat)
{
if(!this._elementFormatForState)
{
this._elementFormatForState = {};
}
this._elementFormatForState[state] = elementFormat;
}
else
{
delete this._elementFormatForState[state];
}
//if the context's current state is the state that we're modifying,
//we need to use the new value immediately.
if(this._stateContext && this._stateContext.currentState === state)
{
this.invalidate(INVALIDATION_FLAG_STATE);
}
}
/**
* @private
*/
override protected function initialize():void
{
if(!this.textBlock)
{
this.textBlock = new TextBlock();
}
if(!this._textLineContainer)
{
this._textLineContainer = new Sprite();
}
if(!this._measurementTextLineContainer)
{
this._measurementTextLineContainer = new Sprite();
}
}
/**
* @private
*/
override protected function draw():void
{
var sizeInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_SIZE);
this.commit();
sizeInvalid = this.autoSizeIfNeeded() || sizeInvalid;
this.layout(sizeInvalid);
}
/**
* @private
*/
protected function commit():void
{
var stylesInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_STYLES);
var dataInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_DATA);
var stateInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_STATE);
if(dataInvalid || stylesInvalid || stateInvalid)
{
this.refreshElementFormat();
}
if(stylesInvalid)
{
this.textBlock.applyNonLinearFontScaling = this._applyNonLinearFontScaling;
this.textBlock.baselineFontDescription = this._baselineFontDescription;
this.textBlock.baselineFontSize = this._baselineFontSize;
this.textBlock.baselineZero = this._baselineZero;
this.textBlock.bidiLevel = this._bidiLevel;
this.textBlock.lineRotation = this._lineRotation;
this.textBlock.tabStops = this._tabStops;
this.textBlock.textJustifier = this._textJustifier;
this.textBlock.userData = this._userData;
this._textLineContainer.filters = this._nativeFilters;
}
if(dataInvalid)
{
this.textBlock.content = this._content;
}
}
/**
* @private
*/
protected function measure(result:Point = null):Point
{
if(!result)
{
result = new Point();
}
var needsWidth:Boolean = this._explicitWidth !== this._explicitWidth; //isNaN
var needsHeight:Boolean = this._explicitHeight !== this._explicitHeight; //isNaN
var newWidth:Number = this._explicitWidth;
var newHeight:Number = this._explicitHeight;
if(needsWidth)
{
newWidth = this._maxWidth;
if(newWidth > MAX_TEXT_LINE_WIDTH)
{
newWidth = MAX_TEXT_LINE_WIDTH;
}
}
if(needsHeight)
{
newHeight = this._maxHeight;
}
this.refreshTextLines(this._measurementTextLines, this._measurementTextLineContainer, newWidth, newHeight);
if(needsWidth)
{
newWidth = Math.ceil(this._measurementTextLineContainer.width);
if(newWidth > this._maxWidth)
{
newWidth = this._maxWidth;
}
}
if(needsHeight)
{
newHeight = Math.ceil(this._measuredHeight);
if(newHeight <= 0 && this._elementFormat)
{
newHeight = this._elementFormat.fontSize;
}
}
result.x = newWidth;
result.y = newHeight;
return result;
}
/**
* @private
*/
protected function layout(sizeInvalid:Boolean):void
{
var stylesInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_STYLES);
var dataInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_DATA);
var stateInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_STATE);
if(sizeInvalid)
{
var scaleFactor:Number = Starling.current.contentScaleFactor;
//these are getting put into an int later, so we don't want it
//to possibly round down and cut off part of the text.
var rectangleSnapshotWidth:Number = Math.ceil(this.actualWidth * scaleFactor);
var rectangleSnapshotHeight:Number = Math.ceil(this.actualHeight * scaleFactor);
if(this._updateSnapshotOnScaleChange)
{
this.getTransformationMatrix(this.stage, HELPER_MATRIX);
rectangleSnapshotWidth *= matrixToScaleX(HELPER_MATRIX);
rectangleSnapshotHeight *= matrixToScaleY(HELPER_MATRIX);
}
if(rectangleSnapshotWidth >= 1 && rectangleSnapshotHeight >= 1 &&
this._nativeFilters && this._nativeFilters.length > 0)
{
HELPER_MATRIX.identity();
HELPER_MATRIX.scale(scaleFactor, scaleFactor);
var bitmapData:BitmapData = new BitmapData(rectangleSnapshotWidth, rectangleSnapshotHeight, true, 0x00ff00ff);
bitmapData.draw(this._textLineContainer, HELPER_MATRIX, null, null, HELPER_RECTANGLE);
this.measureNativeFilters(bitmapData, HELPER_RECTANGLE);
bitmapData.dispose();
bitmapData = null;
this._textSnapshotOffsetX = HELPER_RECTANGLE.x;
this._textSnapshotOffsetY = HELPER_RECTANGLE.y;
rectangleSnapshotWidth = HELPER_RECTANGLE.width;
rectangleSnapshotHeight = HELPER_RECTANGLE.height;
}
var canUseRectangleTexture:Boolean = Starling.current.profile != Context3DProfile.BASELINE_CONSTRAINED;
if(canUseRectangleTexture)
{
if(rectangleSnapshotWidth > this._maxTextureDimensions)
{
this._snapshotWidth = int(rectangleSnapshotWidth / this._maxTextureDimensions) * this._maxTextureDimensions + (rectangleSnapshotWidth % this._maxTextureDimensions);
}
else
{
this._snapshotWidth = rectangleSnapshotWidth;
}
}
else
{
if(rectangleSnapshotWidth > this._maxTextureDimensions)
{
this._snapshotWidth = int(rectangleSnapshotWidth / this._maxTextureDimensions) * this._maxTextureDimensions + MathUtil.getNextPowerOfTwo(rectangleSnapshotWidth % this._maxTextureDimensions);
}
else
{
this._snapshotWidth = MathUtil.getNextPowerOfTwo(rectangleSnapshotWidth);
}
}
if(canUseRectangleTexture)
{
if(rectangleSnapshotHeight > this._maxTextureDimensions)
{
this._snapshotHeight = int(rectangleSnapshotHeight / this._maxTextureDimensions) * this._maxTextureDimensions + (rectangleSnapshotHeight % this._maxTextureDimensions);
}
else
{
this._snapshotHeight = rectangleSnapshotHeight;
}
}
else
{
if(rectangleSnapshotHeight > this._maxTextureDimensions)
{
this._snapshotHeight = int(rectangleSnapshotHeight / this._maxTextureDimensions) * this._maxTextureDimensions + MathUtil.getNextPowerOfTwo(rectangleSnapshotHeight % this._maxTextureDimensions);
}
else
{
this._snapshotHeight = MathUtil.getNextPowerOfTwo(rectangleSnapshotHeight);
}
}
var textureRoot:ConcreteTexture = this.textSnapshot ? this.textSnapshot.texture.root : null;
this._needsNewTexture = this._needsNewTexture || !this.textSnapshot ||
(textureRoot && (textureRoot.scale != scaleFactor ||
this._snapshotWidth != textureRoot.nativeWidth || this._snapshotHeight != textureRoot.nativeHeight));
this._snapshotVisibleWidth = rectangleSnapshotWidth;
this._snapshotVisibleHeight = rectangleSnapshotHeight;
}
//instead of checking sizeInvalid, which will often be triggered by
//changing maxWidth or something for measurement, we check against
//the previous actualWidth/Height used for the snapshot.
if(stylesInvalid || dataInvalid || stateInvalid || this._needsNewTexture ||
this.actualWidth != this._previousContentWidth ||
this.actualHeight != this._previousContentHeight)
{
this._previousContentWidth = this.actualWidth;
this._previousContentHeight = this.actualHeight;
if(this._content)
{
this.refreshTextLines(this._textLines, this._textLineContainer, this.actualWidth, this.actualHeight);
this.refreshSnapshot();
}
if(this.textSnapshot)
{
this.textSnapshot.visible = this._snapshotWidth > 0 && this._snapshotHeight > 0 && this._content !== null;
}
}
}
/**
* If the component's dimensions have not been set explicitly, it will
* measure its content and determine an ideal size for itself. If the
* explicitWidth
or explicitHeight
member
* variables are set, those value will be used without additional
* measurement. If one is set, but not the other, the dimension with the
* explicit value will not be measured, but the other non-explicit
* dimension will still need measurement.
*
* Calls setSizeInternal()
to set up the
* actualWidth
and actualHeight
member
* variables used for layout.
*
* Meant for internal use, and subclasses may override this function
* with a custom implementation.
*/
protected function autoSizeIfNeeded():Boolean
{
var needsWidth:Boolean = this._explicitWidth !== this._explicitWidth; //isNaN
var needsHeight:Boolean = this._explicitHeight !== this._explicitHeight; //isNaN
if(!needsWidth && !needsHeight)
{
return false;
}
this.measure(HELPER_POINT);
return this.setSizeInternal(HELPER_POINT.x, HELPER_POINT.y, false);
}
/**
* @private
*/
protected function measureNativeFilters(bitmapData:BitmapData, result:Rectangle = null):Rectangle
{
if(!result)
{
result = new Rectangle();
}
var resultX:Number = 0;
var resultY:Number = 0;
var resultWidth:Number = 0;
var resultHeight:Number = 0;
var filterCount:int = this._nativeFilters.length;
for(var i:int = 0; i < filterCount; i++)
{
var filter:BitmapFilter = this._nativeFilters[i];
var filterRect:Rectangle = bitmapData.generateFilterRect(bitmapData.rect, filter);
var filterX:Number = filterRect.x;
var filterY:Number = filterRect.y;
var filterWidth:Number = filterRect.width;
var filterHeight:Number = filterRect.height;
if(resultX > filterX)
{
resultX = filterX;
}
if(resultY > filterY)
{
resultY = filterY;
}
if(resultWidth < filterWidth)
{
resultWidth = filterWidth;
}
if(resultHeight < filterHeight)
{
resultHeight = filterHeight;
}
}
result.setTo(resultX, resultY, resultWidth, resultHeight);
return result;
}
/**
* @private
*/
protected function refreshElementFormat():void
{
if(!this._textElement)
{
return;
}
var elementFormat:ElementFormat;
if(this._stateContext && this._elementFormatForState)
{
var currentState:String = this._stateContext.currentState;
if(currentState in this._elementFormatForState)
{
elementFormat = ElementFormat(this._elementFormatForState[currentState]);
}
}
if(!elementFormat && !this._isEnabled && this._disabledElementFormat)
{
elementFormat = this._disabledElementFormat;
}
if(!elementFormat && this._selectedElementFormat &&
this._stateContext is IToggle && IToggle(this._stateContext).isSelected)
{
elementFormat = this._selectedElementFormat;
}
if(!elementFormat)
{
if(!this._elementFormat)
{
this._elementFormat = new ElementFormat();
}
elementFormat = this._elementFormat;
}
this._textElement.elementFormat = elementFormat;
}
/**
* @private
*/
protected function createTextureOnRestoreCallback(snapshot:Image):void
{
var self:TextBlockTextRenderer = this;
var texture:Texture = snapshot.texture;
texture.root.onRestore = function():void
{
var scaleFactor:Number = Starling.contentScaleFactor;
if(texture.scale != scaleFactor)
{
//if we've changed between scale factors, we need to
//recreate the texture to match the new scale factor.
invalidate(INVALIDATION_FLAG_SIZE);
}
else
{
HELPER_MATRIX.identity();
HELPER_MATRIX.scale(scaleFactor, scaleFactor);
var bitmapData:BitmapData = self.drawTextLinesRegionToBitmapData(
snapshot.x, snapshot.y, texture.nativeWidth, texture.nativeHeight);
texture.root.uploadBitmapData(bitmapData);
bitmapData.dispose();
}
};
}
/**
* @private
*/
protected function drawTextLinesRegionToBitmapData(textLinesX:Number, textLinesY:Number,
bitmapWidth:Number, bitmapHeight:Number, bitmapData:BitmapData = null):BitmapData
{
var clipWidth:Number = this._snapshotVisibleWidth - textLinesX;
var clipHeight:Number = this._snapshotVisibleHeight - textLinesY;
if(!bitmapData || bitmapData.width != bitmapWidth || bitmapData.height != bitmapHeight)
{
if(bitmapData)
{
bitmapData.dispose();
}
bitmapData = new BitmapData(bitmapWidth, bitmapHeight, true, 0x00ff00ff);
}
else
{
//clear the bitmap data and reuse it
bitmapData.fillRect(bitmapData.rect, 0x00ff00ff);
}
var nativeScaleFactor:Number = 1;
var starling:Starling = stageToStarling(this.stage);
if(starling && starling.supportHighResolutions)
{
nativeScaleFactor = starling.nativeStage.contentsScaleFactor;
}
HELPER_MATRIX.tx = -textLinesX - this._textSnapshotScrollX * nativeScaleFactor - this._textSnapshotOffsetX;
HELPER_MATRIX.ty = -textLinesY - this._textSnapshotScrollY * nativeScaleFactor - this._textSnapshotOffsetY;
HELPER_RECTANGLE.setTo(0, 0, clipWidth, clipHeight);
bitmapData.draw(this._textLineContainer, HELPER_MATRIX, null, null, HELPER_RECTANGLE);
return bitmapData;
}
/**
* @private
*/
protected function refreshSnapshot():void
{
if(this._snapshotWidth == 0 || this._snapshotHeight == 0)
{
return;
}
var scaleFactor:Number = Starling.contentScaleFactor;
if(this._updateSnapshotOnScaleChange)
{
this.getTransformationMatrix(this.stage, HELPER_MATRIX);
var globalScaleX:Number = matrixToScaleX(HELPER_MATRIX);
var globalScaleY:Number = matrixToScaleY(HELPER_MATRIX);
}
HELPER_MATRIX.identity();
HELPER_MATRIX.scale(scaleFactor, scaleFactor);
if(this._updateSnapshotOnScaleChange)
{
HELPER_MATRIX.scale(globalScaleX, globalScaleY);
}
var totalBitmapWidth:Number = this._snapshotWidth;
var totalBitmapHeight:Number = this._snapshotHeight;
var xPosition:Number = 0;
var yPosition:Number = 0;
var bitmapData:BitmapData;
var snapshotIndex:int = -1;
do
{
var currentBitmapWidth:Number = totalBitmapWidth;
if(currentBitmapWidth > this._maxTextureDimensions)
{
currentBitmapWidth = this._maxTextureDimensions;
}
do
{
var currentBitmapHeight:Number = totalBitmapHeight;
if(currentBitmapHeight > this._maxTextureDimensions)
{
currentBitmapHeight = this._maxTextureDimensions;
}
bitmapData = this.drawTextLinesRegionToBitmapData(xPosition, yPosition,
currentBitmapWidth, currentBitmapHeight, bitmapData);
var newTexture:Texture;
if(!this.textSnapshot || this._needsNewTexture)
{
//skip Texture.fromBitmapData() because we don't want
//it to create an onRestore function that will be
//immediately discarded for garbage collection.
newTexture = Texture.empty(bitmapData.width / scaleFactor, bitmapData.height / scaleFactor,
true, false, false, scaleFactor);
newTexture.root.uploadBitmapData(bitmapData);
}
var snapshot:Image = null;
if(snapshotIndex >= 0)
{
if(!this.textSnapshots)
{
this.textSnapshots = new [];
}
else if(this.textSnapshots.length > snapshotIndex)
{
snapshot = this.textSnapshots[snapshotIndex]
}
}
else
{
snapshot = this.textSnapshot;
}
if(!snapshot)
{
snapshot = new Image(newTexture);
this.addChild(snapshot);
}
else
{
if(this._needsNewTexture)
{
snapshot.texture.dispose();
snapshot.texture = newTexture;
snapshot.readjustSize();
}
else
{
//this is faster, if we haven't resized the bitmapdata
var existingTexture:Texture = snapshot.texture;
existingTexture.root.uploadBitmapData(bitmapData);
}
}
if(newTexture)
{
this.createTextureOnRestoreCallback(snapshot);
}
if(snapshotIndex >= 0)
{
this.textSnapshots[snapshotIndex] = snapshot;
}
else
{
this.textSnapshot = snapshot;
}
snapshot.x = xPosition / scaleFactor;
snapshot.y = yPosition / scaleFactor;
if(this._updateSnapshotOnScaleChange)
{
snapshot.scaleX = 1 / globalScaleX;
snapshot.scaleY = 1 / globalScaleY;
snapshot.x /= globalScaleX;
snapshot.y /= globalScaleY;
}
snapshotIndex++;
yPosition += currentBitmapHeight;
totalBitmapHeight -= currentBitmapHeight;
}
while(totalBitmapHeight > 0)
xPosition += currentBitmapWidth;
totalBitmapWidth -= currentBitmapWidth;
yPosition = 0;
totalBitmapHeight = this._snapshotHeight;
}
while(totalBitmapWidth > 0)
bitmapData.dispose();
if(this.textSnapshots)
{
var snapshotCount:int = this.textSnapshots.length;
for(var i:int = snapshotIndex; i < snapshotCount; i++)
{
snapshot = this.textSnapshots[i];
snapshot.texture.dispose();
snapshot.removeFromParent(true);
}
if(snapshotIndex == 0)
{
this.textSnapshots = null;
}
else
{
this.textSnapshots.length = snapshotIndex;
}
}
if(this._updateSnapshotOnScaleChange)
{
this._lastGlobalScaleX = globalScaleX;
this._lastGlobalScaleY = globalScaleY;
}
this._needsNewTexture = false;
}
/**
* @private
* the ascent alone doesn't account for diacritical marks,
* like accents and things. however, increasing the ascent by
* the value of the descent seems to be a good approximation.
*/
protected function calculateLineAscent(line:TextLine):Number
{
var calculatedAscent:Number = line.ascent + line.descent;
if(line.totalAscent > calculatedAscent)
{
calculatedAscent = line.totalAscent;
}
return calculatedAscent;
}
/**
* @private
*/
protected function refreshTextElementText():void
{
if(this._textElement === null)
{
return;
}
if(this._text)
{
this._textElement.text = this._text;
if(this._text !== null && this._text.charAt(this._text.length - 1) == " ")
{
//add an invisible control character because FTE apparently
//doesn't think that it's important to include trailing
//spaces in its width measurement.
this._textElement.text += String.fromCharCode(3);
}
}
else
{
//similar to above. this hack ensures that the baseline is
//measured properly when the text is an empty string.
this._textElement.text = String.fromCharCode(3);
}
}
/**
* @private
*/
protected function refreshTextLines(textLines:Vector., textLineParent:DisplayObjectContainer, width:Number, height:Number):void
{
this.refreshTextElementText();
HELPER_TEXT_LINES.length = 0;
var yPosition:Number = 0;
var lineCount:int = textLines.length;
var lastLine:TextLine;
var cacheIndex:int = lineCount;
for(var i:int = 0; i < lineCount; i++)
{
var line:TextLine = textLines[i];
if(line.validity === TextLineValidity.VALID)
{
lastLine = line;
textLines[i] = line;
continue;
}
else
{
line = lastLine;
if(lastLine)
{
yPosition = lastLine.y;
//we're using this value in the next loop
lastLine = null;
}
cacheIndex = i;
break;
}
}
//copy the invalid text lines over to the helper vector so that we
//can reuse them
for(; i < lineCount; i++)
{
HELPER_TEXT_LINES[int(i - cacheIndex)] = textLines[i];
}
textLines.length = cacheIndex;
if(width >= 0)
{
var lineStartIndex:int = 0;
var canTruncate:Boolean = this._truncateToFit && this._textElement && !this._wordWrap;
var pushIndex:int = textLines.length;
var inactiveTextLineCount:int = HELPER_TEXT_LINES.length;
while(true)
{
this._truncationOffset = 0;
var previousLine:TextLine = line;
var lineWidth:Number = width;
if(!this._wordWrap)
{
lineWidth = MAX_TEXT_LINE_WIDTH;
}
if(inactiveTextLineCount > 0)
{
var inactiveLine:TextLine = HELPER_TEXT_LINES[0];
line = this.textBlock.recreateTextLine(inactiveLine, previousLine, lineWidth, 0, true);
if(line)
{
HELPER_TEXT_LINES.shift();
inactiveTextLineCount--;
}
}
else
{
line = this.textBlock.createTextLine(previousLine, lineWidth, 0, true);
if(line)
{
textLineParent.addChild(line);
}
}
if(!line)
{
//end of text
break;
}
var lineLength:int = line.rawTextLength;
var isTruncated:Boolean = false;
var difference:Number = 0;
while(canTruncate && (difference = line.width - width) > FUZZY_TRUNCATION_DIFFERENCE)
{
isTruncated = true;
if(this._truncationOffset == 0)
{
//this will quickly skip all of the characters after
//the maximum width of the line, instead of going
//one by one.
var endIndex:int = line.getAtomIndexAtPoint(width, 0);
if(endIndex >= 0)
{
this._truncationOffset = line.rawTextLength - endIndex;
}
}
this._truncationOffset++;
var truncatedTextLength:int = lineLength - this._truncationOffset;
//we want to start at this line so that the previous
//lines don't become invalid.
this._textElement.text = this._text.substr(lineStartIndex, truncatedTextLength) + this._truncationText;
var lineBreakIndex:int = this._text.indexOf(LINE_FEED, lineStartIndex);
if(lineBreakIndex < 0)
{
lineBreakIndex = this._text.indexOf(CARRIAGE_RETURN, lineStartIndex);
}
if(lineBreakIndex >= 0)
{
this._textElement.text += this._text.substr(lineBreakIndex);
}
line = this.textBlock.recreateTextLine(line, null, lineWidth, 0, true);
if(truncatedTextLength <= 0)
{
break;
}
}
if(pushIndex > 0)
{
yPosition += this._leading;
}
yPosition += this.calculateLineAscent(line);
line.y = yPosition;
yPosition += line.totalDescent;
textLines[pushIndex] = line;
pushIndex++;
lineStartIndex += lineLength;
}
}
if(textLines === this._measurementTextLines)
{
this._measuredHeight = yPosition;
}
this.alignTextLines(textLines, width, this._textAlign);
inactiveTextLineCount = HELPER_TEXT_LINES.length;
for(i = 0; i < inactiveTextLineCount; i++)
{
line = HELPER_TEXT_LINES[i];
textLineParent.removeChild(line);
}
HELPER_TEXT_LINES.length = 0;
}
/**
* @private
*/
protected function alignTextLines(textLines:Vector., width:Number, textAlign:String):void
{
var lineCount:int = textLines.length;
for(var i:int = 0; i < lineCount; i++)
{
var line:TextLine = textLines[i];
if(textAlign == TEXT_ALIGN_CENTER)
{
line.x = (width - line.width) / 2;
}
else if(textAlign == TEXT_ALIGN_RIGHT)
{
line.x = width - line.width;
}
else
{
line.x = 0;
}
}
}
/**
* @private
*/
protected function stateContext_stateChangeHandler(event:Event):void
{
this.invalidate(INVALIDATION_FLAG_STATE);
}
}
}