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