blob: 4e843efdecedae6980f917563ccc20a5d02edbed [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 spark.components
{
import flash.display.DisplayObject;
import flash.text.TextFormat;
import flashx.textLayout.compose.ISWFContext;
import flashx.textLayout.conversion.ConversionType;
import flashx.textLayout.conversion.ITextExporter;
import flashx.textLayout.conversion.ITextImporter;
import flashx.textLayout.conversion.TextConverter;
import flashx.textLayout.elements.Configuration;
import flashx.textLayout.elements.GlobalSettings;
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.events.DamageEvent;
import flashx.textLayout.factory.StringTextLineFactory;
import flashx.textLayout.factory.TextFlowTextLineFactory;
import flashx.textLayout.factory.TextLineFactoryBase;
import flashx.textLayout.factory.TruncationOptions;
import flashx.textLayout.formats.ITextLayoutFormat;
import flashx.textLayout.tlf_internal;
import mx.core.IEmbeddedFontRegistry;
import mx.core.IFlexModuleFactory;
import mx.core.IUIComponent;
import mx.core.Singleton;
import mx.core.UIComponent;
import mx.core.mx_internal;
import spark.components.supportClasses.TextBase;
import spark.core.CSSTextLayoutFormat;
import spark.core.MaskType;
import spark.utils.MaskUtil;
import spark.utils.TextUtil;
use namespace mx_internal;
use namespace tlf_internal
//--------------------------------------
// Styles
//--------------------------------------
include "../styles/metadata/BasicInheritingTextStyles.as"
include "../styles/metadata/BasicNonInheritingTextStyles.as"
include "../styles/metadata/AdvancedInheritingTextStyles.as"
include "../styles/metadata/AdvancedNonInheritingTextStyles.as"
//--------------------------------------
// Other metadata
//--------------------------------------
[DefaultProperty("content")]
[IconFile("RichText.png")]
[DiscouragedForProfile("mobileDevice")]
/**
* RichText is a low-level UIComponent that can display one or more lines
* of richly-formatted text and embedded images.
*
* <p>For performance reasons, it does not support scrolling,
* selection, editing, clickable hyperlinks, or images loaded from URLs.
* If you need those capabilities, please see the RichEditableText
* class.</p>
*
* <p>RichText uses the Text Layout Framework (TLF) library, which in turn builds on
* the Flash Text Engine (FTE) in Flash Player 10.
* In combination, they provide rich text layout using
* high-quality international typography.</p>
*
* <p>The Spark architecture provides three text "primitives" --
* Label, RichText, and RichEditableText.
* Label is the fastest and most lightweight
* because it uses only FTE, not TLF,
* but it is limited in its capabilities: no rich text,
* no scrolling, no selection, and no editing.
* RichText adds the ability to display rich text
* with complex layout, but is still completely non-interactive.
* RichEditableText is the heaviest-weight,
* but offers most of what TLF can do.
* You should use the lightest-weight text primitive that meets your needs.</p>
*
* <p>RichText is similar to the MX control mx.controls.Text.
* The Text control uses the older TextField class, instead of TLF,
* to display text.</p>
*
* <p>The most important differences between RichText and Text are:
* <ul>
* <li>RichText offers better typography, better support
* for international languages, and better text layout than Text.</li>
* <li>RichText has an object-oriented model of what it displays,
* while Text does not.</li>
* <li>Text is selectable, while RichText does not support selection.</li>
* </ul></p>
*
* <p>RichText uses TLF's object-oriented model of rich text,
* in which text layout elements such as divisions, paragraphs, spans,
* and images are represented at runtime by ActionScript objects
* which can be programmatically accessed and manipulated.
* The central object in TLF for representing rich text is a
* TextFlow, and you specify what RichText should display
* by setting its <code>textFlow</code> property to a TextFlow instance.
* (Please see the description of the <code>textFlow</code>
* property for information about how to create one.)
* You can also set the <code>text</code> property that
* is inherited from TextBase, but if you don't need
* the richness of a TextFlow, you should consider using
* Label instead.</p>
*
* <p>At compile time, you can put TLF markup tags inside
* the RichText tag, as the following example shows:
* <pre>
* &lt;s:RichText&gt;Hello &lt;s:span fontWeight="bold"&gt;World!&lt;/s:span&gt;&lt;/s:RichText&gt;
* </pre>
* In this case, the MXML compiler sets the <code>content</code>
* property, causing a TextFlow to be automatically created
* from the FlowElements that you specify.</p>
*
* <p>The default text formatting is determined by CSS styles
* such as <code>fontFamily</code>, <code>fontSize</code>.
* Any formatting information in the TextFlow overrides
* the default formatting provided by the CSS styles.</p>
*
* <p>You can control the spacing between lines with the
* <code>lineHeight</code> style and the spacing between
* paragraphs with the <code>paragraphSpaceBefore</code>
* and <code>paragraphSpaceAfter</code> styles.
* You can align or justify the text using the <code>textAlign</code>
* and <code>textAlignLast</code> styles.
* You can inset the text from the component's edges using the
* <code>paddingLeft</code>, <code>paddingTop</code>,
* <code>paddingRight</code>, and <code>paddingBottom</code> styles.</p>
*
* <p>If you don't specify any kind of width for a RichText,
* then the longest line, as determined by these explicit line breaks,
* determines the width of the Label.</p>
*
* <p>When you specify a width, the text wraps at the right
* edge of the component and the text is clipped when there is more
* text than fits.
* If you set the <code>lineBreak</code> style to <code>explicit</code>,
* new lines will start only at explicit lines breaks, such as
* if you use CR (<code>\r</code>), LF (<code>\n</code>),
* or CR+LF (<code>\r\n</code>) in <code>text</code>
* or if you use <code>&lt;p&gt;</code> and <code>&lt;br/&gt;</code>
* in TLF markup. In that case, lines that are wider than the control
* are clipped.</p>
*
* <p>If you have more text than you have room to display it,
* RichText can truncate the text for you.
* Truncating text means replacing excess text
* with a truncation indicator such as "...".
* See the inherited properties <code>maxDisplayedLines</code>
* and <code>isTruncated</code>.</p>
*
* <p>By default,RichText has no background,
* but you can draw one using the <code>backgroundColor</code>
* and <code>backgroundAlpha</code> styles.
* Borders are not supported.
* If you need a border, or a more complicated background, use a separate
* graphic element, such as a Rect, behind the RichText.</p>
*
* <p>Because RichText uses TLF,
* it supports displaying left-to-right (LTR) text such as French,
* right-to-left (RTL) text such as Arabic, and bidirectional text
* such as a French phrase inside of an Arabic one.
* If the predominant text direction is right-to-left,
* set the <code>direction</code> style to <code>rtl</code>.
* The <code>textAlign</code> style defaults to <code>"start"</code>,
* which makes the text left-aligned when <code>direction</code>
* is <code>ltr</code> and right-aligned when <code>direction</code>
* is <code>rtl</code>.
* To get the opposite alignment,
* set <code>textAlign</code> to <code>end</code>.</p>
*
* <p>RichText uses TLF's StringTextFlowFactory and TextFlowTextLineFactory
* classes to create one or more TextLine objects to statically display
* its text.
* For performance, its TextLines do not contain information
* about individual glyphs; for more info, see the TextLineValidity class.</p>
*
* <p>To use this component in a list-based component, such as a List or DataGrid,
* create an item renderer.
* For information about creating an item renderer, see
* <a href="http://help.adobe.com/en_US/flex/using/WS4bebcd66a74275c3-fc6548e124e49b51c4-8000.html">
* Custom Spark item renderers</a>. </p>
*
* @mxml
*
* <p>The <code>&lt;s:RichText&gt;</code> tag inherits all of the tag
* attributes of its superclass and adds the following tag attributes:</p>
*
* <pre>
* &lt;s:RichText
* <strong>Properties</strong>
* luminosityClip="false"
* luminosityInvert="false"
* maskType="MaskType.CLIP"
* textFlow="<i>TextFlow</i>"
* /&gt;
* </pre>
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*
* @see spark.components.RichEditableText
* @see spark.components.Label
* @see flash.text.engine.TextLineValidity
*
* @includeExample examples/RichTextExample.mxml
*/
public class RichText extends TextBase
{
include "../core/Version.as";
//--------------------------------------------------------------------------
//
// Class variables
//
//--------------------------------------------------------------------------
/**
* @private
*/
private static var classInitialized:Boolean = false;
/**
* @private
* This TLF object composes TextLines from a text String.
* We use it when the 'text' property is set to a String
* that doesn't contain linebreaks.
*/
private static var staticStringFactory:StringTextLineFactory;
/**
* @private
* This TLF object composes TextLines from a TextFlow.
* We use it when the 'textFlow' or 'content' property is set,
* and when the 'text' property is set to a String
* that contains linebreaks (and therefore is interpreted
* as multiple paragraphs).
*/
private static var staticTextFlowFactory:TextFlowTextLineFactory;
/**
* @private
* This TLF object is used to import a 'text' String
* containing linebreaks to create a multiparagraph TextFlow.
*/
private static var staticPlainTextImporter:ITextImporter;
/**
* @private
* This TLF object is used to export a TextFlow as plain 'text',
* by walking the leaf FlowElements in the TextFlow.
*/
private static var staticPlainTextExporter:ITextExporter;
//--------------------------------------------------------------------------
//
// Class methods
//
//--------------------------------------------------------------------------
/**
* @private
* This method initializes the static vars of this class.
* Rather than calling it at static initialization time,
* we call it in the constructor to do the class initialization
* when the first instance is created.
* (It does an immediate return if it has already run.)
* By doing so, we avoid any static initialization issues
* related to whether this class or the TLF classes
* that it uses are initialized first.
*/
private static function initClass():void
{
if (classInitialized)
return;
// Set the TLF hook used for localizing runtime error messages.
// TLF itself has English-only messages,
// but higher layers like Flex can provide localized versions.
GlobalSettings.resourceStringFunction = TextUtil.getResourceString;
// Set the TLF hook used to specify the callback used for changing
// the FontLookup based on SWFContext.
GlobalSettings.resolveFontLookupFunction = TextUtil.resolveFontLookup;
// Pre-FP10.1, set default tab stops in TLF. Without this, if there
// is a tab and TLF is measuring width, the tab will
// measure as the rest of the remaining width up to 10000.
GlobalSettings.enableDefaultTabStops = !Configuration.playerEnablesArgoFeatures;
staticStringFactory = new StringTextLineFactory();
staticTextFlowFactory = new TextFlowTextLineFactory();
staticPlainTextImporter =
TextConverter.getImporter(TextConverter.PLAIN_TEXT_FORMAT);
staticPlainTextExporter =
TextConverter.getExporter(TextConverter.PLAIN_TEXT_FORMAT);
classInitialized = true;
}
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function RichText()
{
super();
initClass();
text = "";
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
* This object determines the default text formatting used
* by this component, based on its CSS styles.
* It is set to null by stylesInitialized() and styleChanged(),
* and recreated whenever necessary in commitProperties().
*/
private var hostFormat:ITextLayoutFormat;
/**
* @private
* Holds the last recorded value of the textFlow generation for _textFlow.
* Used to determine whether to return immediately from damage event if
* there have been no changes.
*/
private var lastGeneration:uint = 0; // 0 means not set
/**
* @private
* Holds the last recorded value of the module factory
* used to create the font.
*/
mx_internal var embeddedFontContext:IFlexModuleFactory;
/**
* @private
* Specifies whether the StringTextLineFactory
* or the TextFlowTextLineFactory is used to create the TextLines.
* A StringTextLineFactory is more efficient; it is used
* by default to render the default text ""
* and when 'text' is set to a string without linebreaks;
* otherwise, a TextFlowTextLineFactory is used.
*/
private var factory:TextLineFactoryBase;
/**
* @private
* If true, the damage handler will return immediately.
*/
private var ignoreDamageEvent:Boolean;
//--------------------------------------------------------------------------
//
// Overridden properties
//
//--------------------------------------------------------------------------
//----------------------------------
// text
//----------------------------------
// Compiler will strip leading and trailing whitespace from text string.
[CollapseWhiteSpace]
// The _text storage var is mx_internal in TextBase.
/**
* @private
*/
private var textChanged:Boolean = false;
/**
* @private
* Source of text: one of "text", "textFlow" or "content".*
*/
private var source:String = "";
/**
* @private
*/
override public function get text():String
{
// Extracting the plaintext from a TextFlow is somewhat expensive,
// as it involves iterating over the leaf FlowElements in the TextFlow.
// Therefore we do this extraction only when necessary, namely when
// you first set the 'content' or the 'textFlow'
// (or mutate the TextFlow), and then get the 'text'.
if (_text == null)
{
// If 'content' was last set,
// we have to first turn that into a TextFlow.
if (contentChanged)
{
_textFlow = createTextFlowFromContent(_content);
lastGeneration = _textFlow.generation;
}
// Once we have a TextFlow, we can export its plain text.
_text = staticPlainTextExporter.export(
_textFlow, ConversionType.STRING_TYPE) as String;
}
return _text;
}
/**
* @private
* This will create a TextFlow with a single paragraph with a single span
* with exactly the text specified. If there is whitespace and line
* breaks in the text, they will remain, regardless of the settings of
* the lineBreak and whiteSpaceCollapse styles.
*/
override public function set text(value:String):void
{
// Treat setting the 'text' to null
// as if it were set to the empty String
// (which is the default state).
if (value == null)
value = "";
// If the most recent change to _text was caused by calling this method,
// then it's safe to short-cicuit in the same way that TextBase does.
if ((source == "text") && (value == _text))
return;
// Don't return early if value is the same as _text,
// because _text might have been produced from setting
// 'textFlow' or 'content'.
// For example, if you set a TextFlow corresponding to
// "Hello <span color="OxFF0000">World</span>"
// and then get the 'text', it will be the String "Hello World"
// But if you then set the 'text' to "Hello World"
// this represents a change: the "World" should no longer be red.
_text = value;
textChanged = true;
source = "text";
// If more than one of 'text', 'textFlow', and 'content' is set,
// the last one set wins.
textFlowChanged = false;
contentChanged = false;
// If there was a textFlow remove its damage handler.
removeDamageHandler();
// The other two are now invalid and must be recalculated when needed.
_textFlow = null;
_content = null;
factory = staticStringFactory;
invalidateTextLines();
invalidateProperties();
invalidateSize();
invalidateDisplayList();
}
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// content
//----------------------------------
/**
* @private
* Storage for the content property.
*/
protected var _content:Object;
/**
* @private
*/
private var contentChanged:Boolean = false;
/**
* @private
* This metadata tells the MXML compiler to disable some of its default
* interpretation of the value specified for the 'content' property.
* Normally, for properties of type Object, it assumes that things
* looking like numbers are numbers and things looking like arrays
* are arrays. But <content>1</content> should generate code to set the
* content to the String "1", not the int 1, and <content>[1]</content>
* should set it to the String "[1]", not the Array [ 1 ].
* However, {...} continues to be interpreted as a databinding
* expression, and @Resource(...), @Embed(...), etc.
* as compiler directives.
* Similar metadata on TLF classes causes the same rules to apply
* within <p>, <span>, etc.
*/
[RichTextContent]
/**
* This property is intended for use in MXML at compile time;
* to get or set rich text content at runtime,
* please use the <code>textFlow</code> property instead.
*
* <p>The <code>content</code> property is the default property
* for RichText, so that you can write MXML such as
* <pre>
* &lt;s:RichText&gt;Hello &lt;s:span fontWeight="bold"/&gt;World&lt;/s:span&gt;&lt;/s:RichText&gt;
* </pre>
* and have the String and SpanElement that you specify
* as the content be used to create a TextFlow.</p>
*
* <p>This property is typed as Object because you can set it to
* to a String, a FlowElement, or an Array of Strings and FlowElements.
* In the example above, you are specifying the content
* to be a 2-element Array whose first element is the String
* "Hello" and whose second element is a SpanElement with the text
* "World" in boldface.</p>
*
* <p>No matter how you specify the content, it gets converted
* into a TextFlow, and when you get this property, you will get
* the resulting TextFlow.</p>
*
* <p>Adobe recommends using <code>textFlow</code> property
* to get and set rich text content at runtime,
* because it is strongly typed as a TextFlow
* rather than as an Object.
* A TextFlow is the canonical representation
* for rich text content in the Text Layout Framework.</p>
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get content():Object
{
return textFlow;
}
/**
* @private
*/
public function set content(value:Object):void
{
// Treat setting the 'content' to null
// as if 'text' were being set to the empty String
// (which is the default state).
if (value == null)
{
text = "";
return;
}
if (value == _content)
return;
_content = value;
contentChanged = true;
source = "content";
// If more than one of 'text', 'textFlow', and 'content' is set,
// the last one set wins.
textChanged = false;
textFlowChanged = false;
// If there was a textFlow remove its damage handler.
removeDamageHandler();
// The other two are now invalid and must be recalculated when needed.
_text = null;
_textFlow = null;
factory = staticTextFlowFactory;
invalidateTextLines();
invalidateProperties();
invalidateSize();
invalidateDisplayList();
}
//----------------------------------
// mask
//----------------------------------
/**
* @private
*/
private var maskChanged:Boolean;
/**
* @private
*/
override public function set mask(value:DisplayObject):void
{
if (super.mask == value)
return;
var oldMask:UIComponent = super.mask as UIComponent;
super.mask = value;
// If the old mask was attached by us, then we need to
// undo the attachment logic
if (oldMask && oldMask.$parent === this)
{
if (oldMask.parent is UIComponent)
UIComponent(oldMask.parent).childRemoved(oldMask);
oldMask.$parent.removeChild(oldMask);
}
maskChanged = true;
maskTypeChanged = true;
invalidateProperties();
invalidateDisplayList();
}
//----------------------------------
// maskType
//----------------------------------
/**
* @private
* Storage for the maskType property.
*/
private var _maskType:String = MaskType.CLIP;
/**
* @private
*/
private var maskTypeChanged:Boolean;
[Inspectable(defaultValue="clip", enumeration="clip,alpha,luminosity")]
/**
* <p>The maskType defines how the mask is applied to the component.</p>
*
* <p>The possible values are <code>MaskType.CLIP</code>, <code>MaskType.ALPHA</code> and
* <code>MaskType.LUMINOSITY</code>.</p>
*
* <p><strong>Clip Masking</strong></p>
*
* <p>When masking in clip mode, a clipping masks is reduced to 1-bit. This means that a mask will
* not affect the opacity of a pixel in the source content; it either leaves the value unmodified,
* if the corresponding pixel in the mask is has a non-zero alpha value, or makes it fully
* transparent, if the mask pixel value has an alpha value of zero.</p>
*
* <p>When clip masking is used, only the actual path and shape vectors and fills defined by the
* mask are used to determine the effect on the source content. strokes and bitmap filters
* defined on the mask are ignored. Any filled region in the mask is considered filled, and renders
* the source content. The type and parameters of the fill is irrelevant; a solid color fill,
* gradient fill, or bitmap fill in a mask will all render the underlying source content, regardless
* of the alpha values of the mask fill.</p>
*
* <p>BitmapGraphics are treated as bitmap filled rectangles when used in a clipping mask. As a
* result, the alpha channel of the source bitmap is irrelevant when part of a mask -- the bitmap
* affects the mask in the same manner as solid filled rectangle of equivalent dimensions.</p>
*
* <p><strong>Alpha Masking</strong></p>
*
* <p>In alpha mode, the opacity of each pixel in the source content is multiplied by the opacity
* of the corresponding region of the mask. i.e., a pixel in the source content with an opacity of
* 1 that is masked by a region of opacity of .5 will have a resulting opacity of .5. A source pixel
* with an opacity of .8 masked by a region with opacity of .5 will have a resulting opacity of .4.</p>
*
* <p>Conceptually, alpha masking is equivalent to rendering the transformed mask and source content
* into separate RGBA surfaces, and multiplying the alpha channel of the mask content into the alpha
* channel of the source content. All of the mask content is rendered into its surface before
* compositing into the source content's surface. As a result, all FXG features, such as strokes,
* bitmap filters, and fill opacity will affect the final composited content.</p>
*
* <p>When in alpha mode, the alpha channel of any bitmap data is composited normally into the mask
* alpha channel, and will affect the final rendered content. This holds true for both BitmapGraphics
* and bitmap filled shapes and paths.</p>
*
* <p><strong>Luminosity Masking</strong></p>
*
* <p>A luminosity mask, sometimes called a 'soft mask', works very similarly to an alpha mask
* except that both the opacity and RGB color value of a pixel in the source content is multiplied
* by the opacity and RGB color value of the corresponding region in the mask.</p>
*
* <p>Conceptually, luminosity masking is equivalent to rendering the transformed mask and source content
* into separate RGBA surfaces, and multiplying the alpha channel and the RGB color value of the mask
* content into the alpha channel and RGB color value of the source content. All of the mask content is
* rendered into its surface before compositing into the source content's surface. As a result, all FXG
* features, such as strokes, bitmap filters, and fill opacity will affect the final composited
* content.</p>
*
* <p>Luminosity masking is not native to Flash but is common in Adobe Creative Suite tools like Adobe
* Illustrator and Adobe Photoshop. In order to accomplish the visual effect of a luminosity mask in
* Flash-rendered content, a graphic element specifying a luminosity mask actually instantiates a shader
* filter that mimics the visual look of a luminosity mask as rendered in Adobe Creative Suite tools.</p>
*
* <p>Objects being masked by luminosity masks can set properties to control the RGB color value and
* clipping of the mask. See the luminosityInvert and luminosityClip attributes.</p>
*
* @see spark.core.MaskType
*
* @default MaskType.CLIP
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get maskType():String
{
return _maskType;
}
/**
* @private
*/
public function set maskType(value:String):void
{
if (_maskType == value)
return;
_maskType = value;
maskTypeChanged = true;
invalidateProperties();
}
//----------------------------------
// luminosityInvert
//----------------------------------
/**
* @private
* Storage for the luminosityInvert property.
*/
private var _luminosityInvert:Boolean = false;
/**
* @private
*/
private var luminositySettingsChanged:Boolean;
[Inspectable(defaultValue="false")]
/**
* A property that controls the calculation of the RGB
* color value of a graphic element being masked by
* a luminosity mask. If true, the RGB color value of a
* pixel in the source content is inverted and multipled
* by the corresponding region in the mask. If false,
* the source content's pixel's RGB color value is used
* directly.
*
* @default false
* @see #maskType
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get luminosityInvert():Boolean
{
return _luminosityInvert;
}
/**
* @private
*/
public function set luminosityInvert(value:Boolean):void
{
if (_luminosityInvert == value)
return;
_luminosityInvert = value;
luminositySettingsChanged = true;
}
//----------------------------------
// luminosityClip
//----------------------------------
/**
* @private
* Storage for the luminosityClip property.
*/
private var _luminosityClip:Boolean = false;
[Inspectable(defaultValue="false")]
/**
* A property that controls whether the luminosity
* mask clips the masked content. This property can
* only have an effect if the graphic element has a
* mask applied to it that is of type
* <code>MaskType.LUMINOSITY</code>.
*
* @default false
* @see #maskType
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get luminosityClip():Boolean
{
return _luminosityClip;
}
/**
* @private
*/
public function set luminosityClip(value:Boolean):void
{
if (_luminosityClip == value)
return;
_luminosityClip = value;
luminositySettingsChanged = true;
}
//----------------------------------
// textFlow
//----------------------------------
/**
* @private
* Storage for the textFlow property.
*/
private var _textFlow:TextFlow;
/**
* @private
*/
private var textFlowChanged:Boolean = false;
/**
* The TextFlow representing the rich text displayed by this component.
*
* <p>A TextFlow is the most important class
* in the Text Layout Framework (TLF).
* It is the root of a tree of FlowElements
* representing rich text content.</p>
*
* <p>You normally create a TextFlow from TLF markup
* using the <code>TextFlowUtil.importFromString()</code>
* or <code>TextFlowUtil.importFromXML()</code> methods.
* Alternately, you can use TLF's TextConverter class
* (which can import a subset of HTML) or build a TextFlow
* using methods like <code>addChild()</code> on TextFlow.</p>
*
* <p>Setting this property affects the <code>text</code> property
* and vice versa.</p>
*
* <p>If you set the <code>textFlow</code> and get the <code>text</code>,
* the text in each paragraph will be separated by a single
* LF (<code>\n</code>).</p>
*
* <p>If you set the <code>text</code> to a String such as
* <code>"Hello World"</code> and get the <code>textFlow</code>,
* it will be a TextFlow containing a single ParagraphElement
* with a single SpanElement.</p>
*
* <p>If the text contains explicit line breaks --
* CR (<code>\r</code>), LF (<code>\n</code>), or CR+LF (<code>\r\n</code>) --
* then the content will be set to a TextFlow
* which contains multiple paragraphs, each with one span.</p>
*
* <p>To turn a TextFlow object into TLF markup,
* use the markup returned from the <code>TextFlowUtil.export()</code> method.</p>
*
* @see spark.utils.TextFlowUtil#importFromString()
* @see spark.utils.TextFlowUtil#importFromXML()
* @see spark.components.RichEditableText#text
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get textFlow():TextFlow
{
// We might not have a valid _textFlow for two reasons:
// either because the 'text' was set (which is the state
// after construction) or because the 'content' was set.
if (!_textFlow)
{
if (_content != null)
_textFlow = createTextFlowFromContent(_content);
else
_textFlow = staticPlainTextImporter.importToFlow(_text);
lastGeneration = _textFlow ? _textFlow.generation : 0;
}
_textFlow.addEventListener(DamageEvent.DAMAGE,
textFlow_damageHandler);
// Ensure our textFlow has the most appropriate
// swf context associated with it.
if (_textFlow.flowComposer)
{
_textFlow.flowComposer.swfContext =
ISWFContext(getEmbeddedFontContext());
}
return _textFlow;
}
/**
* @private
*/
public function set textFlow(value:TextFlow):void
{
// Treat setting the 'textFlow' to null
// as if 'text' were being set to the empty String
// (which is the default state).
if (value == null)
{
text = "";
return;
}
if (value == _textFlow)
return;
// If there was a textFlow remove its damage handler.
removeDamageHandler();
_textFlow = value;
textFlowChanged = true;
source = "textFlow";
// If more than one of 'text', 'textFlow', and 'content' is set,
// the last one set wins.
textChanged = false;
contentChanged = false;
// The other two are now invalid and must be recalculated when needed.
_text = null
_content = null;
factory = staticTextFlowFactory;
invalidateTextLines();
invalidateProperties();
invalidateSize();
invalidateDisplayList();
}
//--------------------------------------------------------------------------
//
// Overridden methods: UIComponent
//
//--------------------------------------------------------------------------
/**
* @private
*/
override protected function commitProperties():void
{
super.commitProperties();
// Only one of textChanged, textFlowChanged, and contentChanged
// will be true; the other two will be false because each setter
// guarantees this.
if (textChanged)
{
// If the text has linebreaks (CR, LF, or CF+LF)
// create a multi-paragraph TextFlow from it
// and use the TextFlowTextLineFactory to render it.
// Otherwise the StringTextLineFactory will put
// all of the lines into a single paragraph
// and FTE performance will degrade on a large paragraph.
if (_text.indexOf("\n") != -1 || _text.indexOf("\r") != -1)
{
_textFlow = staticPlainTextImporter.importToFlow(_text);
factory = staticTextFlowFactory;
}
textChanged = false;
}
else if (textFlowChanged)
{
// Nothing to do at commitProperties() time.
textFlowChanged = false;
}
else if (contentChanged)
{
_textFlow = createTextFlowFromContent(_content);
contentChanged = false;
}
lastGeneration = _textFlow ? _textFlow.generation : 0;
// At this point we know which TextLineFactory we're going to use
// and we know the _text or _textFlow that it will compose.
// If the styles have changed, hostFormat will have
// been set to null to indicate that it is invalid.
// In that case, create a new one.
if (!hostFormat)
{
hostFormat = new CSSTextLayoutFormat(this);
// Note: CSSTextLayoutFormat has special processing
// for the fontLookup style. If it is "auto",
// the fontLookup format is set to either
// "device" or "embedded" depending on whether
// embeddedFontContext is null or non-null.
}
if (_textFlow)
{
// We might have a new TextFlow, or a new hostFormat,
// so attach the latter to the former.
_textFlow.hostFormat = hostFormat;
// Add a damage handler.
_textFlow.addEventListener(DamageEvent.DAMAGE,
textFlow_damageHandler);
}
if (maskChanged)
{
if (mask && !mask.parent)
{
addChild(mask);
MaskUtil.applyMask(mask, null);
}
maskChanged = false;
}
if (luminositySettingsChanged)
{
MaskUtil.applyLuminositySettings(
mask, _maskType, _luminosityInvert, _luminosityClip);
luminositySettingsChanged = false;
}
if (maskTypeChanged)
{
MaskUtil.applyMaskType(
mask, _maskType, _luminosityInvert, _luminosityClip, this);
maskTypeChanged = false;
}
}
/**
* @private
*/
override public function stylesInitialized():void
{
super.stylesInitialized();
// The old hostFormat is invalid
// and a new one must be created.
hostFormat = null;
}
/**
* @private
*/
override public function styleChanged(styleProp:String):void
{
super.styleChanged(styleProp);
// The old hostFormat is invalid
// and a new one must be created.
hostFormat = null;
invalidateTextLines();
invalidateProperties();
invalidateSize();
invalidateDisplayList();
}
/**
* @private
*/
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void
{
// The factory will compose just enough lines to fill the
// compositionHeight. If not all the text is composed, the reported
// contentHeight will be an estimate of what the height will be when
// it is all composed and there will not be textLines to back it up.
// There is no easy way to tell if the contentHeight is the actual
// value or an estimated value.
if (!isNaN(_composeHeight) && unscaledHeight != _composeHeight)
{
invalidateTextLines();
}
super.updateDisplayList(unscaledWidth, unscaledHeight);
}
//--------------------------------------------------------------------------
//
// Overridden methods: TextBase
//
//--------------------------------------------------------------------------
/**
* @private
* Returns true to indicate all lines were composed.
*/
override mx_internal function composeTextLines(width:Number = NaN,
height:Number = NaN):Boolean
{
// If there is no explicit width but there is an explicit
// maxWidth use that.
if (isNaN(width) && !isNaN(explicitMaxWidth))
width = explicitMaxWidth;
super.composeTextLines(width, height);
// Ignore damage events while we're re-composing the text lines.
ignoreDamageEvent = true;
// Set the composition bounds to be used by createTextLines().
// If there is no explicit width, and there is no explicit maxWidth,
// the width will be computed by this method.
// If the height is NaN, it will be computed by this method
// by the time it returns.
// The bounds are then used by the addTextLines() method
// to determine the isOverset flag.
// The composition bounds are also reported by the measure() method.
bounds.x = 0;
bounds.y = 0;
bounds.width = width;
bounds.height = height;
removeTextLines();
releaseTextLines();
createTextLines();
// Truncation only done if not measuring width and line breaks are
// toFit. So if we are measuring, create the text lines to figure
// out their size and then recreate them using this size so truncation
// will be done.
if (maxDisplayedLines != 0 && !isTruncated &&
getStyle("lineBreak") == "toFit")
{
var bp:String = getStyle("blockProgression");
if ((isNaN(width) && bp == "tb") || (isNaN(height) && bp != "tb"))
{
textLines.length = 0;
// bounds contains the measured size of the lines created above
createTextLines();
}
}
// Add the new text lines to the container.
addTextLines();
// Figure out if the text overruns the available space for composition.
isOverset = isTextOverset(width, height);
// Just recomposed so reset.
invalidateCompose = false;
// Listen for "damage" events in case the textFlow is
// modified programatically.
ignoreDamageEvent = false;
// Created all lines.
return true;
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* @private
*/
private function createTextFlowFromContent(content:Object):TextFlow
{
var textFlow:TextFlow ;
if (content is TextFlow)
{
textFlow = content as TextFlow;
}
else if (content is Array)
{
textFlow = new TextFlow();
textFlow.whiteSpaceCollapse = getStyle("whiteSpaceCollapse");
textFlow.mxmlChildren = content as Array;
textFlow.whiteSpaceCollapse = undefined;
}
else
{
textFlow = new TextFlow();
textFlow.whiteSpaceCollapse = getStyle("whiteSpaceCollapse");
textFlow.mxmlChildren = [ content ];
textFlow.whiteSpaceCollapse = undefined;
}
return textFlow;
}
/**
* @private
* Uses TextLineFactory to compose the textFlow
* into as many TextLines as fit into the bounds.
*/
private function createTextLines():void
{
// Clear any previously generated TextLines from the textLines Array.
textLines.length = 0;
// Note: Even if we have nothing to compose, we nevertheless
// use the StringTextLineFactory to compose an empty string.
// Since it appends the paragraph terminator "\u2029",
// it actually creates and measures one TextLine.
// Its width is 0 but its height is equal to the font's
// ascent plus descent.
factory.compositionBounds = bounds;
// Set up the truncation options.
var truncationOptions:TruncationOptions;
if (maxDisplayedLines != 0)
{
truncationOptions = new TruncationOptions();
truncationOptions.lineCountLimit = maxDisplayedLines;
truncationOptions.truncationIndicator =
TextBase.truncationIndicatorResource;
}
factory.truncationOptions = truncationOptions;
// If the CSS styles for this component specify an embedded font,
// embeddedFontContext will be set to the module factory that
// should create TextLines (since they must be created in the
// SWF where the embedded font is). Otherwise, this will be null.
embeddedFontContext = getEmbeddedFontContext();
if (factory is StringTextLineFactory)
{
// We know text is non-null since it got this far.
staticStringFactory.text = _text;
staticStringFactory.textFlowFormat = hostFormat;
staticStringFactory.swfContext = ISWFContext(embeddedFontContext);
staticStringFactory.createTextLines(addTextLine);
}
else if (factory is TextFlowTextLineFactory)
{
if (_textFlow && _textFlow.flowComposer)
{
_textFlow.flowComposer.swfContext =
ISWFContext(embeddedFontContext);
}
staticTextFlowFactory.swfContext = ISWFContext(embeddedFontContext);
staticTextFlowFactory.createTextLines(addTextLine, _textFlow);
}
bounds = factory.getContentBounds();
setIsTruncated(factory.isTruncated);
}
/**
* @private
* Uses StringTextLineFactory to compose an empty string so we can
* determine the baseline from the text line. The height is important
* if the text is vertically aligned.
*/
override mx_internal function createEmptyTextLine(height:Number=NaN):void
{
// Clear any previously generated TextLines from the textLines Array.
textLines.length = 0;
// Note:
// Use the StringTextLineFactory to compose an empty string.
// Since it appends the paragraph terminator "\u2029",
// it actually creates and measures one TextLine.
// Its width is 0 but its height is equal to the font's
// ascent plus descent.
// Note: Prior to TLF2, the factory would callback addTextLine
// when width=0 but that has been optimized out. Now width to NaN.
bounds.x = 0;
bounds.y = 0;
bounds.width = width ? width : NaN;
bounds.height = height;
staticStringFactory.compositionBounds = bounds;
// Set up the truncation options.
staticStringFactory.truncationOptions = null;
staticStringFactory.text = "";
staticStringFactory.textFlowFormat = hostFormat;
staticStringFactory.createTextLines(addTextLine);
}
/**
* @private
* Callback passed to createTextLines().
*/
private function addTextLine(textLine:DisplayObject):void
{
textLines.push(textLine);
}
/**
* @private
* Make sure to remove the damage handler before resetting the text flow.
*/
private function removeDamageHandler():void
{
// Could check factory is TextFlowTextLineFactory but be safe and
// try to remove whenever there is a text flow.
if (_textFlow != null)
{
_textFlow.removeEventListener(DamageEvent.DAMAGE,
textFlow_damageHandler);
}
}
//--------------------------------------------------------------------------
//
// Event handlers
//
//--------------------------------------------------------------------------
/**
* @private
* Called when the TextFlow dispatches a 'damage' event
* to indicate it has been modified. This could mean the styles changed
* or the content changed, or both changed.
*/
private function textFlow_damageHandler(event:DamageEvent):void
{
// If there are no changes to the generation, don't recompose.
// The TextFlowFactory createTextLines dispatches damage events every
// time the textFlow is composed, even if there are no changes.
if (ignoreDamageEvent || _textFlow.generation == lastGeneration)
return;
// Update the last know generation for _textFlow.
lastGeneration = _textFlow.generation;
// Invalidate _text and _content.
_text = null;
_content = null;
// After the TextFlow has been mutated,
// we must render it, not the 'text' String.
factory = staticTextFlowFactory;
// Force recompose since text and/or styles may have changed.
invalidateTextLines();
// We don't need to call invalidateProperties()
// because the hostFormat and the _textFlow are still valid.
// This is smart enough not to remeasure if the explicit width/height
// were specified.
invalidateSize();
invalidateDisplayList();
}
}
}