| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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> |
| * <s:RichText>Hello <s:span fontWeight="bold">World!</s:span></s:RichText> |
| * </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><p></code> and <code><br/></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><s:RichText></code> tag inherits all of the tag |
| * attributes of its superclass and adds the following tag attributes:</p> |
| * |
| * <pre> |
| * <s:RichText |
| * <strong>Properties</strong> |
| * luminosityClip="false" |
| * luminosityInvert="false" |
| * maskType="MaskType.CLIP" |
| * textFlow="<i>TextFlow</i>" |
| * /> |
| * </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> |
| * <s:RichText>Hello <s:span fontWeight="bold"/>World</s:span></s:RichText> |
| * </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(); |
| } |
| } |
| |
| } |