| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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.events.Event; |
| import flash.geom.Rectangle; |
| import flash.text.engine.TextBlock; |
| import flash.text.engine.TextLine; |
| 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.OverflowPolicy; |
| import flashx.textLayout.elements.ParagraphElement; |
| import flashx.textLayout.elements.SubParagraphGroupElement; |
| import flashx.textLayout.elements.TextFlow; |
| import flashx.textLayout.formats.BaselineOffset; |
| import flashx.textLayout.formats.BlockProgression; |
| import flashx.textLayout.formats.Direction; |
| import flashx.textLayout.formats.ITextLayoutFormat; |
| import flashx.textLayout.formats.TextAlign; |
| import flashx.textLayout.formats.VerticalAlign; |
| import flashx.textLayout.tlf_internal; |
| |
| 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; |
| |
| // 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 ? _sharedComposeState : new ComposeState(); |
| _sharedComposeState = null; |
| return rslt; |
| } |
| |
| /** @private */ |
| static tlf_internal function releaseComposeState(state:ComposeState):void |
| { |
| if (_sharedComposeState == null) |
| { |
| _sharedComposeState = state; |
| if (_sharedComposeState) |
| _sharedComposeState.releaseAnyReferences(); |
| } |
| } |
| |
| /** Constructor. */ |
| public function ComposeState() |
| { |
| super(); |
| } |
| |
| /** @private */ |
| protected override function createParcelList():IParcelList |
| { |
| return ParcelList.getParcelList(); |
| } |
| /** @private */ |
| protected override function releaseParcelList(list:IParcelList):void |
| { |
| ParcelList.releaseParcelList(list); |
| } |
| |
| /** @private */ |
| public override function composeTextFlow(textFlow:TextFlow, composeToPosition:int, controllerEndIndex:int):int |
| { |
| _curLineIndex = 0; |
| |
| vjBeginLineIndex = 0; |
| vjDisableThisParcel = false; |
| |
| return super.composeTextFlow(textFlow, composeToPosition, controllerEndIndex); |
| } |
| |
| protected override function initializeForComposer(composer:IFlowComposer,composeToPosition:int,controllerEndIndex:int):void |
| { |
| super.initializeForComposer(composer,composeToPosition,controllerEndIndex); |
| |
| // start composing from the first damaged position. Update internal composition state as if we'd composed to here already. |
| _startComposePosition = composer.damageAbsoluteStart; // _startController.absoluteStart; |
| 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; |
| } |
| |
| /** @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) |
| { |
| CONFIG::debug { assert(_curLineIndex == _flowComposer.findLineIndexAtPosition(_curElementStart + _curElementOffset),"bad _curLineIndex"); } |
| while (_curLineIndex < _flowComposer.numLines) |
| _flowComposer.getLineAt(_curLineIndex++).setController(null,-1); |
| } |
| } |
| |
| override protected function doVerticalAlignment(canVerticalAlign:Boolean,nextParcel:Parcel):Boolean |
| { |
| var result:Boolean = false; |
| if (canVerticalAlign && _curParcel && vjBeginLineIndex != _curLineIndex && !vjDisableThisParcel && _curParcel.columnCoverage == Parcel.FULL_COLUMN) |
| { |
| var controller:ContainerController = _curParcel.controller; |
| var vjtype:String = controller.computedFormat.verticalAlign; |
| if (vjtype != VerticalAlign.TOP) |
| { |
| // Exclude overset lines |
| var end:int = _flowComposer.findLineIndexAtPosition(_curElementStart + _curElementOffset) |
| |
| if (vjBeginLineIndex < end) |
| { |
| applyVerticalAlignmentToColumn(controller,vjtype,_flowComposer.lines,vjBeginLineIndex,end-vjBeginLineIndex); |
| result = true; // lines were moved |
| } |
| } |
| } |
| |
| // always reset these variables |
| vjDisableThisParcel = false; |
| vjBeginLineIndex = _curLineIndex; |
| |
| return result; |
| } |
| |
| /** Final adjustment on the content bounds. */ |
| override protected function finalParcelAdjustment(controller:ContainerController):void |
| { |
| var minX:Number = TextLine.MAX_LINE_WIDTH; |
| var minY:Number = TextLine.MAX_LINE_WIDTH; |
| var maxX:Number = -TextLine.MAX_LINE_WIDTH; |
| var maxY:Number = -TextLine.MAX_LINE_WIDTH; |
| |
| var verticalText:Boolean = _blockProgression == BlockProgression.RL; |
| |
| var lineIndex:int = _flowComposer.findLineIndexAtPosition(controller.absoluteStart); |
| while (lineIndex < _curLineIndex) |
| { |
| var line:TextFlowLine = _flowComposer.getLineAt(lineIndex); |
| |
| // Check the logical vertical dimension first |
| // If the lines have children, they may be inlines. The origin of the TextFlowLine is the baseline - ascent, |
| // which does not include the ascent of the inlines. So we have to factor that in. |
| if (verticalText) |
| { |
| maxX = Math.max(line.x + line.ascent, maxX); |
| minX = Math.min(line.x, minX); |
| } |
| else |
| minY = Math.min(line.y + line.ascent - line.height, minY); |
| |
| // CONFIG::debug { assert(line.hasGraphicElement == line.getTextLine().hasGraphicElement,"Bad hasGraphicElement"); } |
| // this is a test for an inline graphic |
| if (line.hasGraphicElement) |
| { |
| var leafElement:FlowLeafElement = _textFlow.findLeaf(line.absoluteStart); |
| var adjustedAscent:Number = line.getLineTypographicAscent(leafElement, leafElement.getAbsoluteStart()); |
| if (!verticalText) |
| minY = Math.min(line.y + line.ascent - adjustedAscent, minY); |
| else |
| maxX = Math.max(line.x + adjustedAscent, maxX); |
| } |
| |
| |
| // Now check the logical horizontal dimension |
| var edgeAdjust:Number; |
| var curParaFormat:ITextLayoutFormat = line.paragraph.computedFormat; |
| if (curParaFormat.direction == Direction.LTR) |
| edgeAdjust = Math.max(line.lineOffset, 0); |
| else |
| edgeAdjust = curParaFormat.paragraphEndIndent; |
| if (verticalText) |
| minY = Math.min(line.y - edgeAdjust, minY); |
| else |
| minX = Math.min(line.x - edgeAdjust, minX); |
| ++lineIndex; |
| } |
| |
| if (_blockProgression == BlockProgression.RL) |
| minX -= _lastLineDescent; |
| |
| // 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; |
| if (maxY != -TextLine.MAX_LINE_WIDTH && Math.abs(maxY-_parcelBottom) >= 1) |
| _parcelBottom = maxY; |
| } |
| |
| private function finalizeLine(useExistingLine:Boolean,curLine:TextFlowLine):void |
| { |
| if ( !useExistingLine ) |
| { |
| _flowComposer.addLine(curLine,_curLineIndex); |
| } |
| _curLineIndex++; |
| |
| commitLastLineState (curLine); |
| } |
| |
| protected override function composeParagraphElement(elem:ParagraphElement, absStart:int):Boolean |
| { |
| _curParaElement = elem; |
| _curParaStart = absStart; |
| _curParaFormat = elem.computedFormat; |
| CONFIG::debug { assert(_curParaStart == elem.getAbsoluteStart(),"composeParagraphElement: bad start"); } |
| if (_startComposePosition == 0) |
| { |
| _curElement = elem.getFirstLeaf(); |
| _curElementStart = _curParaStart; |
| } |
| else |
| { |
| CONFIG::debug { assert(absStart <= _startComposePosition && absStart+elem.textLength > _startComposePosition,"bad call to composeParagraphElement"); } |
| _curElement = elem.findLeaf(_startComposePosition-absStart); |
| _curElementStart = _curElement.getAbsoluteStart(); |
| _curElementOffset = _startComposePosition-_curElementStart; |
| _curLineIndex = _flowComposer.findLineIndexAtPosition(_curElementStart + _curElementOffset); |
| // next time we are all postioned |
| _startComposePosition = 0; |
| } |
| |
| return composeParagraphElementIntoLines(); |
| } |
| |
| /** @private */ |
| protected override function composeNextLine():TextFlowLine |
| { |
| CONFIG::debug { assert(_curLineIndex == _flowComposer.findLineIndexAtPosition(_curElementStart + _curElementOffset),"bad _curLineIndex"); } |
| |
| // Check to see if there's an existing line that is composed up-to-date |
| var line:TextFlowLine = _curLineIndex < _flowComposer.numLines ? _flowComposer.lines[_curLineIndex] : null; |
| var useExistingLine:Boolean = line && (!line.isDamaged() || line.validity == FlowDamageType.GEOMETRY); |
| var curLine:TextFlowLine = useExistingLine ? line : null; |
| var startCompose:int = _curElementStart + _curElementOffset - _curParaStart; |
| var prevLine:TextFlowLine; |
| if (startCompose != 0) |
| { |
| prevLine = _flowComposer.lines[_curLineIndex - 1]; |
| if (prevLine.absoluteStart < _curParaStart) // is the previous line in the previous paragraph? |
| prevLine = null; |
| } |
| var finishLineSlug:Rectangle = _parcelList.currentParcel; |
| |
| 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"); } |
| |
| curLine = createTextLine(prevLine, startCompose, _parcelList.getComposeXCoord(finishLineSlug), _parcelList.getComposeYCoord(finishLineSlug), _parcelList.getComposeWidth(finishLineSlug)); |
| if (curLine != null) |
| break; |
| // force advance to the next parcel |
| if (!_parcelList.next()) |
| return null; |
| } |
| |
| // updates _lineSlug |
| curLine = fitLineToParcel(curLine, !useExistingLine); |
| if (curLine) |
| break; |
| if (_parcelList.atEnd()) |
| return null; |
| finishLineSlug = _lineSlug; |
| } |
| |
| // Clear up user_invalid |
| if (curLine.validity == FlowDamageType.GEOMETRY) |
| curLine.clearDamage(); |
| |
| finalizeLine(useExistingLine,curLine); |
| |
| CONFIG::debug { assert(curLine != null, "curLine != null"); } |
| return curLine; |
| } |
| |
| /** @private */ |
| protected function createTextLine(prevLine:TextFlowLine, // previous line |
| lineStart:int, // text index of position to start from, relative to start of paragraph |
| x:Number, // left edge of the line |
| y:Number, // top of the line |
| targetWidth:Number // target width we're composing into |
| ):TextFlowLine |
| { |
| // trace("lineBreak: start", prevLine ? (prevLine.start+prevLine.textLength) : 0, "paraEnd", paraEnd, "(", x, y, ")", "targetWidth", targetWidth); |
| |
| CONFIG::debug { validateLineStart(prevLine, lineStart, _curParaElement); } |
| |
| var lineOffset:Number = Number(_curParaFormat.paragraphStartIndent); // indent to "beginning" of the line. Direction dependent (as is paragraphStartIndent) |
| if (prevLine == null) // first line indent |
| lineOffset += Number(_curParaFormat.textIndent); |
| |
| var outerTargetWidth:Number = targetWidth; |
| targetWidth -= (Number(_curParaFormat.paragraphEndIndent) + lineOffset); // make room for offset and end indent |
| |
| // TargetWidth must be between 0 and TextLine.MAX_LINE_WIDTH |
| if (targetWidth < 0) |
| targetWidth = 0; |
| else if (targetWidth > TextLine.MAX_LINE_WIDTH) |
| targetWidth = TextLine.MAX_LINE_WIDTH; |
| |
| var textLine:TextLine = TextLineRecycler.getLineForReuse(); |
| var textBlock:TextBlock = _curParaElement.getTextBlock(); |
| if (textLine) |
| { |
| CONFIG::debug { assert(_textFlow.backgroundManager == null || _textFlow.backgroundManager.lineDict[textLine] === undefined,"Bad TextLine in recycler cache"); } |
| textLine = swfContext.callInContext(textBlock["recreateTextLine"],textBlock,[textLine, prevLine?prevLine.getTextLine(true):null, targetWidth, lineOffset, true]); |
| } |
| else |
| { |
| textLine = swfContext.callInContext(textBlock.createTextLine,textBlock,[prevLine?prevLine.getTextLine(true):null, targetWidth, lineOffset, true]); |
| } |
| // Unable to fit a new line |
| if (textLine == null) |
| return null; |
| //trace("LineBreak prevLineLength:",prevLine?prevLine.textLine.rawTextLength:0,"nextLineLength:",line?line.textLine.rawTextLength:0); |
| // outerTargetWidth, targetWidth, textIndent, start, textLength, textLine |
| CONFIG::debug { assert(_curParaStart == _curParaElement.getAbsoluteStart(),"bad _curParaStart"); } |
| var line:TextFlowLine = new TextFlowLine(textLine, _curParaElement, outerTargetWidth, lineOffset, lineStart + _curParaStart, textLine.rawTextLength); |
| CONFIG::debug { assert(line.targetWidth == targetWidth,"Bad targetWidth"); } |
| textLine.doubleClickEnabled = true; // allow line to be the target oif a double click event |
| |
| // update spaceBefore & spaceAfter |
| CONFIG::debug |
| { |
| var linePos:uint = line.location; |
| if (linePos & TextFlowLineLocation.FIRST) |
| line.setSpaceBefore(Number(_curParaFormat.paragraphSpaceBefore)); |
| if (linePos & TextFlowLineLocation.LAST) |
| line.setSpaceAfter(Number(_curParaFormat.paragraphSpaceAfter)); |
| } |
| |
| return line; |
| } |
| |
| /** @private */ |
| CONFIG::debug private static function validateLineStart(prevLine:TextFlowLine, 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 = prevLine ? prevLine.getTextLine(true) : null; |
| while (testLine) |
| { |
| testStart += testLine.rawTextLength; |
| testLine = testLine.previousLine; |
| } |
| assert(testStart == lineStart, "Bad lines"); |
| |
| assert(paraNode is ParagraphElement,"composeLine: paraNode must be a para"); |
| assert(!prevLine || !(prevLine.location & TextFlowLineLocation.LAST),"prevLine may not be from a different para"); |
| } |
| } |
| } |