blob: ac87ac5273f0d29130d325bd13f4c3ab3b06ce50 [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.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.Graphics;
import flash.display.Sprite;
import flash.geom.Rectangle;
import flash.text.engine.TextBaseline;
import flashx.textLayout.container.ContainerController;
import flashx.textLayout.container.IFloatController;
import flashx.textLayout.elements.ContainerFormattedElement;
import flashx.textLayout.elements.FlowElement;
import flashx.textLayout.elements.FlowGroupElement;
import flashx.textLayout.elements.FlowLeafElement;
import flashx.textLayout.elements.InlineGraphicElement;
import flashx.textLayout.elements.InlineGraphicElementStatus;
import flashx.textLayout.elements.ParagraphElement;
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.formats.BlockProgression;
import flashx.textLayout.formats.Float;
import flashx.textLayout.tlf_internal;
use namespace tlf_internal;
/** Keeps track of internal state during composition.
*
* This is the advanced layout version, used when there are floats, wraps, or columns.
*/
public class LayoutComposeState extends ComposeState
{
// a single ComposeState that is checked out and checked in
static private var _sharedLayoutComposeState:LayoutComposeState;
/** @private */
static tlf_internal function getLayoutComposeState():LayoutComposeState
{
var rslt:LayoutComposeState = _sharedLayoutComposeState ? _sharedLayoutComposeState : new LayoutComposeState();
_sharedLayoutComposeState = null;
return rslt;
}
/** @private */
static tlf_internal function releaseLayoutComposeState(state:ComposeState):void
{
if (_sharedLayoutComposeState == null)
{
_sharedLayoutComposeState = state as LayoutComposeState;
if (_sharedLayoutComposeState)
_sharedLayoutComposeState.releaseAnyReferences();
}
}
public function LayoutComposeState()
{
super();
}
/** @private */
protected override function createParcelList():IParcelList
{
return LayoutParcelList.getLayoutParcelList();
}
/** @private */
protected override function releaseParcelList(list:IParcelList):void
{
LayoutParcelList.releaseLayoutParcelList(list);
}
protected override function initializeForComposer(composer:IFlowComposer,composeToPosition:int,controllerEndIndex:int):void
{
// We don't allow composing from the middle of a container because with floats parcels are dynamically created, and
// there may have been some created before the _startCompositionPosition, and we don't have a way to recreate the full
// parcel state without composing all the text. This is an optimization that should be added later when we fix the floats
// code. We can cache off information about each parcel that was created, and the text position it was created at, and
// use that to recreate the parcel list state.
super.initializeForComposer(composer,composeToPosition,controllerEndIndex);
_startComposePosition = Math.min(_startComposePosition, _startController.absoluteStart);
}
// Debugging code to show parcel edges - requires removing graphics.clear from ContainerControllerBase
/* CONFIG::debug override public function composeTextFlow(textFlow:TextFlow):int
{
var value:int = super.composeTextFlow(textFlow);
var controller:ContainerController = textFlow.flowComposer.getControllerAt(0);
if (controller)
{
var container:Sprite = textFlow.flowComposer.getControllerAt(0).container as Sprite;
var graphics:Graphics = container.graphics;
graphics.clear();
graphics.lineStyle(1, 0xFF0000);
var boundsArray:Array = LayoutParcelList(_parcelList).getBounds();
for (var i:int = 0; i < boundsArray.length; ++i)
{
var bounds:Rectangle = boundsArray[i] as Rectangle;
graphics.moveTo(bounds.x, bounds.y);
graphics.lineTo(bounds.right, bounds.y);
graphics.lineTo(bounds.right, bounds.bottom);
graphics.lineTo(bounds.left, bounds.bottom);
graphics.lineTo(bounds.left, bounds.top);
}
}
return value;
} */
/* @private */
override protected function parcelHasChanged(newParcel:Parcel):void
{
if (_curParcel && newParcel && _curParcel.controller == newParcel.controller && _curParcel.column == newParcel.column)
vjDisableThisParcel = true;
super.parcelHasChanged(newParcel);
}
/* @private */
protected override function composeFloat(elem:ContainerFormattedElement,composeFrame:ContainerController):void
{
elem.createGeometry(null);
// TODO: support arbitrary containers?
var floatContainer:DisplayObjectContainer = ContainerFormattedElement(elem).flowComposer.getControllerAt(0).container as DisplayObjectContainer;
// var floatContainer:DisplayObjectContainer = ContainerFormattedElement(elem).container as DisplayObjectContainer;
if (floatContainer != null) // is inline
{
if (!(composeFrame is IFloatController))
throw("need a float layout-capable controller!");
// Add as an inline, figure out the size of the inline, wrap around the inline
var parcelRect:Rectangle = IFloatController(composeFrame).computeInlineArea(floatContainer);
// See if it fits. If so, update the state to show the change. If not, remove the item.
if (parcelList.createParcel(parcelRect, elem.computedFormat.blockProgression, true /* next text goes below */))
{
floatContainer.x = parcelList.left;
floatContainer.y = parcelList.top;
floatContainer.height = parcelRect.height;
IFloatController(parcelList.controller).recordInlineChild(floatContainer);
//_contentAlignmentWidth = Math.max(_contentAlignmentWidth, parcelList.right);
parcelList.addTotalDepth(parcelRect.height);
parcelList.next(); // advance to next parcel
}
}
_curLineIndex = composeFrame.rootElement.getTextFlow().flowComposer.findLineIndexAtPosition(elem.getAbsoluteStart() + elem.textLength);
vjDisableThisParcel = true;
vjBeginLineIndex = _curLineIndex;
}
/*
* Compose a floating graphic
*
* @param elem float we'e composing
*/
protected function composeFloatInline(elem:InlineGraphicElement):void
{
if (elem.elementHeight == 0 || elem.elementWidth == 0) // can't compose yet -- graphic isn't ready
return;
var containerElement:ContainerFormattedElement = elem.getAncestorWithContainer();
var blockProgression:String = containerElement.computedFormat.blockProgression;
// HACK!!!
// if the baselineZero is set to ideographicTop, then the descent is the point size (measured from ideographic top)
// but in this case we've already factored that into the line height, so we're adding twice. Very confusing.
var effectiveLastLineDescent:Number = 0;
if (!isNaN(_lastLineDescent))
effectiveLastLineDescent = _lastLineDescent;
var floatRect:Rectangle = new Rectangle(parcelList.left, parcelList.top, elem.elementWidth, elem.elementHeight);
if (blockProgression == BlockProgression.RL)
{
floatRect.left -= effectiveLastLineDescent;
if (elem.float == Float.RIGHT)
floatRect.offset(0, (parcelList.bottom - elem.elementHeight) - floatRect.y);
}
else
{
floatRect.bottom += effectiveLastLineDescent;
if (elem.float == Float.RIGHT)
floatRect.offset((parcelList.right - elem.elementWidth) - floatRect.x, 0);
}
// See if it fits. If so, update the state to show the change. If not, remove the item.
if (parcelList.createParcelExperimental(floatRect, elem.float == Float.RIGHT ? "left" : "right"))
{
var graphic:DisplayObject = elem.graphic;
if (graphic)
{
IFloatController(parcelList.controller).recordInlineChild(graphic);
graphic.x = parcelList.left;
graphic.y = parcelList.top;
if (blockProgression == BlockProgression.TB)
graphic.y += effectiveLastLineDescent;
}
if (blockProgression == BlockProgression.TB)
{
parcelList.addTotalDepth(floatRect.height);
//_contentAlignmentWidth = Math.max(_contentAlignmentWidth, floatRect.width);
}
else
{
parcelList.addTotalDepth(floatRect.width);
//_contentAlignmentWidth = Math.max(_contentAlignmentWidth, floatRect.height);
}
parcelList.next(); // advance to next parcel
}
// _curLineIndex = _curParcel.controller.rootElement.getTextFlow().getAbsoluteLineIndex(elem.getAbsoluteStart() + elem.textLength);
vjDisableThisParcel = true;
// vjBeginLineIndex = _curLineIndex;
}
// Called from composeParagraphElement when we are starting to compose a line. Has hooks to handle floats.
protected override function startLine():void
{
// Compose floats that appear at the start of the line, before processing any text. That way the following text will
// wrap correctly
if (_curElement is InlineGraphicElement && InlineGraphicElement(_curElement).float != Float.NONE)
{
var graphic:InlineGraphicElement = InlineGraphicElement(_curElement);
while (graphic && graphic.float != Float.NONE)
{
composeFloatInline(graphic);
graphic = graphic.getNextLeaf() as InlineGraphicElement;
}
}
}
// Called from composeParagraphElement when we are starting to compose a line. Has hooks to handle floats.
protected override function endLine():void
{
var currentOffset:int = _curElementOffset;
var currentElementStart:int = _curElementStart;
var element:FlowLeafElement = _curElement;
// advance to the next element, using the rootElement of the container as a limitNode
// to prevent going past the content bound to this container
if (currentOffset >= element.textLength)
{
// We may have composed ahead over several spans; skip until we match up
// Loop until we use catch up to where the line we just composed ended (pos).
// Stop if we run out of elements. Skip empty inline elements, and skip floats
// that came at the start of the line before any text -- they've already been
// processed.
var firstTextSpan:Boolean = true;
do{
if (element is InlineGraphicElement && InlineGraphicElement(element).float != Float.NONE)
{
if (!firstTextSpan)
composeFloatInline(element as InlineGraphicElement);
}
else firstTextSpan = false;
currentOffset -= element.textLength;
currentElementStart += _curElement.textLength;
if (currentElementStart == _curParaStart+_curParaElement.textLength)
{
break; // reached end of paragraph
}
element = element.getNextLeaf();
} while (element != null && (currentOffset >= element.textLength || element.textLength == 0 ));
}
}
}
}