blob: 09e69fee0a7dc19c56f38d00fa78aa592b96e698 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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;
}
}