//////////////////////////////////////////////////////////////////////////////// // // 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.formats.BlockProgression; import flashx.textLayout.formats.ITextLayoutFormat; import flashx.textLayout.formats.LineBreak; import flashx.textLayout.formats.VerticalAlign; import flashx.textLayout.tlf_internal; use namespace tlf_internal; [ExcludeClass] /** @private * Implementation of IParcelList used for composing text containers that have single * column, no wraps, and no floats. * * ParcelList will always have one parcel, which corresponds to the container's * bounding box. */ public class ParcelList implements IParcelList { 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. */ private var _parcelArray:Array; /* of Parcel */ private var _numParcels:int; private 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; /** Callback to notify that we're going to the next parcel */ protected var _notifyOnParcelChange:Function; /** Column number of the current parcel */ private var _columnIndex:int; private var _columnController:ContainerController; private var _explicitLineBreaks:Boolean; /** true if we should include the last line if any part of it fits */ // protected var _includePartialLine:Boolean; // private var parcel:Parcel; 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? /** minimum allowable width of a line */ /** Writing mode for vertical, left to right and left to right. @see text.formats.BlockProgression */ protected var _blockProgression:String; // 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:IParcelList):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; this._columnController = 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(getParcelAtIndex(i)); return boundsArray; } protected function get numParcels():int { return _numParcels; } protected function getParcelAtIndex(idx:int):Parcel { return _numParcels == 1 ? _singleParcel : _parcelArray[idx]; } protected function insertParcel(startIdx:int, parcel:Parcel):void { if (_numParcels == 0) _singleParcel = parcel; else { if (_numParcels == 1) _parcelArray = [ _singleParcel ]; _parcelArray.splice(startIdx, 0, parcel); } _numParcels++; } protected function set parcels(newParcels:Array):void { _numParcels = newParcels.length; if (_numParcels == 0) _parcelArray = null; else if (_numParcels == 1) { _parcelArray = null; _singleParcel = newParcels[0]; } else _parcelArray = newParcels; } public function get left():Number { return _currentParcel.left; } public function get right():Number { return _currentParcel.right; } public function get top():Number { return _currentParcel.top; } public function get bottom():Number { return _currentParcel.bottom; } public function get width():Number { return _currentParcel.width; } public function get height():Number { return _currentParcel.height; } public function get fitAny():Boolean { return _currentParcel.fitAny; } public function get controller():ContainerController { return _columnController; } public function get columnIndex():int { return _columnIndex; } public function get explicitLineBreaks():Boolean { return _explicitLineBreaks; } private function get measureWidth():Boolean { if (_explicitLineBreaks) return true; if (!_currentParcel) return false; if (_blockProgression == BlockProgression.TB) return _currentParcel.measureWidth; else return _currentParcel.measureHeight; } private function get measureHeight():Boolean { if (!_currentParcel) return false; if (_blockProgression == BlockProgression.TB) return _currentParcel.measureHeight; else return _currentParcel.measureWidth; } public function get totalDepth():Number { return _totalDepth; } public function get notifyOnParcelChange():Function { return _notifyOnParcelChange; } public function set notifyOnParcelChange(val:Function):void { _notifyOnParcelChange = val; } public function addTotalDepth(value:Number):Number { _hasContent = true; _totalDepth += value; // trace("addTotalDepth", value, "newDepth", totalDepth); return _totalDepth; } protected function reset():void { _totalDepth = 0; _hasContent = false; _columnIndex = 0; _currentParcelIndex = 0; if (_numParcels != 0) { _currentParcel = getParcelAtIndex(_currentParcelIndex); _columnController = _currentParcel.controller; _columnIndex = 0; } else { _currentParcel = null; _columnController = null; _columnIndex = -1; } } private function addParcel(column:Rectangle, cont:ContainerController, col:int, colCoverage:int):void { var newParcel:Parcel = _numParcels == 0 && _singleParcel ? _singleParcel.initialize(column.x,column.y,column.width,column.height,cont,col,colCoverage) : new Parcel(column.x, column.y, column.width, column.height, cont, col, colCoverage) if (_numParcels == 0) _singleParcel = newParcel; else if (numParcels == 1) _parcelArray = [ _singleParcel, newParcel ]; else _parcelArray.push(newParcel); _numParcels++; } protected function addOneControllerToParcelList(controllerToInitialize:ContainerController):void { // Initialize new parcels for columns var columnState:ColumnState = controllerToInitialize.columnState; for (var columnIndex:int = 0; columnIndex < columnState.columnCount; columnIndex++) { var column:Rectangle = columnState.getColumnAt(columnIndex); if (!column.isEmpty()) addParcel(column, controllerToInitialize, columnIndex, Parcel.FULL_COLUMN); } } public function beginCompose(composer:IFlowComposer, controllerEndIndex:int, composeToPosition:Boolean):void { _flowComposer = composer; var rootFormat:ITextLayoutFormat = composer.rootElement.computedFormat; _explicitLineBreaks = rootFormat.lineBreak == LineBreak.EXPLICIT; _blockProgression = rootFormat.blockProgression; 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 = 0; do { addOneControllerToParcelList(ContainerController(composer.getControllerAt(idx))); } while (idx++ != controllerEndIndex) // adjust the last container for scrolling if (controllerEndIndex == composer.numControllers-1) adjustForScroll(ContainerController(ContainerController(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 (_blockProgression != BlockProgression.RL) { if (containerToInitialize.verticalScrollPolicy != ScrollPolicy.OFF) { var p:Parcel = getParcelAtIndex(_numParcels-1); if (p) { var verticalPaddingAmount:Number = containerToInitialize.effectivePaddingBottom + containerToInitialize.effectivePaddingTop; p.bottom = containerToInitialize.verticalScrollPosition + p.height + verticalPaddingAmount; p.fitAny = true; p.composeToPosition = composeToPosition; } } } else // vertical text case { if (containerToInitialize.horizontalScrollPolicy != ScrollPolicy.OFF) { p = getParcelAtIndex(_numParcels-1); if (p) { var horizontalPaddingAmount:Number = containerToInitialize.effectivePaddingRight + containerToInitialize.effectivePaddingLeft; p.left = containerToInitialize.horizontalScrollPosition - p.width - horizontalPaddingAmount; p.fitAny = true; p.composeToPosition = composeToPosition; } } } } public function getComposeXCoord(o:Rectangle):Number { // trace("LPL: getComposeXCoord"); return _blockProgression == BlockProgression.RL ? 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 (measureWidth) return TextLine.MAX_LINE_WIDTH; return _blockProgression == BlockProgression.RL ? o.height : o.width; } public function getComposeHeight(o:Rectangle):Number { // trace("LPL: getComposeHeight"); if (measureHeight) return TextLine.MAX_LINE_WIDTH; return _blockProgression == BlockProgression.RL ? o.width : o.height; } /** True if the current parcel is at the top of the column */ public function isColumnStart():Boolean { return (!_hasContent && _currentParcel.topOfColumn); } /** 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 next():Boolean { CONFIG::debug { assert(_currentParcelIndex >= 0 && _currentParcelIndex < _numParcels, "invalid _currentParcelIndex in ParcelList"); } var nextParcelIsValid:Boolean = (_currentParcelIndex + 1) < _numParcels; _notifyOnParcelChange(nextParcelIsValid ? getParcelAtIndex(_currentParcelIndex + 1) : null) _currentParcelIndex += 1; _totalDepth = 0; _hasContent = false; if (nextParcelIsValid) { _currentParcel = getParcelAtIndex(_currentParcelIndex); var nextController:ContainerController = _currentParcel.controller; if (nextController == _columnController) _columnIndex++; else { _columnIndex = 0; _columnController = nextController; } } else { _currentParcel = null; _columnIndex = -1; _columnController = null; } return nextParcelIsValid; } public function createParcel(parcel:Rectangle, blockProgression:String, verticalJump:Boolean):Boolean // If we can get the requested parcel to fit, create it in the parcels list { return false; } public function createParcelExperimental(parcel:Rectangle, wrapType:String):Boolean // If we can get the requested parcel to fit, create it in the parcels list { return false; } public function get currentParcel():Parcel { return _currentParcel; } /**Return the width 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 height amount of contiguous vertical space that must be available * @param minWidth amount of contiguous horizontal space that must be available * @return amount of contiguous horizontal space actually available */ public function getLineSlug(slugRect:Rectangle, height:Number, minWidth:Number = 0):Boolean { // trace("getLineSlug",slugRect,height,minWidth); if (_currentParcelIndex < _numParcels) { var tileWidth:Number = getComposeWidth(_currentParcel); if (tileWidth > minWidth) { // Fit the line if any part of the line fits in the height. Observe the cast to int! if (currentParcel.composeToPosition || _totalDepth + (_currentParcel.fitAny ? 1 : int(height)) <= getComposeHeight(_currentParcel)) { if (_blockProgression != BlockProgression.RL) { slugRect.x = left; slugRect.y = _currentParcel.top + _totalDepth; slugRect.width = tileWidth; slugRect.height = height; } else { slugRect.x = left; slugRect.y = _currentParcel.top; slugRect.width = _currentParcel.width-_totalDepth; slugRect.height = tileWidth; } return true; } } } return false; } } //end class } //end package