| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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.compose.TextFlowLine; |
| import flashx.textLayout.compose.TextFlowLineLocation; |
| 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 SimpleCompose extends BaseCompose |
| { |
| // reusable scratch TextFlowLine |
| protected var workingLine:TextFlowLine = new TextFlowLine(null, null); |
| |
| // resulting TextLines |
| public var _lines:Array; |
| |
| // scratch aligns for VJ |
| private var _vjLines:Array; |
| |
| // for figuring out when to do VJ |
| private var vjBeginLineIndex:int = 0; |
| private var vjDisableThisParcel:Boolean = false; |
| private var vjParcel:Parcel; |
| private var vjType:String; |
| |
| // accumulator for absolute start computation to support truncation |
| private var _totalLength:Number; |
| |
| /** Constructor. */ |
| public function SimpleCompose() |
| { |
| super(); |
| _lines = new Array(); |
| _vjLines = new Array(); |
| } |
| |
| /** @private */ |
| protected override function createParcelList():IParcelList |
| { |
| return ParcelList.getParcelList(); |
| } |
| /** @private */ |
| protected override function releaseParcelList(list:IParcelList):void |
| { |
| ParcelList.releaseParcelList(list); |
| } |
| |
| protected override function initializeForComposer(composer:IFlowComposer, composeToPosition:int, controllerEndIndex:int):void |
| { |
| super.initializeForComposer(composer, composeToPosition, controllerEndIndex); |
| |
| // vj support |
| _vjLines.splice(0); |
| vjBeginLineIndex = 0; |
| vjParcel = parcelList.currentParcel; |
| vjDisableThisParcel = false; |
| vjType = vjParcel ? vjParcel.controller.computedFormat.verticalAlign : VerticalAlign.TOP; |
| |
| _startController = composer.getControllerAt(0); |
| _startComposePosition = 0; |
| } |
| |
| /** @private */ |
| public override function composeTextFlow(textFlow:TextFlow, composeToPosition:int, controllerEndIndex:int):int |
| { |
| _flowComposer = textFlow.flowComposer as StandardFlowComposer; |
| |
| // empty out lines array |
| _lines.splice(0); |
| |
| // accumulator initialization |
| _totalLength = 0; |
| |
| return super.composeTextFlow(textFlow, composeToPosition, controllerEndIndex); |
| } |
| |
| override protected function doVerticalAlignment(canVerticalAlign:Boolean,nextParcel:Parcel):Boolean |
| { |
| var result:Boolean = false; |
| |
| if (canVerticalAlign && vjType != VerticalAlign.TOP && vjBeginLineIndex != _lines.length && !vjDisableThisParcel && vjParcel.columnCoverage == Parcel.FULL_COLUMN) |
| { |
| applyVerticalAlignmentToColumn(vjParcel.controller,vjType,_vjLines,0,_vjLines.length); |
| result = true; // lines were moved |
| } |
| |
| _vjLines.splice(0); |
| vjBeginLineIndex = _lines.length; |
| vjParcel = nextParcel; // next parcel |
| vjDisableThisParcel = false; |
| if (nextParcel) |
| vjType = vjParcel.controller.computedFormat.verticalAlign; |
| return result; |
| } |
| |
| private function finalizeLine(curLine:TextFlowLine):void |
| { |
| var line:TextLine = curLine.createShape(_blockProgression); |
| |
| if (textFlow.backgroundManager) |
| textFlow.backgroundManager.finalizeLine(curLine); |
| |
| line.userData = _totalLength; // store absolute start position in the userData field |
| _totalLength += line.rawTextLength; // update length accumulator |
| _lines.push(line); |
| if (vjType != VerticalAlign.TOP) |
| _vjLines.push(new VJHelper(line,curLine.height)); |
| |
| commitLastLineState (curLine); |
| } |
| |
| public function get textFlow():TextFlow |
| { |
| return _textFlow; |
| } |
| |
| /** @private */ |
| 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"); } |
| _curElement = elem.getFirstLeaf(); |
| _curElementStart = _curParaStart; |
| return composeParagraphElementIntoLines(); |
| } |
| /** @private */ |
| protected override function composeNextLine():TextFlowLine |
| { |
| // Check to see if there's an existing line that is composed up-to-date |
| |
| var startCompose:int = _curElementStart + _curElementOffset - _curParaStart; |
| var prevLine:TextLine = startCompose != 0 ? workingLine.getTextLine() : null; |
| CONFIG::debug { assert(!prevLine || prevLine.validity == "valid","Bad prevline: "+Debugging.getIdentity(prevLine)); } |
| var finishLineSlug:Rectangle = _parcelList.currentParcel; |
| var curLine:TextFlowLine; |
| |
| for (;;) |
| { |
| for (;;) |
| { |
| // 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, true); |
| if (curLine) |
| break; |
| if (_parcelList.atEnd()) |
| return null; |
| finishLineSlug = _lineSlug; |
| } |
| |
| finalizeLine(curLine); |
| |
| CONFIG::debug { assert(curLine != null, "curLine != null"); } |
| return curLine; |
| } |
| |
| /** @private */ |
| protected function createTextLine(prevLine:TextLine, // 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 |
| { |
| // adjust target width for text indent, start and end indent |
| 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 = (targetWidth < 0) ? 0 : targetWidth; // no negative targetwidth allowed |
| if (targetWidth > TextLine.MAX_LINE_WIDTH) |
| targetWidth = TextLine.MAX_LINE_WIDTH; |
| |
| //var textLine:TextLine = _flowComposer.textLineCreator.createTextLine(_curParaElement.getTextBlock(), prevLine, targetWidth, lineOffset, true); |
| 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, targetWidth, lineOffset, true ]); |
| } |
| else |
| { |
| textLine = swfContext.callInContext(textBlock.createTextLine, textBlock, [prevLine, targetWidth, lineOffset, true ]); |
| } |
| // Unable to fit a new line |
| if (textLine == null) |
| return null; |
| |
| CONFIG::debug { assert(_curParaStart == _curParaElement.getAbsoluteStart(),"bad _curParaStart"); } |
| workingLine.initialize(_curParaElement, outerTargetWidth, lineOffset, lineStart + _curParaStart, textLine.rawTextLength, textLine); |
| CONFIG::debug { assert(workingLine.targetWidth == targetWidth,"Bad targetWidth"); } |
| |
| // update spaceBefore & spaceAfter |
| CONFIG::debug |
| { |
| var linePos:uint = workingLine.location; |
| workingLine.setSpaceBefore((linePos & TextFlowLineLocation.FIRST) ? Number(_curParaFormat.paragraphSpaceBefore) : 0); |
| workingLine.setSpaceAfter((linePos & TextFlowLineLocation.LAST) ? Number(_curParaFormat.paragraphSpaceAfter) : 0); |
| } |
| |
| return workingLine; |
| } |
| |
| /** @private */ |
| tlf_internal function swapLines(lines:Array):Array |
| { |
| var current:Array = _lines; |
| _lines = lines; |
| return current; |
| } |
| |
| /** 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 textLine:TextLine; |
| var verticalText:Boolean = _blockProgression == BlockProgression.RL; |
| var startPos:int = controller.absoluteStart; |
| |
| for each (textLine in _lines) |
| { |
| var leaf:FlowLeafElement = controller.textFlow.findLeaf(startPos); |
| var para:ParagraphElement = leaf.getParagraph(); |
| |
| // Check the logical vertical dimension first |
| // If the lines have children, they may be inlines. The origin of the TextLine is the baseline, |
| // which does not include the ascent of the inlines or the text. So we have to factor that in. |
| // var verticalAdjust:Number = verticalText ? textLine.descent : textLine.ascent; |
| var inlineAscent:Number = 0; |
| if (textLine.numChildren > 0) // adjustjust logical vertical coord to take into account inlines |
| { |
| var leafStart:int = leaf.getAbsoluteStart(); |
| inlineAscent = TextFlowLine.getTextLineTypographicAscent(textLine, leaf, leafStart, startPos + textLine.rawTextLength, para); |
| } |
| |
| // Figure out the logical horizontal adjustment |
| var edgeAdjust:Number = 0; |
| var curParaFormat:ITextLayoutFormat = para.computedFormat; |
| if (curParaFormat.direction == Direction.LTR) |
| edgeAdjust = curParaFormat.paragraphStartIndent + Math.max(curParaFormat.textIndent, 0); |
| else |
| edgeAdjust = curParaFormat.paragraphEndIndent; |
| |
| if (verticalText) |
| { |
| minX = Math.min(textLine.x - textLine.descent, minX); |
| maxX = Math.max(textLine.x + Math.max(inlineAscent,textLine.ascent), maxX); |
| minY = Math.min(textLine.y - edgeAdjust, minY); |
| } |
| else |
| { |
| if (inlineAscent < textLine.ascent) |
| inlineAscent = textLine.ascent; |
| minX = Math.min(textLine.x - edgeAdjust, minX); |
| minY = Math.min(textLine.y - inlineAscent, minY); |
| } |
| startPos += textLine.rawTextLength; |
| } |
| // 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; |
| } |
| |
| tlf_internal override function releaseAnyReferences():void |
| { |
| super.releaseAnyReferences(); |
| workingLine.initialize(null,0,0,0,0,null); |
| // parcelList.releaseAnyReferences(); |
| } |
| } |
| } |
| import flash.text.engine.TextLine; |
| import flashx.textLayout.compose.IVerticalJustificationLine; |
| import flash.text.engine.TextLineCreationResult; |
| |
| class VJHelper implements IVerticalJustificationLine |
| { |
| private var _line:TextLine; |
| private var _height:Number; |
| |
| public function VJHelper(line:TextLine,h:Number) |
| { |
| _line = line; |
| _height = h; |
| } |
| public function get x():Number |
| { return _line.x; } |
| public function set x(val:Number):void |
| { _line.x = val; } |
| |
| public function get y():Number |
| { return _line.y; } |
| public function set y(val:Number):void |
| { _line.y = val; } |
| |
| public function get ascent():Number |
| { return _line.ascent; } |
| public function get descent():Number |
| { return _line.descent; } |
| public function get height():Number |
| { return _height; } |
| } |