| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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.geom.Rectangle; |
| import flash.text.engine.TextLine; |
| |
| import flashx.textLayout.container.ColumnState; |
| import flashx.textLayout.container.ContainerController; |
| import flashx.textLayout.container.ScrollPolicy; |
| import flashx.textLayout.debug.assert; |
| import flashx.textLayout.elements.TableCellElement; |
| import flashx.textLayout.formats.BlockProgression; |
| import flashx.textLayout.formats.ITextLayoutFormat; |
| import flashx.textLayout.formats.LineBreak; |
| import flashx.textLayout.formats.VerticalAlign; |
| import flashx.textLayout.tlf_internal; |
| import flashx.textLayout.utils.Twips; |
| |
| use namespace tlf_internal; |
| |
| /** @private |
| * Used for composing text containers, keeps track of the areas that text in the |
| * flow is composed into. |
| * |
| * ParcelList will always have one parcel, which corresponds to the container's |
| * bounding box. |
| */ |
| public class ParcelList |
| { |
| protected var _flowComposer:IFlowComposer; |
| |
| /** Current vertical position in the parcel. */ |
| protected var _totalDepth:Number; |
| |
| /** whether the current parcel has any content */ |
| protected var _hasContent:Boolean; |
| |
| /** The list of parcels that are available for text layout. |
| They are appear in the array in reading order: the first text goes in the |
| first parcel, when it gets filled later text is flowed into the second |
| parcel, and so on. */ |
| protected var _parcelArray:Array; /* of Parcel */ |
| protected var _numParcels:int; |
| protected var _singleParcel:Parcel; |
| |
| /** Index of the "current" parcel. These next two variables must be kept in sync. */ |
| protected var _currentParcelIndex:int; |
| protected var _currentParcel:Parcel; |
| |
| protected var _insideListItemMargin:Number; |
| |
| protected var _leftMargin:Number; |
| protected var _rightMargin:Number; |
| |
| protected var _explicitLineBreaks:Boolean; |
| |
| /** True if text is vertical (as for some Japanese & Chinese, false otherwise */ |
| protected var _verticalText:Boolean; |
| |
| private static const MAX_HEIGHT:Number = 900000000; // vertical scroll max - capped to prevent loss of precision - what should it be? |
| private static const MAX_WIDTH:Number = 900000000; // horizontal scroll max - capped to prevent loss of precision - what should it be? |
| |
| // a single parcellist that is checked out and checked in |
| static private var _sharedParcelList:ParcelList; |
| |
| /** @private */ |
| static tlf_internal function getParcelList():ParcelList |
| { |
| var rslt:ParcelList = _sharedParcelList ? _sharedParcelList : new ParcelList(); |
| _sharedParcelList = null; |
| return rslt; |
| } |
| |
| /** @private */ |
| static tlf_internal function releaseParcelList(list:ParcelList):void |
| { |
| if (_sharedParcelList == null) |
| { |
| _sharedParcelList = list as ParcelList; |
| if (_sharedParcelList) |
| _sharedParcelList.releaseAnyReferences(); |
| } |
| } |
| |
| /** Constructor. */ |
| public function ParcelList() |
| { _numParcels = 0; } |
| |
| /** prevent any leaks. @private */ |
| tlf_internal function releaseAnyReferences():void |
| { |
| this._flowComposer = null; |
| |
| _numParcels = 0; |
| _parcelArray = null; |
| |
| if (_singleParcel) |
| _singleParcel.releaseAnyReferences(); |
| } |
| |
| CONFIG::debug public function getBounds():Array |
| { |
| var boundsArray:Array = []; |
| for (var i:int = 0; i < _numParcels; ++i) |
| boundsArray.push(getParcelAt(i)); |
| return boundsArray; |
| } |
| |
| public function getParcelAt(idx:int):Parcel |
| { return _numParcels <= 1 ? _singleParcel : _parcelArray[idx]; } |
| |
| public function get currentParcelIndex():int |
| { return _currentParcelIndex; } |
| |
| public function get explicitLineBreaks():Boolean |
| { |
| return _explicitLineBreaks; |
| } |
| |
| private function get measureLogicalWidth():Boolean |
| { |
| if (_explicitLineBreaks) |
| return true; |
| if (!_currentParcel) |
| return false; |
| var controller:ContainerController = _currentParcel.controller; |
| return _verticalText ? controller.measureHeight : controller.measureWidth; |
| } |
| |
| private function get measureLogicalHeight():Boolean |
| { |
| if (!_currentParcel) |
| return false; |
| var controller:ContainerController = _currentParcel.controller; |
| return _verticalText ? controller.measureWidth : controller.measureHeight; |
| } |
| |
| public function get totalDepth():Number |
| { |
| return _totalDepth; |
| } |
| |
| public function addTotalDepth(value:Number):Number |
| { |
| _totalDepth += value; |
| // trace("addTotalDepth", value, "newDepth", totalDepth); |
| return _totalDepth; |
| } |
| |
| protected function reset():void |
| { |
| // Composition starts with an initial invalid parcel. It will start by calling next(), which will |
| // advance to the first parcel. |
| _totalDepth = 0; |
| _hasContent = false; |
| |
| _currentParcelIndex = -1; |
| _currentParcel = null; |
| |
| _leftMargin = 0; |
| _rightMargin = 0; |
| _insideListItemMargin = 0; |
| } |
| |
| public function addParcel(column:Rectangle, controller:ContainerController, columnIndex:int):Parcel |
| { |
| var newParcel:Parcel = _numParcels == 0 && _singleParcel |
| ? _singleParcel.initialize(_verticalText, column.x,column.y,column.width,column.height,controller,columnIndex) |
| : new Parcel(_verticalText, column.x, column.y, column.width, column.height, controller, columnIndex) |
| if (_numParcels == 0) |
| _singleParcel = newParcel; |
| else if (_numParcels == 1) |
| _parcelArray = [ _singleParcel, newParcel ]; |
| else |
| _parcelArray.push(newParcel); |
| _numParcels++; |
| return newParcel; |
| } |
| |
| // Return numbers of parcels in this parcel list |
| public function numParcels():int |
| { |
| return this._numParcels; |
| } |
| |
| // Pop up top most parcel |
| public function popParcel():Parcel |
| { |
| _numParcels -- ; |
| return _parcelArray.pop(); |
| } |
| |
| public function addTableCell2ColumnState(controller:ContainerController, cell:TableCellElement):void |
| { |
| var columnState:ColumnState = controller.columnState; |
| if (columnState) |
| columnState.pushTableCell(cell); |
| } |
| protected function addOneControllerToParcelList(controllerToInitialize:ContainerController):void |
| { |
| // Initialize new parcels for columns |
| var columnState:ColumnState = controllerToInitialize.columnState; |
| columnState.clearCellList(); |
| for (var columnIndex:int = 0; columnIndex < columnState.columnCount; columnIndex++) |
| { |
| var column:Rectangle = columnState.getColumnAt(columnIndex); |
| if (!column.isEmpty()) |
| addParcel(column, controllerToInitialize, columnIndex); |
| } |
| } |
| |
| public function beginCompose(composer:IFlowComposer, controllerStartIndex:int, controllerEndIndex:int, composeToPosition:Boolean):void |
| { |
| _flowComposer = composer; |
| |
| var rootFormat:ITextLayoutFormat = composer.rootElement.computedFormat; |
| _explicitLineBreaks = rootFormat.lineBreak == LineBreak.EXPLICIT; |
| _verticalText = (rootFormat.blockProgression == BlockProgression.RL); |
| |
| if (composer.numControllers != 0) |
| { |
| // if controllerEndIndex is not specified then assume we are composing to position and add all controllers |
| if (controllerEndIndex < 0) |
| controllerEndIndex = composer.numControllers-1; |
| else |
| controllerEndIndex = Math.min(controllerEndIndex,composer.numControllers-1); |
| var idx:int = controllerStartIndex; |
| do |
| { |
| addOneControllerToParcelList(ContainerController(composer.getControllerAt(idx))); |
| } while (idx++ != controllerEndIndex) |
| // adjust the last container for scrolling |
| if (controllerEndIndex == composer.numControllers-1) |
| adjustForScroll(composer.getControllerAt(composer.numControllers-1), composeToPosition); |
| } |
| reset(); |
| } |
| |
| /** Adjust the size of the parcel corresponding to the last column of the containter, in |
| * order to account for scrolling. |
| */ |
| private function adjustForScroll(containerToInitialize:ContainerController, composeToPosition:Boolean):void |
| { |
| // Expand the last parcel if scrolling could be enabled. Expand to twice what would fit in available space. |
| // We will start composing from the top, so if we've scrolled down there will be more to compose. |
| // We turn on fitAny, so that lines will be included in the container even if only a tiny portion of the line |
| // fits. This makes lines that are only partially scrolling in appear. We turn on composeToPosition if we're |
| // forcing composition to go through a given position -- this will make all lines fit, and composition will |
| // continue until it is past the supplied position. |
| if (_verticalText) |
| { |
| if (containerToInitialize.horizontalScrollPolicy != ScrollPolicy.OFF) |
| { |
| p = getParcelAt(_numParcels-1); |
| if (p) |
| { |
| var horizontalPaddingAmount:Number = containerToInitialize.getTotalPaddingRight() + containerToInitialize.getTotalPaddingLeft(); |
| var right:Number = p.right; |
| p.x = containerToInitialize.horizontalScrollPosition - p.width - horizontalPaddingAmount; |
| p.width = right - p.x; |
| p.fitAny = true; |
| p.composeToPosition = composeToPosition; |
| } |
| } |
| } |
| else |
| { |
| if (containerToInitialize.verticalScrollPolicy != ScrollPolicy.OFF) |
| { |
| var p:Parcel = getParcelAt(_numParcels-1); |
| if (p) |
| { |
| var verticalPaddingAmount:Number = containerToInitialize.getTotalPaddingBottom() + containerToInitialize.getTotalPaddingTop(); |
| p.height = (containerToInitialize.verticalScrollPosition + p.height + verticalPaddingAmount) - p.y; |
| p.fitAny = true; |
| p.composeToPosition = composeToPosition; |
| } |
| } |
| } |
| } |
| |
| public function get leftMargin():Number |
| { |
| return _leftMargin; |
| } |
| |
| public function pushLeftMargin(leftMargin:Number):void |
| { |
| _leftMargin += leftMargin; |
| } |
| |
| public function popLeftMargin(leftMargin:Number):void |
| { |
| _leftMargin -= leftMargin; |
| } |
| |
| public function get rightMargin():Number |
| { |
| return _rightMargin; |
| } |
| |
| public function pushRightMargin(rightMargin:Number):void |
| { |
| _rightMargin += rightMargin; |
| } |
| |
| public function popRightMargin(rightMargin:Number):void |
| { |
| _rightMargin -= rightMargin; |
| } |
| |
| public function pushInsideListItemMargin(margin:Number):void |
| { _insideListItemMargin += margin; } |
| public function popInsideListItemMargin(margin:Number):void |
| { _insideListItemMargin -= margin; } |
| public function get insideListItemMargin():Number |
| { return _insideListItemMargin; } |
| |
| public function getComposeXCoord(o:Rectangle):Number |
| { |
| // trace("LPL: getComposeXCoord"); |
| return _verticalText ? o.right : o.left; |
| } |
| public function getComposeYCoord(o:Rectangle):Number |
| { |
| // trace("LPL: getComposeYCoord"); |
| return o.top; |
| } |
| |
| public function getComposeWidth(o:Rectangle):Number |
| { |
| // trace("LPL: getComposeWidth"); |
| if (measureLogicalWidth) |
| return TextLine.MAX_LINE_WIDTH; |
| return _verticalText ? o.height : o.width; |
| } |
| |
| public function getComposeHeight(o:Rectangle):Number |
| { |
| // trace("LPL: getComposeHeight"); |
| if (measureLogicalHeight) |
| return TextLine.MAX_LINE_WIDTH; |
| return _verticalText ? o.width : o.height; |
| } |
| |
| /** Returns true if the current parcel is the last. |
| */ |
| public function atLast():Boolean |
| { |
| return _numParcels == 0 || _currentParcelIndex == _numParcels -1; |
| } |
| |
| public function atEnd():Boolean |
| { |
| return _numParcels == 0 || _currentParcelIndex >= _numParcels; |
| } |
| |
| public function gotoParcel(index:int, depth:Number = 0):Boolean |
| { |
| if ( index < 0 || index >= _numParcels ) |
| return false; |
| |
| _currentParcel = this.getParcelAt(index); |
| if ( _currentParcel == null ) |
| return false; |
| _currentParcelIndex = index; |
| |
| _totalDepth = depth; |
| |
| return true; |
| } |
| |
| public function next():Boolean |
| { |
| CONFIG::debug { assert(_currentParcelIndex >= -1 && _currentParcelIndex < _numParcels, "invalid _currentParcelIndex in ParcelList"); } |
| var nextParcelIsValid:Boolean = (_currentParcelIndex + 1) < _numParcels; |
| |
| _currentParcelIndex += 1; |
| _totalDepth = 0; |
| |
| if (nextParcelIsValid) |
| { |
| _currentParcel = getParcelAt(_currentParcelIndex); |
| var nextController:ContainerController = _currentParcel.controller; |
| } |
| else |
| _currentParcel = null; |
| |
| return nextParcelIsValid; |
| } |
| |
| public function get currentParcel():Parcel |
| { return _currentParcel; } |
| |
| /**Return the slug rectangle for a line that goes at the current vertical location, |
| * and could extend down for at least height pixels. Note that this function |
| * can change the current parcel, and the location within the parcel. |
| * @param slugRect result rectangle where line was fit |
| * @param height amount of contiguous vertical space that must be available |
| * @param minWidth amount of contiguous horizontal space that must be available |
| * @return true if a line slug was fit horizontal space actually available |
| */ |
| public function getLineSlug(slug:Slug, height:Number, minWidth:Number, textIndent:Number, directionLTR:Boolean):Boolean |
| { |
| if (currentParcel.getLineSlug(slug, _totalDepth, height, minWidth, currentParcel.fitAny ? 1 : int(height), _leftMargin, _rightMargin, textIndent+_insideListItemMargin, directionLTR, _explicitLineBreaks)) |
| { |
| if (totalDepth != slug.depth) |
| _totalDepth = slug.depth; |
| return true; |
| } |
| return false; |
| } |
| |
| // Attempts to fit a float of the specified width and height in the current parcel. Float is considered to fit if it |
| // is alone on the line but exceeds the parcel width and fits within the logical height. |
| // Returns success or failure. |
| public function fitFloat(slug:Slug, totalDepth:Number, width:Number, height:Number):Boolean |
| { |
| return currentParcel.getLineSlug(slug, totalDepth, height, width, currentParcel.fitAny ? 1 : int(height), _leftMargin, _rightMargin, 0, true, _explicitLineBreaks); |
| } |
| |
| } //end class |
| } //end package |