| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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.events.Event; |
| import flash.geom.Rectangle; |
| import flash.text.engine.TextBlock; |
| import flash.text.engine.TextElement; |
| import flash.text.engine.TextLine; |
| import flash.text.engine.TextLineCreationResult; |
| import flash.text.engine.TextLineValidity; |
| |
| import flashx.textLayout.container.ContainerController; |
| import flashx.textLayout.debug.Debugging; |
| import flashx.textLayout.debug.assert; |
| import flashx.textLayout.elements.ContainerFormattedElement; |
| import flashx.textLayout.elements.FlowElement; |
| import flashx.textLayout.elements.FlowGroupElement; |
| import flashx.textLayout.elements.FlowLeafElement; |
| import flashx.textLayout.elements.InlineGraphicElement; |
| import flashx.textLayout.elements.OverflowPolicy; |
| import flashx.textLayout.elements.ParagraphElement; |
| import flashx.textLayout.elements.TextFlow; |
| import flashx.textLayout.formats.BaselineOffset; |
| import flashx.textLayout.formats.BlockProgression; |
| import flashx.textLayout.formats.Direction; |
| import flashx.textLayout.formats.Float; |
| import flashx.textLayout.formats.ITextLayoutFormat; |
| import flashx.textLayout.formats.ListStylePosition; |
| import flashx.textLayout.formats.TextAlign; |
| import flashx.textLayout.formats.VerticalAlign; |
| import flashx.textLayout.tlf_internal; |
| import flashx.textLayout.utils.Twips; |
| |
| use namespace tlf_internal; |
| |
| [ExcludeClass] |
| /** Keeps track of internal state during composition. |
| * |
| * This is the simpler version, used when there are no floats, no wraps, no columns. |
| * @private |
| */ |
| public class ComposeState extends BaseCompose |
| { |
| /** Index of current line */ |
| protected var _curLineIndex:int; |
| |
| // for figuring out when to do VJ |
| protected var vjBeginLineIndex:int; |
| protected var vjDisableThisParcel:Boolean; |
| |
| protected var _useExistingLine:Boolean; |
| |
| // a single ComposeState that is checked out and checked in |
| static private var _sharedComposeState:ComposeState; |
| |
| /** @private */ |
| static tlf_internal function getComposeState():ComposeState |
| { |
| var rslt:ComposeState = _sharedComposeState; |
| if (rslt) |
| { |
| _sharedComposeState = null; |
| return rslt; |
| } |
| return new ComposeState(); |
| } |
| |
| /** @private */ |
| static tlf_internal function releaseComposeState(state:ComposeState):void |
| { |
| state.releaseAnyReferences(); |
| _sharedComposeState = state; |
| } |
| |
| /** @private */ |
| protected override function createParcelList():ParcelList |
| { |
| return ParcelList.getParcelList(); |
| } |
| /** @private */ |
| protected override function releaseParcelList(list:ParcelList):void |
| { |
| ParcelList.releaseParcelList(list); |
| } |
| |
| /** @private */ |
| public override function composeTextFlow(textFlow:TextFlow, composeToPosition:int, controllerEndIndex:int):int |
| { |
| CONFIG::debug { assert(textFlow.flowComposer is StandardFlowComposer,"ComposeState.composeTextFlow requires textFlow with a StandardFlowComposer"); } |
| _curLineIndex = -1; // unitialized: this will get set in composeParagraphElement |
| _curLine = null; |
| |
| vjBeginLineIndex = 0; |
| vjDisableThisParcel = false; |
| |
| return super.composeTextFlow(textFlow, composeToPosition, controllerEndIndex); |
| } |
| |
| protected override function initializeForComposer(composer:IFlowComposer,composeToPosition:int,controllerStartIndex:int, controllerEndIndex:int):void |
| { |
| |
| // start composing from the first damaged position. Update internal composition state as if we'd composed to here already. |
| _startComposePosition = composer.damageAbsoluteStart; |
| if (controllerStartIndex == -1) |
| { |
| var controllerIndex:int = composer.findControllerIndexAtPosition(_startComposePosition); |
| if (controllerIndex == -1) |
| { |
| controllerIndex = composer.numControllers-1; |
| // if off the end in the overflow - find the last non-zero controller |
| while (controllerIndex != 0 && composer.getControllerAt(controllerIndex).textLength == 0) |
| controllerIndex--; |
| } |
| } |
| |
| // if damage is in overflow after last controller we could get smart about that |
| _startController = composer.getControllerAt(controllerIndex); |
| CONFIG::debug { assert(_startController != null,"Bad start start controller"); } |
| |
| // Disable partial container composition if we have to vertically align the lines. |
| if (_startController.computedFormat.verticalAlign != VerticalAlign.TOP) |
| _startComposePosition = _startController.absoluteStart; |
| |
| // Comment this line in to disable composing from the middle of a container. |
| // _startComposePosition = _startController.absoluteStart; |
| |
| super.initializeForComposer(composer,composeToPosition, controllerIndex, controllerEndIndex); |
| } |
| |
| /** @private */ |
| protected override function composeInternal(composeRoot:FlowGroupElement,absStart:int):void |
| { |
| super.composeInternal(composeRoot,absStart); |
| |
| // mark all overflow lines as not being in any container or column |
| if (_curElement) |
| { |
| var lineIndex:int = _curLineIndex; |
| CONFIG::debug { assert(_curLineIndex == _flowComposer.findLineIndexAtPosition(_curElementStart + _curElementOffset),"bad _curLineIndex"); } |
| while (lineIndex < _flowComposer.numLines) |
| _flowComposer.getLineAt(lineIndex++).setController(null,-1); |
| } |
| } |
| |
| override protected function doVerticalAlignment(canVerticalAlign:Boolean,nextParcel:Parcel):void |
| { |
| if (canVerticalAlign && _curParcel && vjBeginLineIndex != _curLineIndex && !vjDisableThisParcel) |
| { |
| var controller:ContainerController = _curParcel.controller; |
| var vjtype:String = controller.computedFormat.verticalAlign; |
| // Don't allow vertical justification if the line contains floats, since we don't support recomposing when lines are moved. |
| if (vjtype == VerticalAlign.JUSTIFY) |
| { |
| for (var i:int = controller.numFloats - 1; i >= 0 && canVerticalAlign; --i) |
| { |
| var floatInfo:FloatCompositionData = controller.getFloatAt(i); |
| if (floatInfo.floatType != Float.NONE) |
| canVerticalAlign = false; |
| |
| } |
| } |
| if (canVerticalAlign && vjtype != VerticalAlign.TOP) |
| { |
| // Exclude overset lines |
| var end:int = _flowComposer.findLineIndexAtPosition(_curElementStart + _curElementOffset) |
| |
| if (vjBeginLineIndex < end) |
| { |
| var beginFloatIndex:int = 0; |
| var endFloatIndex:int = 0; |
| var lines:Array = (_flowComposer as StandardFlowComposer).lines; |
| if (controller.numFloats > 0) |
| { |
| beginFloatIndex = controller.findFloatIndexAtOrAfter(lines[vjBeginLineIndex].absoluteStart); |
| endFloatIndex = controller.findFloatIndexAfter(_curElementStart + _curElementOffset); |
| } |
| applyVerticalAlignmentToColumn(controller,vjtype,lines,vjBeginLineIndex,end-vjBeginLineIndex, beginFloatIndex, endFloatIndex); |
| } |
| } |
| } |
| |
| // always reset these variables |
| vjDisableThisParcel = false; |
| vjBeginLineIndex = _curLineIndex; |
| } |
| |
| |
| /** apply vj and adjust the parcel bounds */ |
| override protected function applyVerticalAlignmentToColumn(controller:ContainerController,vjType:String,lines:Array,beginIndex:int,numLines:int,beginFloatIndex:int,endFloatIndex:int):void |
| { |
| super.applyVerticalAlignmentToColumn(controller, vjType, lines, beginIndex, numLines, beginFloatIndex, endFloatIndex); |
| |
| // Reposition lines in case they got aligned after being added to _linesInView |
| for each (var textLine:TextLine in controller.composedLines) |
| { |
| var line:TextFlowLine = textLine.userData as TextFlowLine; |
| CONFIG::debug |
| { |
| for (var i:int = 0; i < _textFlow.flowComposer.numLines; ++i) |
| { |
| var tfl:TextFlowLine = _textFlow.flowComposer.getLineAt(i); |
| if (tfl == line) |
| break; |
| } |
| assert(i < _textFlow.flowComposer.numLines, "Creating shape for unknown (old) line"); |
| } |
| line.createShape(_blockProgression, textLine); |
| } |
| } |
| |
| /** Final adjustment on the content bounds. */ |
| override protected function finalParcelAdjustment(controller:ContainerController):void |
| { |
| CONFIG::debug { assert(_flowComposer.getLineAt(_flowComposer.findLineIndexAtPosition(_curParcelStart)).absoluteStart == _curParcelStart,"finalParcelAdjustment: bad _curParcelStart"); } |
| |
| var minX:Number = TextLine.MAX_LINE_WIDTH; |
| var minY:Number = TextLine.MAX_LINE_WIDTH; |
| var maxX:Number = -TextLine.MAX_LINE_WIDTH; |
| |
| var verticalText:Boolean = _blockProgression == BlockProgression.RL; |
| |
| if (!isNaN(_parcelLogicalTop)) |
| { |
| if (verticalText) |
| maxX = _parcelLogicalTop; |
| else |
| minY = _parcelLogicalTop; |
| } |
| |
| if (!_measuring) |
| { |
| if (verticalText) |
| minY = _accumulatedMinimumStart; |
| else |
| minX = _accumulatedMinimumStart; |
| } |
| else |
| { |
| var edgeAdjust:Number; |
| var curPara:ParagraphElement; |
| var curParaFormat:ITextLayoutFormat; |
| var curParaDirection:String; |
| |
| for (var lineIndex:int = _flowComposer.findLineIndexAtPosition(_curParcelStart); lineIndex < _curLineIndex; lineIndex++) |
| { |
| var line:TextFlowLine = _flowComposer.getLineAt(lineIndex); |
| |
| // Now check the logical horizontal dimension |
| CONFIG::debug { assert(line != null && line.paragraph != null, "found it");} |
| |
| if (line.paragraph != curPara) |
| { |
| curPara = line.paragraph; |
| curParaFormat = curPara.computedFormat; |
| curParaDirection = curParaFormat.direction; |
| if (curParaDirection != Direction.LTR) |
| edgeAdjust = curParaFormat.paragraphEndIndent; |
| } |
| |
| if (curParaDirection == Direction.LTR) |
| edgeAdjust = Math.max(line.lineOffset, 0); |
| |
| edgeAdjust = verticalText ? line.y - edgeAdjust : line.x - edgeAdjust; |
| |
| var numberLine:TextLine = TextFlowLine.findNumberLine(line.getTextLine(true)); |
| |
| if (numberLine) |
| { |
| var numberLineStart:Number = verticalText ? numberLine.y+line.y : numberLine.x+line.x; |
| edgeAdjust = Math.min(edgeAdjust,numberLineStart); |
| } |
| |
| if (verticalText) |
| minY = Math.min(edgeAdjust, minY); |
| else |
| minX = Math.min(edgeAdjust, minX); |
| } |
| } |
| |
| // Don't make adjustments for tiny fractional values. |
| if (minX != TextLine.MAX_LINE_WIDTH && Math.abs(minX-_parcelLeft) >= 1) |
| _parcelLeft = minX; |
| if (maxX != -TextLine.MAX_LINE_WIDTH && Math.abs(maxX-_parcelRight) >= 1) |
| _parcelRight = maxX; |
| if (minY != TextLine.MAX_LINE_WIDTH && Math.abs(minY-_parcelTop) >= 1) |
| _parcelTop = minY; |
| } |
| |
| /** Called when we are finished composing a line. Handler for derived classes to override default behavior. */ |
| override protected function endLine(textLine:TextLine):void |
| { |
| super.endLine(textLine); |
| |
| if ( !_useExistingLine ) |
| (_flowComposer as StandardFlowComposer).addLine(_curLine,_curLineIndex); |
| |
| commitLastLineState (_curLine); |
| _curLineIndex++; |
| } |
| |
| protected override function composeParagraphElement(elem:ParagraphElement, absStart:int):Boolean |
| { |
| if (_curLineIndex < 0) |
| _curLineIndex = _flowComposer.findLineIndexAtPosition(_curElementStart + _curElementOffset); |
| |
| return super.composeParagraphElement(elem, absStart); |
| } |
| |
| /** @private */ |
| protected override function composeNextLine():TextLine |
| { |
| CONFIG::debug { assert(_curLineIndex == _flowComposer.findLineIndexAtPosition(_curElementStart + _curElementOffset),"bad _curLineIndex"); } |
| |
| // Find out how long a zero-logical height line can be in the parcel at the current position (depth). |
| // See if there is an existing line that is composed up-to-date matching the parcel width. If there is, |
| // we try to reuse it, otherwise we create a new line. Once we have placed a line, we will recheck the |
| // width in fitLineToParcel to make sure it fits at the (possibly changed) line height. |
| var startCompose:int = _curElementStart + _curElementOffset - _curParaStart; |
| var line:TextFlowLine = _curLineIndex < _flowComposer.numLines ? (_flowComposer as StandardFlowComposer).lines[_curLineIndex] : null; |
| |
| var useExistingLine:Boolean = line && (!line.isDamaged() || line.validity == FlowDamageType.GEOMETRY); |
| var numberLine:TextLine; |
| |
| // create numberLine if in a listElement |
| if (_listItemElement && _listItemElement.getAbsoluteStart() == _curElementStart+_curElementOffset) |
| { |
| var peekLine:TextLine; |
| if (useExistingLine && (peekLine = line.peekTextLine()) != null) |
| { |
| // may be null |
| numberLine = TextFlowLine.findNumberLine(peekLine); |
| } |
| else |
| { |
| var isRTL:Boolean = _curParaElement.computedFormat.direction == Direction.RTL; |
| numberLine = TextFlowLine.createNumberLine(_listItemElement, _curParaElement, _flowComposer.swfContext, isRTL ? _parcelList.rightMargin : _parcelList.leftMargin); |
| } |
| |
| pushInsideListItemMargins(numberLine); |
| } |
| |
| _parcelList.getLineSlug(_lineSlug, 0, 1, _textIndent, _curParaFormat.direction == Direction.LTR); |
| |
| if (useExistingLine && Twips.to(_lineSlug.width) != line.outerTargetWidthTW) |
| useExistingLine = false; |
| |
| _curLine = useExistingLine ? line : null; |
| var textLine:TextLine; |
| |
| for (;;) |
| { |
| while (!_curLine) |
| { |
| useExistingLine = false; |
| // generate new line |
| CONFIG::debug { assert(!_parcelList.atEnd(), "failing to stop"); } |
| CONFIG::debug { assert(_curElement is FlowLeafElement, "element must be leaf before calling composeLine"); } |
| CONFIG::debug { validateLineStart(_previousLine, startCompose, _curParaElement); } |
| |
| textLine = createTextLine(_lineSlug.width, !_lineSlug.wrapsKnockOut /* don't allow emergency breaks next to floats or padded elements */); |
| if (textLine) |
| break; |
| |
| // force advance within the parcel to the next wider slug, or (if there are no more) to the next parcel |
| var newDepth:Number = _curParcel.findNextTransition(_lineSlug.depth); |
| if (newDepth < Number.MAX_VALUE) |
| { |
| _parcelList.addTotalDepth(newDepth - _lineSlug.depth); |
| if (!_parcelList.getLineSlug(_lineSlug, 0, 1, _textIndent, _curParaFormat.direction == Direction.LTR)) |
| return null; |
| } |
| else |
| { |
| advanceToNextParcel(); |
| if (!_parcelList.atEnd()) |
| { |
| if (_parcelList.getLineSlug(_lineSlug, 0, 1, _textIndent, _curParaFormat.direction == Direction.LTR)) |
| continue; |
| } |
| popInsideListItemMargins(numberLine); |
| return null; |
| } |
| } |
| |
| // updates _lineSlug |
| CONFIG::debug { assert(textLine || useExistingLine, "expected non-null textLine because we had to regenerate the line"); } |
| if (!textLine) |
| textLine = _curLine.getTextLine(true); |
| if (fitLineToParcel(textLine, !useExistingLine, numberLine)) |
| break; // we have a good line |
| _curLine = null; // keep looking |
| if (_parcelList.atEnd()) |
| { |
| popInsideListItemMargins(numberLine); |
| return null; |
| } |
| } |
| |
| // Clear up user_invalid |
| if (_curLine.validity == FlowDamageType.GEOMETRY) |
| _curLine.clearDamage(); |
| |
| _useExistingLine = useExistingLine; |
| |
| popInsideListItemMargins(numberLine); |
| |
| CONFIG::debug { assert(textLine != null, "textLine != null"); } |
| return textLine; |
| } |
| |
| /** @private */ |
| override protected function createTextLine( |
| targetWidth:Number, // target width we're composing into |
| allowEmergencyBreaks:Boolean // true to allow words to break in the middle of narrow columns, false to force overset |
| ):TextLine |
| { |
| _curLine = new TextFlowLine(null, null); // it will be initialized in BaseCompose.createTextLine |
| var textLine:TextLine = super.createTextLine(targetWidth, allowEmergencyBreaks); |
| |
| if (textLine) |
| textLine.doubleClickEnabled = true; // allow line to be the target oif a double click event |
| else |
| _curLine = null; |
| |
| return textLine; |
| } |
| |
| /** @private */ |
| CONFIG::debug private static function validateLineStart(previousLine:TextLine, lineStart:int, paraNode:ParagraphElement):void |
| { |
| // If the lines have been released, don't validate |
| if (lineStart != 0 && paraNode.getTextBlock().firstLine == null) |
| return; |
| |
| var testStart:int = 0; |
| var testLine:TextLine = previousLine; |
| while (testLine) |
| { |
| CONFIG::debug { assert(testLine.validity == TextLineValidity.VALID, "previous line in paragraph is not valid!"); } |
| testStart += testLine.rawTextLength; |
| testLine = testLine.previousLine; |
| } |
| assert(testStart == lineStart, "Bad lines"); |
| |
| assert(paraNode is ParagraphElement,"composeLine: paraNode must be a para"); |
| var prevLine:TextFlowLine = previousLine ? previousLine.userData as TextFlowLine : null; |
| assert(!previousLine || prevLine != null, "prevLine null"); |
| assert(!previousLine || !(prevLine.location & TextFlowLineLocation.LAST),"prevLine may not be from a different para"); |
| } |
| } |
| } |