| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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.debug.assert; |
| import flashx.textLayout.container.ContainerController; |
| import flashx.textLayout.formats.ClearFloats; |
| import flashx.textLayout.formats.Direction; |
| import flashx.textLayout.tlf_internal; |
| |
| use namespace tlf_internal; |
| |
| [ExcludeClass] |
| /** Helper class for implementations of IParcelList |
| * |
| * @private |
| */ |
| public class Parcel |
| { |
| public var x:Number; |
| public var y:Number; |
| public var width:Number; |
| public var height:Number; |
| public var logicalWidth:Number; |
| |
| private var _controller:ContainerController; |
| private var _columnIndex:int; |
| private var _fitAny:Boolean; |
| private var _composeToPosition:Boolean; |
| |
| private var _left:Edge; |
| private var _right:Edge; |
| private var _maxWidth:Number; |
| |
| private var _verticalText:Boolean; |
| |
| /** Constructor. */ |
| public function Parcel(verticalText:Boolean, x:Number, y:Number, width:Number, height:Number, controller:ContainerController, columnIndex:int) |
| { |
| initialize(verticalText, x, y, width, height, controller, columnIndex); |
| } |
| |
| public function initialize(verticalText:Boolean, x:Number, y:Number, width:Number, height:Number, controller:ContainerController, columnIndex:int):Parcel |
| { |
| this.x = x; |
| this.y = y; |
| this.width = width; |
| this.height = height; |
| this.logicalWidth = verticalText ? height : width; |
| this._verticalText = verticalText; |
| |
| _controller = controller; |
| _columnIndex = columnIndex; |
| _fitAny = false; |
| _composeToPosition = false; |
| |
| var xmin:Number; |
| var xmax:Number; |
| if (verticalText) |
| { |
| xmin = y; |
| _maxWidth = height; |
| } |
| else |
| { |
| xmin = x; |
| _maxWidth = width; |
| } |
| if (_maxWidth <= 0) |
| throw new Error("Malformed column"); |
| |
| _left = new Edge(xmin); |
| _right = new Edge(xmin + _maxWidth); |
| return this; |
| } |
| |
| |
| /** prevent any leaks. @private */ |
| tlf_internal function releaseAnyReferences():void |
| { |
| _controller = null; |
| } |
| |
| public function get bottom():Number { return (y + height); } |
| public function get right():Number { return (x + width); } |
| |
| public function get controller():ContainerController |
| { return _controller; } |
| |
| /** column number in the container */ |
| public function get columnIndex():int |
| { return _columnIndex; } |
| public function get fitAny():Boolean |
| { return _fitAny; } |
| public function set fitAny(value:Boolean):void |
| { _fitAny = value; } |
| public function get composeToPosition():Boolean |
| { return _composeToPosition; } |
| public function set composeToPosition(value:Boolean):void |
| { _composeToPosition = value; } |
| /** Do explicit line breaking (no wrapping) */ |
| |
| private function getLogicalHeight():Number |
| { |
| if (_verticalText) |
| { |
| return _controller.measureWidth ? TextLine.MAX_LINE_WIDTH : width; |
| } |
| else |
| { |
| return _controller.measureHeight ? TextLine.MAX_LINE_WIDTH : height; |
| } |
| } |
| |
| // Returns the amount to move down to apply clear past any floats |
| public function applyClear(clear:String, depth:Number, direction:String):Number |
| { |
| var leftMargin:Number; |
| var rightMargin:Number; |
| var adjustedDepth:Number = depth; |
| |
| if (clear == ClearFloats.START) |
| clear = (direction == Direction.LTR) ? ClearFloats.LEFT : ClearFloats.RIGHT; |
| else if (clear == ClearFloats.END) |
| clear = (direction == Direction.RTL) ? ClearFloats.LEFT : ClearFloats.RIGHT; |
| |
| while (adjustedDepth < Number.MAX_VALUE) |
| { |
| leftMargin = _left.getMaxForSpan(adjustedDepth, adjustedDepth + 1); // getLeftForSpan |
| if (leftMargin > 0 && (clear == ClearFloats.BOTH || clear == ClearFloats.LEFT)) |
| { |
| adjustedDepth = _left.findNextTransition(adjustedDepth); |
| continue; |
| } |
| rightMargin = _right.getMaxForSpan(adjustedDepth, adjustedDepth + 1); // getRightForSpan |
| if (rightMargin > 0 && (clear == ClearFloats.BOTH || clear == ClearFloats.RIGHT)) |
| { |
| adjustedDepth = _right.findNextTransition(adjustedDepth); |
| continue; |
| } |
| |
| return adjustedDepth - depth; |
| } |
| |
| return (_verticalText ? this.width : this.height); |
| } |
| |
| public function fitsInHeight(depth:Number, minimumHeight:Number):Boolean |
| { |
| return composeToPosition || depth + minimumHeight <= getLogicalHeight(); |
| } |
| |
| // Given a current location, what is the longest longest line of a given height that can be placed there, |
| // that is at least as wide as the minimum width. Return false if there is no possible line in the parcel. |
| // May shift the line down if thee is more space below. |
| public function getLineSlug(slug:Slug, depth:Number, lineHeight:Number, minimumWidth:Number, minimumHeight:Number, leftMargin:Number, rightMargin:Number, textIndent:Number, directionLTR:Boolean, useExplicitLineBreaks:Boolean):Boolean |
| { |
| if (!fitsInHeight(depth, minimumHeight)) |
| return false; |
| |
| slug.height = lineHeight; |
| |
| while (depth < Number.MAX_VALUE) |
| { |
| slug.depth = depth; |
| |
| slug.leftMargin = _left.getMaxForSpan(slug.depth, slug.depth + lineHeight); // getLeftForSpan |
| slug.wrapsKnockOut = slug.leftMargin != 0; |
| if (leftMargin > 0) |
| slug.leftMargin = Math.max(leftMargin, slug.leftMargin); |
| else |
| slug.leftMargin += leftMargin; // negative indent, let it overlap |
| slug.rightMargin = _right.getMaxForSpan(slug.depth, slug.depth + lineHeight); // getRightForSpan |
| slug.wrapsKnockOut = slug.wrapsKnockOut || (slug.rightMargin != 0); |
| if (rightMargin > 0) |
| slug.rightMargin = Math.max(rightMargin, slug.rightMargin); |
| else |
| slug.rightMargin += rightMargin; // negative indent, let it overlap |
| if (textIndent) |
| { |
| if (directionLTR) |
| slug.leftMargin += textIndent; |
| else |
| slug.rightMargin += textIndent; |
| } |
| |
| if (useExplicitLineBreaks || (_verticalText && _controller.measureHeight) || (!_verticalText && _controller.measureWidth)) |
| slug.width = TextLine.MAX_LINE_WIDTH; |
| else |
| slug.width = this.logicalWidth - (slug.leftMargin + slug.rightMargin); |
| if (!minimumWidth || slug.width >= minimumWidth) |
| break; |
| depth = findNextTransition(depth); |
| } |
| |
| return (depth < Number.MAX_VALUE); |
| } |
| |
| public function knockOut(knockOutWidth:Number, yMin:Number, yMax:Number, onLeft:Boolean):void |
| { |
| var edge:Edge = onLeft ? _left : _right; |
| edge.addSpan(knockOutWidth, yMin, yMax); |
| } |
| |
| public function removeKnockOut(knockOutWidth:Number, yMin:Number, yMax:Number, onLeft:Boolean):void |
| { |
| var edge:Edge = onLeft ? _left : _right; |
| edge.removeSpan(knockOutWidth, yMin, yMax); |
| } |
| |
| /** Returns true if the parcel has no knockouts */ |
| public function isRectangular():Boolean |
| { |
| return (_left.numSpans <= 0 && _right.numSpans <= 0); |
| } |
| |
| /* public function getLeftForSpan(ymin:Number, ymax:Number):Number |
| { |
| return _left.getMaxForSpan(ymin, ymax); |
| } |
| |
| public function getRightForSpan(ymin:Number, ymax:Number):Number |
| { |
| return _right.getMinForSpan(ymin, ymax); |
| } */ |
| |
| public function findNextTransition(y:Number):Number |
| { |
| return Math.min(_left.findNextTransition(y), _right.findNextTransition(y)); |
| } |
| |
| } |
| } |
| |
| class Span |
| { |
| public var x:Number; |
| public var ymin:Number; |
| public var ymax:Number; |
| |
| public function Span(x:Number, ymin:Number, ymax:Number) |
| { |
| this.x = x; |
| this.ymin = ymin; |
| this.ymax = ymax; |
| } |
| |
| public function overlapsInY(ymin:Number, ymax:Number):Boolean |
| { |
| return !(ymax < this.ymin || ymin >= this.ymax); |
| } |
| |
| public function equals(x:Number, ymin:Number, ymax:Number):Boolean |
| { |
| return x == this.x && ymin == this.ymin && ymax == this.ymax; |
| } |
| } |
| |
| import __AS3__.vec.Vector; |
| //import flashx.textLayout.utils.Twips; |
| |
| class Edge |
| { |
| private var _xbase:Number; |
| private var _spanList:Vector.<Span>; |
| |
| public function Edge(xbase:Number) |
| { |
| _xbase = xbase; |
| _spanList = new Vector.<Span>(); |
| } |
| |
| public function get base():Number |
| { |
| return _xbase; |
| } |
| |
| public function addSpan(x:Number, ymin:Number, ymax:Number):void |
| { |
| _spanList.push(new Span(x, ymin, ymax)); |
| } |
| |
| public function removeSpan(x:Number, ymin:Number, ymax:Number):void |
| { |
| for (var i:int=0; i < _spanList.length; ++i) |
| { |
| if (_spanList[i].equals(x, ymin, ymax)) |
| { |
| _spanList.splice(i,1); |
| return; |
| } |
| } |
| |
| CONFIG::debug { flashx.textLayout.debug.assert(false, "Deleting a span not in list"); } |
| } |
| |
| public function get numSpans():int |
| { |
| return _spanList.length; |
| } |
| |
| public function getMaxForSpan(ymin:Number, ymax:Number):Number |
| { |
| var res:Number = 0; |
| for each (var span:Span in _spanList) |
| { |
| if (span.x > res && span.overlapsInY(ymin, ymax)) |
| res = span.x; |
| } |
| return res; |
| } |
| |
| /*public function getMinForSpan(ymin:Number, ymax:Number):Number |
| { |
| var res:Number = _xbase; |
| for each (var span:Span in _spanList) |
| { |
| if (span.x < res && span.overlapsInY(ymin, ymax)) |
| res = span.x; |
| } |
| return res; |
| } */ |
| |
| public function findNextTransition(y:Number):Number |
| { |
| var res:Number = Number.MAX_VALUE; |
| for each (var s:Span in _spanList) |
| { |
| if (s.ymin > y && s.ymin < res) |
| res = s.ymin; |
| if (s.ymax > y && s.ymax < res) |
| res = s.ymax; |
| } |
| return res; |
| } |
| } |
| |