| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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 spark.components.supportClasses |
| { |
| |
| import flash.display.DisplayObject; |
| import flash.display.Graphics; |
| import flash.display.Shape; |
| import flash.events.Event; |
| import flash.geom.Rectangle; |
| import flash.text.engine.FontLookup; |
| import flash.text.engine.TextLine; |
| import flash.text.engine.TextLineValidity; |
| |
| import mx.core.IFlexModuleFactory; |
| import mx.core.UIComponent; |
| import mx.core.mx_internal; |
| import mx.events.FlexEvent; |
| import mx.resources.IResourceManager; |
| import mx.resources.ResourceManager; |
| |
| import spark.core.IDisplayText; |
| import spark.utils.TextUtil; |
| |
| import flashx.textLayout.compose.TextLineRecycler; |
| |
| use namespace mx_internal; |
| |
| //-------------------------------------- |
| // Styles |
| //-------------------------------------- |
| |
| /** |
| * The alpha level of the color defined by |
| * the <code>backgroundColor</code> style. |
| * Valid values range from 0.0 to 1.0. |
| * |
| * @default 1.0 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| [Style(name="backgroundAlpha", type="Number", inherit="no", minValue="0.0", maxValue="1.0")] |
| |
| /** |
| * The color of the background of the entire |
| * bounding rectangle of this component. |
| * If this style is <code>undefined</code>, |
| * no background is drawn. |
| * Otherwise, this RGB color is drawn with an alpha level |
| * determined by the <code>backgroundAlpha</code> style. |
| * |
| * @default undefined |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| [Style(name="backgroundColor", type="uint", format="Color", inherit="no")] |
| |
| //-------------------------------------------------------------------------- |
| // Other metadata for accessibility API |
| //------------------------------------------------------------------------- |
| [AccessibilityClass(implementation="spark.accessibility.TextBaseAccImpl")] |
| |
| /** |
| * The base class for Spark text controls such as Label and RichText |
| * which display text using CSS styles for the default format. |
| * |
| * <p>In addition to adding a <code>text</code> property, |
| * this class also adds the <code>maxDisplayedLines</code> |
| * and <code>isTruncated</code> properties to support truncation.</p> |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| |
| public class TextBase extends UIComponent implements IDisplayText |
| { |
| |
| include "../../core/Version.as"; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Class variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Most resources are fetched on the fly from the ResourceManager, |
| * so they automatically get the right resource when the locale changes. |
| * But since truncation can happen frequently, |
| * this class caches this resource value in this variable |
| * and updates it when the locale changes. |
| */ |
| mx_internal static var truncationIndicatorResource:String; |
| |
| /** |
| * @private |
| * Mixins for accessibility |
| */ |
| mx_internal static var createAccessibilityImplementation:Function; |
| |
| /** |
| * @private |
| * Accessibility initialization function |
| */ |
| |
| override protected function initializeAccessibility():void |
| { |
| if (TextBase.createAccessibilityImplementation != null) |
| TextBase.createAccessibilityImplementation(this); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Constructor. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function TextBase() |
| { |
| super(); |
| |
| // set mouseChildren to false so that the TextLines that get |
| // added don't trap the mouse events. |
| mouseChildren = false; |
| |
| var resourceManager:IResourceManager = ResourceManager.getInstance(); |
| |
| if (!truncationIndicatorResource) |
| { |
| truncationIndicatorResource = resourceManager.getString( |
| "core", "truncationIndicator"); |
| } |
| |
| addEventListener(FlexEvent.UPDATE_COMPLETE, updateCompleteHandler); |
| |
| // Register as a weak listener for "change" events from ResourceManager. |
| // If TextBases registered as a strong listener, |
| // they wouldn't get garbage collected. |
| resourceManager.addEventListener( |
| Event.CHANGE, resourceManager_changeHandler, false, 0, true); |
| |
| // Our background fill. |
| _backgroundShape = new Shape(); |
| addChild(_backgroundShape); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * The composition bounds used when creating the TextLines. |
| */ |
| mx_internal var bounds:Rectangle = new Rectangle(0, 0, NaN, NaN); |
| |
| /** |
| * @private |
| * The TextLines and Shapes created to render the text. |
| * (Shapes are used to render the backgroundColor format for RichText.) |
| */ |
| mx_internal var textLines:Vector.<DisplayObject> = |
| new Vector.<DisplayObject>(); |
| |
| /** |
| * @private |
| * This flag is set to true if the text must be clipped. |
| */ |
| mx_internal var isOverset:Boolean = false; |
| |
| /** |
| * @private |
| * This flag is used to avoid getting or setting the scrollRect |
| * of our displayObject unnecessarily when we need to clip TextLines |
| * that extend beyond our bounds. |
| * It shouldn't even be set to null if you can avoid it, |
| * because Player 10.0 allocates a surface even in this case. |
| */ |
| mx_internal var hasScrollRect:Boolean = false; |
| |
| /** |
| * @private |
| */ |
| mx_internal var invalidateCompose:Boolean = true; |
| |
| /** |
| * @private |
| * The value of bounds.width, before the compose was done. |
| */ |
| mx_internal var _composeWidth:Number; |
| |
| /** |
| * @private |
| * The value of bounds.height, before the compose was done. |
| */ |
| mx_internal var _composeHeight:Number; |
| |
| /** |
| * @private |
| * Cache the width constraint as set by the layout in setLayoutBoundsSize() |
| * so that text reflow can be calculated during a subsequent measure pass. |
| */ |
| private var _widthConstraint:Number = NaN; |
| |
| /** |
| * @private |
| * We can optimize for a single line text reflow, which is a lot of cases. |
| */ |
| private var _measuredOneTextLine:Boolean = false; |
| |
| /** |
| * @private |
| * Shape we use to render our background fill to work around several |
| * player blendMode issues. See SDK-24821. |
| */ |
| private var _backgroundShape:Shape; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Overridden properties: UIComponent |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // baselinePosition |
| //---------------------------------- |
| |
| [Inspectable(category="General")] |
| |
| /** |
| * @private |
| */ |
| override public function get baselinePosition():Number |
| { |
| if (!validateBaselinePosition()) |
| return NaN; |
| |
| // Create an empty text line so we can measure the height. If the |
| // text is vertically aligned then need the composeHeight so the |
| // baseline remains consistent when the width is so narrow there |
| // are no textLines. |
| if (textLines.length == 0 || |
| (textLines.length == 1 && textLines[0] is Shape)) |
| createEmptyTextLine(_composeHeight); |
| |
| // Return the baseline of the first line of composed text. |
| return textLines.length > 0 ? getBaselineFromFirstTextLine() : 0; |
| } |
| |
| private function getBaselineFromFirstTextLine():Number |
| { |
| // you may find a Shape in here when background colors are on |
| for each (var tl:DisplayObject in textLines) |
| { |
| if (tl is TextLine) |
| return tl.y; |
| } |
| return 0; |
| } |
| |
| //---------------------------------- |
| // visible |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var visibleChanged:Boolean = false; |
| |
| /** |
| * @private |
| */ |
| override public function set visible(value:Boolean):void |
| { |
| super.visible = value; |
| visibleChanged = true; |
| |
| invalidateDisplayList(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // isTruncated |
| //---------------------------------- |
| |
| /** |
| * @private |
| * Storage for the isTruncated property. |
| */ |
| private var _isTruncated:Boolean = false; |
| |
| /** |
| * A read-only property reporting whether the text has been truncated. |
| * |
| * <p>Truncating text means replacing excess text |
| * with a truncation indicator such as "...". |
| * The truncation indicator is locale-dependent; |
| * it is specified by the "truncationIndicator" resource |
| * in the "core" resource bundle.</p> |
| * |
| * <p>If <code>maxDisplayedLines</code> is 0, no truncation occurs. |
| * Instead, the text will simply be clipped |
| * if it doesn't fit within the component's bounds.</p> |
| * |
| * <p>If <code>maxDisplayedLines</code> is is a positive integer, |
| * the text will be truncated if necessary to reduce |
| * the number of lines to this integer.</p> |
| * |
| * <p>If <code>maxDisplayedLines</code> is -1, the text will be truncated |
| * to display as many lines as will completely fit within the height |
| * of the component.</p> |
| * |
| * <p>Truncation is only performed if the <code>lineBreak</code> |
| * style is <code>"toFit"</code>; the value of this property |
| * is ignored if <code>lineBreak</code> is <code>"explicit"</code>.</p> |
| * |
| * @default false |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get isTruncated():Boolean |
| { |
| // For some reason, the compiler needs an explicit cast to Boolean |
| // to avoid a warning even though at runtime "is Boolean" is true. |
| return Boolean(_isTruncated); |
| } |
| |
| /** |
| * @private |
| * Dispatch an "isTruncatedChanged" event when the property is set. |
| */ |
| mx_internal function setIsTruncated(value:Boolean):void |
| { |
| if (_isTruncated != value) |
| { |
| _isTruncated = value; |
| if (showTruncationTip) |
| toolTip = _isTruncated ? text : null; |
| dispatchEvent(new Event("isTruncatedChanged")); |
| } |
| } |
| |
| //---------------------------------- |
| // maxDisplayedLines |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _maxDisplayedLines:int = 0; |
| |
| [Inspectable(category="General", minValue="-1", defaultValue="0")] |
| |
| /** |
| * An integer which determines whether, and where, |
| * the text gets truncated. |
| * |
| * <p>Truncating text means replacing excess text |
| * with a truncation indicator such as "...". |
| * The truncation indicator is locale-dependent; |
| * it is specified by the "truncationIndicator" resource |
| * in the "core" resource bundle.</p> |
| * |
| * <p>If the value is 0, no truncation occurs. |
| * Instead, the text will simply be clipped |
| * if it doesn't fit within the component's bounds.</p> |
| * |
| * <p>If the value is is a positive integer, |
| * the text will be truncated if necessary to reduce |
| * the number of lines to this integer.</p> |
| * |
| * <p>If the value is -1, the text will be truncated to display |
| * as many lines as will completely fit within the height |
| * of the component.</p> |
| * |
| * <p>Truncation is only performed if the <code>lineBreak</code> |
| * style is <code>"toFit"</code>; the value of this property |
| * is ignored if <code>lineBreak</code> is <code>"explicit"</code>.</p> |
| * |
| * @default 0 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get maxDisplayedLines():int |
| { |
| return _maxDisplayedLines; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set maxDisplayedLines(value:int):void |
| { |
| if (value != _maxDisplayedLines) |
| { |
| _maxDisplayedLines = value; |
| |
| invalidateTextLines(); |
| |
| invalidateSize(); |
| invalidateDisplayList(); |
| } |
| } |
| |
| //---------------------------------- |
| // showTruncationTip |
| //---------------------------------- |
| |
| /** |
| * @private |
| * Storage for the showTruncationTip property. |
| */ |
| private var _showTruncationTip:Boolean = false; |
| |
| [Inspectable(category="General", defaultValue="false")] |
| |
| /** |
| * A property that controls whether the component |
| * should show a toolTip when the text has been truncated. |
| * |
| * @default false |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get showTruncationTip():Boolean |
| { |
| return _showTruncationTip; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set showTruncationTip(value:Boolean):void |
| { |
| _showTruncationTip = value; |
| |
| // Typically the toolTip gets set when the text is composed, |
| // based on showToolTip and isTruncated. |
| // But showToolTip can change at runtime |
| // without later recomposing the text, |
| // so we handle that by also setting toolTip here. |
| toolTip = _isTruncated && _showTruncationTip ? text : null; |
| } |
| |
| //---------------------------------- |
| // text |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| mx_internal var _text:String = ""; |
| |
| [Inspectable(category="General", defaultValue="")] |
| |
| /** |
| * The text displayed by this text component. |
| * |
| * <p>The formatting of this text is controlled by CSS styles. |
| * The supported styles depend on the subclass.</p> |
| * |
| * @default "" |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get text():String |
| { |
| return _text; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set text(value:String):void |
| { |
| if (value != _text) |
| { |
| _text = value; |
| |
| invalidateTextLines(); |
| invalidateSize(); |
| invalidateDisplayList(); |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| /** |
| * @private |
| */ |
| override protected function measure():void |
| { |
| // _widthConstraint trumps even explicitWidth as some layouts may choose |
| // to specify width different from the explicit. |
| var constrainedWidth:Number = |
| !isNaN(_widthConstraint) ? _widthConstraint : explicitWidth; |
| |
| // for measurement, try not to collapse so small we don't measure |
| // anything |
| var fontSize:Number = getStyle("fontSize") |
| var allLinesComposed:Boolean = |
| composeTextLines(Math.max(constrainedWidth, fontSize), Math.max(explicitHeight, fontSize)); |
| |
| // Anytime we are composing we need to invalidate the display list |
| // as we may have messed up the text lines. |
| invalidateDisplayList(); |
| |
| // Put on next pixel boundary for crisp edges. If text is aligned, |
| // x and/or y may not be 0. |
| var newMeasuredHeight:Number = Math.ceil(bounds.bottom); |
| |
| // If the measured height is not affected, then constrained |
| // width measurement is not neccessary. |
| if (!isNaN(_widthConstraint) && measuredHeight == newMeasuredHeight) |
| return; |
| |
| // Call super.measure() here insted of in the beginning of the method, |
| // as it zeroes the measuredWidth, measuredHeight and these values will |
| // still be valid if we decided to do an early return above. |
| super.measure(); |
| |
| measuredWidth = Math.ceil(bounds.right); |
| measuredHeight = newMeasuredHeight; |
| |
| // Remember the number of text lines during measure. We can use this to |
| // optimize the double measure scheme for text reflow. |
| _measuredOneTextLine = allLinesComposed && |
| textLines.length == 1; |
| |
| //trace(id, drawnDisplayObject.name, "measure", measuredWidth, measuredHeight); |
| } |
| |
| /** |
| * @private |
| * We override the setLayoutBoundsSize to determine whether to perform |
| * text reflow. This is a convenient place, as the layout passes NaN |
| * for a dimension not constrained to the parent. |
| */ |
| override public function setLayoutBoundsSize(width:Number, |
| height:Number, |
| postLayoutTransform:Boolean = true):void |
| { |
| super.setLayoutBoundsSize(width, height, postLayoutTransform); |
| |
| // TODO (egeorgie): possible optimization - if we reflow the text |
| // immediately, we'll be able to detect whether the constrained |
| // width causes the measured height to change. |
| // Also certain layouts like vertical/horizontal will |
| // be able to get the better performance as subsequent elements |
| // will not go through updateDisplayList twice. This also has the |
| // potential of avoiding text compositing during measure. |
| |
| // Did we already constrain the width? |
| if (_widthConstraint == width) |
| return; |
| |
| // No reflow for explicit lineBreak |
| if (getStyle("lineBreak") == "explicit") |
| return; |
| |
| // If we don't measure |
| if (canSkipMeasurement()) |
| return; |
| |
| if (!isNaN(explicitHeight)) |
| return; |
| |
| // We support reflow only in the case of constrained width and |
| // unconstrained height. Note that we compare with measuredWidth, |
| // as for example the TextBase can be |
| // constrained by the layout with "left" and "right", but the |
| // container width itself may not be constrained and it would depend |
| // on the element's measuredWidth. |
| var constrainedWidth:Boolean = !isNaN(width) && (width != measuredWidth) && (width != 0); |
| if (!constrainedWidth) |
| return; |
| |
| // Special case - if we have a single line, then having a constraint larger |
| // than the measuredWidth will not result in measuredHeight change, as we |
| // will still have only a single line |
| if (_measuredOneTextLine && width > measuredWidth) |
| return; |
| |
| // We support reflow only when we don't have a transform. |
| // We could add support for scale, but not skew or rotation. |
| if (postLayoutTransform && hasComplexLayoutMatrix) |
| return; |
| |
| _widthConstraint = width; |
| invalidateSize(); |
| |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function updateDisplayList(unscaledWidth:Number, |
| unscaledHeight:Number):void |
| { |
| //trace(id, drawnDisplayObject.name, "updateDisplayList", unscaledWidth, unscaledHeight); |
| super.updateDisplayList(unscaledWidth, unscaledHeight); |
| |
| // Figure out if a compose is needed or maybe just clip what is already |
| // composed. |
| |
| var compose:Boolean = false; |
| var clipText:Boolean = false; |
| var contentHeight:Number = Math.ceil(bounds.bottom); |
| var contentWidth:Number = Math.ceil(bounds.right); |
| |
| // TODO (gosmith):Optimize for right-to-left text |
| // so composition isn't always done when the height or width changes. |
| if (invalidateCompose || |
| composeForAlignStyles(unscaledWidth, unscaledHeight, contentWidth, contentHeight)) |
| { |
| compose = true; |
| } |
| else if (unscaledHeight != contentHeight) |
| { |
| // Height changed. |
| if (composeOnHeightChange(unscaledHeight, contentHeight)) |
| { |
| compose = true; |
| } |
| else if (unscaledHeight < contentHeight) |
| { |
| // Don't need to recompose but need to clip since not all the |
| // height is needed. |
| clipText = true; |
| } |
| } |
| |
| // Width changed. |
| if (!compose && unscaledWidth != contentWidth) |
| { |
| if (composeOnWidthChange(unscaledWidth, contentWidth)) |
| { |
| compose = true; |
| } |
| else if (getStyle("lineBreak") == "explicit" && |
| unscaledWidth < contentWidth) |
| { |
| // Explicit line breaks. Don't need to recompose but need to |
| // clip since the not all the width is needed. |
| clipText = true; |
| } |
| } |
| |
| // Compose will add the new text lines to the display object container. |
| // Otherwise, if the text is in a shared container, make sure the |
| // position of the lines has remained the same. |
| if (compose) |
| composeTextLines(unscaledWidth, unscaledHeight); |
| |
| // If the text is overset it always has to be clipped (as well as if |
| // it is being clipped to reduce the size to avoid a recomposition). |
| if (isOverset) |
| clipText = true; |
| |
| //trace(id, drawnDisplayObject.name, "udl", "compose", compose, "clip", |
| // clipText, "bounds", bounds); |
| |
| // Set the scrollRect used for clipping appropriately. |
| clip(clipText, unscaledWidth, unscaledHeight); |
| |
| // If backgroundColor is defined, fill the bounds of the component |
| // with backgroundColor drawn with alpha level backgroundAlpha, |
| // otherwise, render a fully transparent background. |
| |
| var backgroundColor:* = getStyle("backgroundColor"); |
| var backgroundAlpha:Number = getStyle("backgroundAlpha"); |
| |
| if (backgroundColor === undefined) |
| { |
| backgroundColor = 0; |
| backgroundAlpha = 0; |
| } |
| |
| var g:Graphics = _backgroundShape.graphics; |
| g.clear(); |
| g.beginFill(uint(backgroundColor), backgroundAlpha); |
| g.drawRect(0, 0, unscaledWidth, unscaledHeight); |
| g.endFill(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Overidden Methods: ISimpleStyleClient |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| override public function styleChanged(styleProp:String):void |
| { |
| super.styleChanged(styleProp); |
| |
| invalidateTextLines(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| mx_internal function createEmptyTextLine(height:Number=NaN):void |
| { |
| // override this |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal function invalidateTextLines():void |
| { |
| invalidateCompose = true; |
| } |
| |
| /** |
| * @private |
| */ |
| private function composeForAlignStyles(unscaledWidth:Number, |
| unscaledHeight:Number, |
| contentWidth:Number, |
| contentHeight:Number):Boolean |
| { |
| // For textAlign, if the composeWidth isn't the same |
| // as the unscaledWidth, and the text isn't left aligned, we need to |
| // recompose. |
| // |
| var width:Number = isNaN(_composeWidth) ? |
| contentWidth : _composeWidth; |
| if (unscaledWidth != width) |
| { |
| var direction:String = getStyle("direction"); |
| var textAlign:String = getStyle("textAlign"); |
| |
| var leftAligned:Boolean = |
| textAlign == "left" || |
| textAlign == "start" && direction == "ltr" || |
| textAlign == "end" && direction == "rtl"; |
| |
| if (!leftAligned) |
| return true; |
| } |
| |
| // For verticalAlign, if the composeHeight isn't the same as the |
| // unscaledHeight, and the text isn't top aligned, we need to |
| // recompose (or adjust the y values of all the text lines). |
| var height:Number = isNaN(_composeHeight) ? |
| contentHeight : _composeHeight; |
| if (unscaledHeight != height) |
| { |
| var verticalAlign:String = getStyle("verticalAlign"); |
| var topAligned:Boolean = (verticalAlign == "top"); |
| |
| if (!topAligned) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * @private |
| */ |
| private function composeOnHeightChange(unscaledHeight:Number, |
| contentHeight:Number):Boolean |
| { |
| // Height increased and there may be more text. It is possible that |
| // there is more text even if isOverset isn't set. If height |
| // was specified, it will compose just enough text to fit the |
| // composition bounds and depending on text placement, it may not |
| // be overset. |
| if (unscaledHeight > contentHeight && |
| (isOverset || !isNaN(_composeHeight))) |
| return true; |
| |
| if (maxDisplayedLines != 0 && getStyle("lineBreak") == "toFit") |
| { |
| // -1 is fill the bounds and the bounds changed so recompose. |
| if (maxDisplayedLines == -1) |
| return true; |
| |
| // If truncating at n lines and the height got smaller, may need to |
| // redo the truncation. Or if the height got larger, only have to |
| // redo the truncation if we don't already have the number of |
| // truncation lines needed. |
| if (maxDisplayedLines > 0 && |
| (unscaledHeight < contentHeight || |
| textLines.length != maxDisplayedLines)) |
| { |
| return true; |
| } |
| } |
| |
| // TODO (gosmith): Handle this case properly. |
| if (getStyle("blockProgression") != "tb") |
| return true; |
| |
| return false; |
| } |
| |
| /** |
| * @private |
| */ |
| private function composeOnWidthChange(unscaledWidth:Number, |
| contentWidth:Number):Boolean |
| { |
| // If toFit, then the composeWidth must equal the unscaledWidth |
| // so that the text flows properly. |
| if (getStyle("lineBreak") == "toFit") |
| { |
| if (isNaN(_composeWidth) || _composeWidth != unscaledWidth) |
| return true; |
| } |
| |
| // TODO (gosmith): Handle this case properly. |
| if (getStyle("blockProgression") != "tb") |
| return true; |
| |
| return false; |
| } |
| |
| /** |
| * @private |
| * Returns false to indicate no lines were composed. |
| */ |
| mx_internal function composeTextLines(width:Number = NaN, |
| height:Number = NaN):Boolean |
| { |
| _composeWidth = width; |
| _composeHeight = height; |
| |
| setIsTruncated(false); |
| |
| return false; |
| } |
| |
| /** |
| * @private |
| * Adds the TextLines created by composeTextLines() to this container. |
| */ |
| mx_internal function addTextLines():void |
| { |
| var n:int = textLines.length; |
| if (n == 0) |
| return; |
| |
| for (var i:int = n - 1; i >= 0; i--) |
| { |
| var textLine:DisplayObject = textLines[i]; |
| // Add new TextLine accounting for our background Shape. |
| $addChildAt(textLine, 1); |
| } |
| } |
| |
| /** |
| * @private |
| * Removes the TextLines created by composeTextLines() |
| * from whatever container they were in. |
| * |
| * This does not empty the textLines Array. |
| */ |
| mx_internal function removeTextLines():void |
| { |
| var n:int = textLines.length; |
| if (n == 0) |
| return; |
| |
| for (var i:int = 0; i < n; i++) |
| { |
| var textLine:DisplayObject = textLines[i]; |
| var parent:UIComponent = textLine.parent as UIComponent; |
| if (parent) |
| UIComponent(textLine.parent).$removeChild(textLine); |
| } |
| } |
| |
| /** |
| * @private |
| * Adds the TextLines to the reuse cache, and clears the textLines array. |
| */ |
| mx_internal function releaseTextLines( |
| textLinesVector:Vector.<DisplayObject> = null):void |
| { |
| if (!textLinesVector) |
| textLinesVector = textLines; |
| |
| var n:int = textLinesVector.length; |
| for (var i:int = 0; i < n; i++) |
| { |
| var textLine:TextLine = textLinesVector[i] as TextLine; |
| if (textLine) |
| { |
| // Throws an ArgumentError if validity set to INVALID in |
| // either of these cases. |
| if (textLine.validity != TextLineValidity.INVALID && |
| textLine.validity != TextLineValidity.STATIC) |
| { |
| textLine.validity = TextLineValidity.INVALID; |
| } |
| |
| textLine.userData = null; // clear any userData |
| TextLineRecycler.addLineForReuse(textLine); |
| } |
| } |
| |
| textLinesVector.length = 0; |
| } |
| |
| /** |
| * @private |
| * Does the bounds of the contents rectangle fit within the bounds |
| * of the composition rectangle? |
| */ |
| mx_internal function isTextOverset(composeWidth:Number, |
| composeHeight:Number):Boolean |
| { |
| // The composition bounds available for text placement. |
| var compositionRect:Rectangle = |
| new Rectangle(0, 0, composeWidth, composeHeight); |
| |
| // Add in a half-pixel slop factor to the throw-away rectangle (do |
| // not modify bounds). This covers the case where the |
| // y (textLine.y - textLine.ascent) is slightly less than 0 because of |
| // rounding errors. |
| compositionRect.inflate(0.25, 0.25); |
| |
| // The bounds of the composed text. |
| var contentRect:Rectangle = bounds; |
| |
| // Does the text fit totally within the composition area? This is |
| // a Rectangle.contains but allows for composition width and/or height |
| // to be NaN. |
| var isOverset:Boolean = (contentRect.top < compositionRect.top || |
| contentRect.left < compositionRect.left || |
| (!isNaN(compositionRect.bottom) && |
| contentRect.bottom > compositionRect.bottom) || |
| (!isNaN(compositionRect.right) && |
| contentRect.right > compositionRect.right)); |
| |
| //trace(id, drawnDisplayObject.name, "bounds", contentRect, "overset", isOverset); |
| |
| return isOverset; |
| } |
| |
| /** |
| * Use scrollRect to clip overset lines. |
| * But don't read or write scrollRect if you can avoid it, |
| * because this causes Player 10.0 to allocate memory. |
| * And if scrollRect is already set to a Rectangle instance, |
| * reuse it rather than creating a new one. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| mx_internal function clip(clipText:Boolean, w:Number, h:Number):void |
| { |
| // TODO (rfrishbe): What if someone else sets the scrollRect? |
| if (clipText) |
| { |
| var r:Rectangle = scrollRect; |
| if (r) |
| { |
| r.x = 0; |
| r.y = 0; |
| r.width = w; |
| r.height = h; |
| } |
| else |
| { |
| r = new Rectangle(0, 0, w, h); |
| } |
| scrollRect = r; |
| hasScrollRect = true; |
| } |
| else if (hasScrollRect) |
| { |
| scrollRect = null; |
| hasScrollRect = false; |
| } |
| } |
| |
| /** |
| * @private |
| * Uses the component's CSS styles to determine the module factory |
| * that should creates its TextLines. |
| */ |
| mx_internal function getEmbeddedFontContext():IFlexModuleFactory |
| { |
| var fontContext:IFlexModuleFactory; |
| |
| var fontLookup:String = getStyle("fontLookup"); |
| if (fontLookup != FontLookup.DEVICE) |
| { |
| var font:String = getStyle("fontFamily"); |
| var bold:Boolean = getStyle("fontWeight") == "bold"; |
| var italic:Boolean = getStyle("fontStyle") == "italic"; |
| |
| fontContext = getFontContext(font, bold, italic, true); |
| } |
| |
| return fontContext; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Event handlers |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| private function resourceManager_changeHandler(event:Event):void |
| { |
| var resourceManager:IResourceManager = ResourceManager.getInstance(); |
| |
| truncationIndicatorResource = resourceManager.getString( |
| "core", "truncationIndicator"); |
| |
| // If we're truncating, recompose the text. |
| if (maxDisplayedLines != 0) |
| { |
| invalidateTextLines(); |
| |
| invalidateSize(); |
| invalidateDisplayList(); |
| } |
| } |
| |
| /** |
| * @private |
| * We clear the width constraint that's used for the text reflow |
| * after the layout pass is complete. |
| */ |
| private function updateCompleteHandler(event:FlexEvent):void |
| { |
| // Make sure that if we did a double pass, next time around we'll |
| // measure normally |
| _widthConstraint = NaN; |
| } |
| } |
| |
| } |