blob: 3dc40603d6abf4b2771af46cf68ed4825d9585cc [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.TextBaseline;
import flash.text.engine.TextBlock;
import flash.text.engine.TextLine;
import flashx.textLayout.*;
import flashx.textLayout.container.ContainerController;
import flashx.textLayout.debug.Debugging;
import flashx.textLayout.debug.assert;
import flashx.textLayout.elements.*;
import flashx.textLayout.formats.*;
import flashx.textLayout.utils.LocaleUtil;
use namespace tlf_internal;
[ExcludeClass]
/** @private Common composer base class */
public class BaseCompose
{
public static function get globalSWFContext():ISWFContext
{
return GlobalSWFContext.globalSWFContext;
}
protected var _parcelList:IParcelList;
/** List of areas we're composing into, matches the container's bounding box */
public function get parcelList():IParcelList
{ return _parcelList; }
/** Element of current location */
protected var _curElement:FlowLeafElement;
/** Absolute start position of _curElement */
protected var _curElementStart:int;
/** Offset from element start to current location */
protected var _curElementOffset:int;
/** ParagraphElement that contains the current location */
protected var _curParaElement:ParagraphElement;
protected var _curParaFormat:ITextLayoutFormat;
/** Absolute start position of _curParaElement */
protected var _curParaStart:int;
/** leading direction for the current line's para (set when line is being composed and committed to _lastLineLeadingModel when line is finalized) */
private var _curLineLeadingModel:String = "";
/** leading amount for the current line (set when line is being composed and committed to _lastLineLeading when line is finalized) */
private var _curLineLeading:Number;
/** leading direction for the last line's para */
protected var _lastLineLeadingModel:String = "";
/** leading amount for the last line */
protected var _lastLineLeading:Number;
/** descent of the last line */
protected var _lastLineDescent:Number;
/** Amount of spaceAfter added to the previous line */
protected var _spaceCarried:Number;
/** BlockProgression - vertical horizontal etc. @see text.formats.BlockProgression */
protected var _blockProgression:String;
/** Minimum left edge coordinate across all the parcels in a controller */
private var _controllerLeft:Number;
/** Minimum top edge across all the parcels in a controller */
private var _controllerTop:Number;
/** Maximum right edge coordinate across all the parcels in a controller */
private var _controllerRight:Number;
/** Maximum bottom edge coordinate across all the parcels in a controller */
private var _controllerBottom:Number;
/** Maximum horizontal extension from left/right edge of the parcel. Alignment width for the parcel. */
protected var _contentLogicalExtent:Number;
/* Commited extent any lines needing additional alignment must update this number */
protected var _contentCommittedExtent:Number;
/** Minimum left edge coordinate across all the parcels in a controller */
protected var _parcelLeft:Number;
/** Minimum top edge across all the parcels in a controller */
protected var _parcelTop:Number;
/** Maximum right edge coordinate across all the parcels in a controller */
protected var _parcelRight:Number;
/** Maximum bottom edge coordinate across all the parcels in a controller */
protected var _parcelBottom:Number;
/** owning textFlow of current compose */
protected var _textFlow:TextFlow;
private var _releaseLineCreationData:Boolean;
/** flowComposer of current compose */
protected var _flowComposer:StandardFlowComposer;
/** rootElement of current compose */
protected var _rootElement:ContainerFormattedElement;
/** position to stop composing at */
protected var _stopComposePos:int;
/** First damaged controller to begin composing */
protected var _startController:ContainerController;
/** Beginning composition position. Note this gets cleared once its been passed */
protected var _startComposePosition:int;
// scratch line slugs
static protected var _candidateLineSlug:Rectangle = new Rectangle();
static protected var _lineSlug:Rectangle = new Rectangle();
// scratch array for holding lines awaiting alignment
static private var _alignLines:Array;
/** Parcel we are composing - used for keeping track of when it changes b/c parcelList.parcel may have advanced */
protected var _curParcel:Parcel;
/** Start position of _curParcel */
protected var _curParcelStart:int;
/** Constructor. */
public function BaseCompose()
{
}
protected function createParcelList():IParcelList
{ return null; }
protected function releaseParcelList(list:IParcelList):void
{ }
/** Starting controller for skipping ahead */
public function get startController():ContainerController
{ return _startController; }
/** prevent any leaks. @private */
tlf_internal function releaseAnyReferences():void
{
_curElement = null;
_curParaElement = null;
_curParaFormat = null;
_flowComposer = null;
_parcelList = null;
_rootElement = null;
_startController = null;
_textFlow = null;
}
/** Initialize for a composition that will compose up through the controllerEndIndex, or all the way to the end of the flow
* @param composer
* @param composeToPosition -1 means not specified. 0 means request to compose nothing, >0 specifies a position to force compose to
* @param controllerEndIndex index of the last controller to compose for, or -1 to compose through all controllers
*/
protected function initializeForComposer(composer:IFlowComposer, composeToPosition:int, controllerEndIndex:int):void
{
_parcelList = createParcelList();
_parcelList.notifyOnParcelChange = parcelHasChanged;
_spaceCarried = 0;
// TODO: just use the rootElement for table cells
_blockProgression = composer.rootElement.computedFormat.blockProgression;
// for a non-specified compose position the ParcelList handles the bail out - just set to textLength
_stopComposePos = composeToPosition >= 0 ? Math.min(_textFlow.textLength,composeToPosition) : _textFlow.textLength;
// this chains through the list - tell it if a "care about" comopseToPosition was specified
_parcelList.beginCompose(composer, controllerEndIndex, composeToPosition > 0);
_contentLogicalExtent = 0;
_contentCommittedExtent = 0;
}
/*
* Compose an inline-block element, used for tables or other inline-blocks. The
* element has a container associated with it, and the container is going to be placed
* after the current paragraph if it fits in the text container.
*
* @param composeFrame the text container we're composing into
*/
protected function composeFloat(elem:ContainerFormattedElement,composeFrame:ContainerController):void
{
// Should get handled in derived class
CONFIG::debug { assert(false, "Floats are not supported in ComposeState"); }
}
/** Called when we are about to compose a line. Handler for derived classes to override default behavior. */
protected function startLine():void
{
// does nothing
}
/** Called when we are finished composing a line. Handler for derived classes to override default behavior. */
protected function endLine():void
{
// does nothing
}
private function composeBlockElement(elem:FlowGroupElement,absStart:int):Boolean
{
// Compose all the children, until all the containers are filled, or if we're on the last container, we've hit the stop compose text index
var idx:int;
if (_startComposePosition != 0)
{
idx = elem.findChildIndexAtPosition(_startComposePosition-absStart);
CONFIG::debug { assert(idx != -1,"Bad _startComposePosition to index in composeBlockElement"); }
absStart += elem.getChildAt(idx).parentRelativeStart;
}
else
idx = 0;
for (; idx < elem.numChildren && (absStart <= _stopComposePos || ! parcelList.atLast()); idx++)
{
var child:FlowElement = elem.getChildAt(idx);
var para:ParagraphElement = child as ParagraphElement;
if (para)
{
var rslt:Boolean = composeParagraphElement(para,absStart);
// we need to flush each TextBlock - this saves a lot of memory at the cost of performance during editing
// note that this is a nop on older players. only newer players implement flush
if (releaseLineCreationData)
para.releaseLineCreationData();
if (!rslt)
return false; // done
}
else if (child.display == FlowElementDisplayType.FLOAT)
{
composeFloat(ContainerFormattedElement(child),_parcelList.controller);
if (_parcelList.atEnd())
return false;
CONFIG::debug { assert(child.getAbsoluteStart() + child.textLength - _parcelList.controller.absoluteStart >= 0, "frame has negative composition"); }
}
else
{
if (!composeBlockElement(FlowGroupElement(child),absStart))
return false;
}
absStart += child.textLength;
}
return true;
}
// TODO: move somewhere reasonable
private static function doNothingOnParcelChange(newParcel:Parcel):void
{ }
/**
* Compose the flow into the text container. Starts at the root element,
* and composes elements until either there are no more elements, or the
* text container is full. It will compose only the lines which are
* marked invalid, so that existing lines that are unchanged are not
* recomposed.
*/
public function composeTextFlow(textFlow:TextFlow, composeToPosition:int, controllerEndIndex:int):int
{
_textFlow = textFlow;
_releaseLineCreationData = textFlow.configuration.releaseLineCreationData && Configuration.playerEnablesArgoFeatures;
// must setup _startController and _startComposePosition
initializeForComposer(textFlow.flowComposer, composeToPosition, controllerEndIndex);
_flowComposer = _textFlow.flowComposer as StandardFlowComposer;
_rootElement = textFlow;
_curElementOffset = 0;
_curElement = _rootElement.getFirstLeaf();
_curElementStart = 0; // current position in the text (start of current line)
_curParcel = null;
resetControllerBounds();
if (_startController != _flowComposer.getControllerAt(0))
{
var cacheNotify:Function = _parcelList.notifyOnParcelChange;
_parcelList.notifyOnParcelChange = doNothingOnParcelChange;
// skip parcels until the first one in startController
while(_parcelList.currentParcel.controller != _startController)
_parcelList.next();
_parcelList.notifyOnParcelChange = cacheNotify;
}
parcelHasChanged(_parcelList.currentParcel); // force start of composition acccounting initialization
composeInternal(_rootElement,0);
for (;;)
{
if (parcelList.atEnd())
{
parcelHasChanged(null); // force end of composition accounting for the parcel
break;
}
parcelList.next();
}
releaseParcelList(_parcelList);
_parcelList = null;
return _curElementStart + _curElementOffset; // Return last composed position
}
private function resetControllerBounds():void
{
_controllerLeft = TextLine.MAX_LINE_WIDTH;
_controllerTop = TextLine.MAX_LINE_WIDTH;
_controllerRight = -TextLine.MAX_LINE_WIDTH;
_controllerBottom = -TextLine.MAX_LINE_WIDTH;
}
/** Release line creation data during this compose */
protected function get releaseLineCreationData():Boolean
{ return _releaseLineCreationData; }
// Create new lines through composition. lines, wrap, etc.
protected function composeInternal(composeRoot:FlowGroupElement,absStart:int):void
{
composeBlockElement(composeRoot,absStart);
}
protected function composeParagraphElement(elem:ParagraphElement,absStart:int):Boolean
{
CONFIG::debug { assert(false,"MISSING OVERRIDE"); }
return false;
}
protected function composeParagraphElementIntoLines():Boolean
{
var curLine:TextFlowLine;
// loop creating lines
for (;;)
{
if (_parcelList.atEnd())
return false;
// Allow derived classes to do processing here
startLine();
// Get the next line
curLine = composeNextLine();
if (curLine == null)
return false;
var alignData:AlignData = calculateTextAlign(curLine, curLine.getTextLine());
/* {
for (var idx:int = 0; idx < curLine.textLine.atomCount; idx++)
{
trace(idx.toString()+": beginIndex: " + curLine.textLine.getAtomTextBlockBeginIndex(idx)+ " bidiLevel: "+ curLine.textLine.getAtomBidiLevel(idx) + " bounds: " + curLine.textLine.getAtomBounds(idx));
}
} */
if ((curLine.spaceBefore != 0 || _spaceCarried != 0) && !_parcelList.isColumnStart())
_parcelList.addTotalDepth(Math.max(curLine.spaceBefore, _spaceCarried));
_spaceCarried = 0;
_parcelList.addTotalDepth(curLine.height);
_curElementOffset += curLine.textLength;
// textLength is the first character in the next line
var textLine:TextLine = curLine.getTextLine();
var lineWidth:Number;
if (_parcelList.explicitLineBreaks)
{
var isRTL:Boolean = _curParaElement.computedFormat.direction == Direction.RTL;
textLine = curLine.getTextLine(true);
var lastAtom:int = textLine.atomCount - 1;
// If we're at the end of the paragraph, don't count the terminator
var endOfParagraph:Boolean = _curElementStart+_curElementOffset == _curParaStart + _curParaElement.textLength;
if (endOfParagraph && !isRTL)
--lastAtom; // can go negative if just the terminator. in that case use left/top of atom zero
var bounds:Rectangle = textLine.getAtomBounds(lastAtom >= 0 ? lastAtom : 0); // get rightmost atom bounds
lineWidth = (_blockProgression == BlockProgression.TB)
? (lastAtom >= 0 ? bounds.right : bounds.left)
: (lastAtom >= 0 ? bounds.bottom : bounds.top);
if (isRTL) // in right to left, get leftmost atom bounds, that has trailing space
{
// in RTL strip the width of the paragraph terminator from the front
bounds = textLine.getAtomBounds(lastAtom != 0 && endOfParagraph ? 1 : 0);
lineWidth -= (_blockProgression == BlockProgression.TB) ? bounds.left : bounds.top;
}
textLine.flushAtomData();
}
else
lineWidth = textLine.textWidth;
var rightSidePadding:Number = _curParaFormat.direction == Direction.LTR ? _curParaFormat.paragraphEndIndent : _curParaFormat.paragraphStartIndent;
var textIndent:Number = 0;
var rightSideIndent:Number = 0;
var leftSideIndent:Number = 0;
if (_curParaFormat.direction == Direction.RTL && (curLine.location & TextFlowLineLocation.FIRST))
{
// the textIndent isn't applied on left aligned paragraphs in measured RTL mode
// need to be careful because leftaligned paragraphs need to be exactly right coming out of this routine
if (alignData && (_blockProgression == BlockProgression.TB && !curLine.controller.measureWidth || _blockProgression == BlockProgression.RL && !curLine.controller.measureHeight))
rightSideIndent = _curParaFormat.textIndent;
}
var leftSidePadding:Number = _curParaFormat.direction == Direction.LTR ? _curParaFormat.paragraphStartIndent : _curParaFormat.paragraphEndIndent;
if (_curParaFormat.direction == Direction.LTR && (curLine.location & TextFlowLineLocation.FIRST))
{
// recording leftSideIndent is here because there is an extra alignment step for non-left aligned paragraphs
leftSideIndent = _curParaFormat.textIndent;
}
if (alignData)
{
alignData.rightSidePadding = rightSidePadding;
alignData.leftSidePadding = leftSidePadding;
alignData.lineWidth = lineWidth;
alignData.rightSideIndent = rightSideIndent;
alignData.leftSideIndent = leftSideIndent;
// trace("AlignData",alignData.leftSidePadding,alignData.rightSidePadding,alignData.lineWidth,alignData.leftSideIndent,alignData.rightSideIndent);
}
// extent from the left margin
var lineExtent:Number = lineWidth + leftSidePadding+leftSideIndent + rightSidePadding+rightSideIndent;
_contentLogicalExtent = Math.max(_contentLogicalExtent, lineExtent);
if (!alignData)
_contentCommittedExtent = Math.max(_contentCommittedExtent, lineExtent);
CONFIG::debug { assert(_parcelList.controller.textLength >= 0, "frame has negative composition"); }
if (_parcelList.atEnd())
return false;
endLine();
// 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 (_curElementOffset >= _curElement.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.
do{
_curElementOffset -= _curElement.textLength;
_curElementStart += _curElement.textLength;
if (_curElementStart == _curParaStart+_curParaElement.textLength)
{
_curElement = null;
break;
}
_curElement = _curElement.getNextLeaf();
CONFIG::debug { assert(_curElement && _curElement.getParagraph() == _curParaElement,"composeParagraphElement: bad textLength in TextLine"); }
} while (_curElementOffset >= _curElement.textLength || _curElement.textLength == 0 );
}
_spaceCarried = curLine.spaceAfter;
if (_curElement == null)
break;
}
return true;
}
protected function composeNextLine():TextFlowLine
{
CONFIG::debug { throw new Error("composeNextLine requires override"); }
return null;
}
// fills in _lineSlug
protected function fitLineToParcel(curLine:TextFlowLine, isNewLine:Boolean):TextFlowLine
{
// Try to place the line in the current parcel.
// get a zero height parcel. place the line there and then test if it still fits.
// if it doesn't place it in the new result parcel
// still need to investigate because the height used on the 2nd getLineSlug call may be too big.
for (;;)
{
if (_parcelList.getLineSlug(_candidateLineSlug,0))
break;
_parcelList.next();
if (_parcelList.atEnd())
return null;
_spaceCarried = 0;
}
curLine.setController(_parcelList.controller,_parcelList.columnIndex);
// If we are at the last parcel, we let text be clipped if that's specified in the configuration. At the point where no part of text can be accommodated, we go overset.
// If we are not at the last parcel, we let text flow to the next parcel instead of getting clipped.
var spaceBefore:Number = Math.max(curLine.spaceBefore, _spaceCarried);
for (;;)
{
finishComposeLine(curLine, _candidateLineSlug, isNewLine);
if (_parcelList.getLineSlug(_lineSlug, spaceBefore + (_parcelList.atLast() && _textFlow.configuration.overflowPolicy != OverflowPolicy.FIT_DESCENDERS ? curLine.height-curLine.ascent : curLine.height+curLine.descent)))
{
CONFIG::debug { assert(_parcelList.getComposeXCoord(_candidateLineSlug) == _parcelList.getComposeXCoord(_lineSlug) && _parcelList.getComposeYCoord(_candidateLineSlug) == _parcelList.getComposeYCoord(_lineSlug),"fitLineToParcel: slug mismatch"); }
break;
}
spaceBefore = curLine.spaceBefore;
for (;;)
{
_parcelList.next();
if (_parcelList.atEnd())
return null;
if (_parcelList.getLineSlug(_candidateLineSlug,0))
break;
}
curLine.setController(_parcelList.controller,_parcelList.columnIndex);
}
// check to see if we got a good line
return (_parcelList.getComposeWidth(_lineSlug) == curLine.outerTargetWidth) ? curLine : null;
}
protected function finishComposeLine(curLine:TextFlowLine, lineSlug:Rectangle, isNewLine:Boolean):void
{
var curTextLine:TextLine = curLine.getTextLine();
var lineHeight:Number = 0;
//replace X and Y with rise and run.
// rise - the offset within a line relative to block progressiong. For RL this is X, for TB Y
// run - the indentation of the line. For RL this is Y, TB X
var rise:Number = _blockProgression != BlockProgression.RL ? parcelList.getComposeYCoord(lineSlug) : _parcelList.getComposeXCoord(lineSlug);
var run:Number = _blockProgression != BlockProgression.RL ? parcelList.getComposeXCoord(lineSlug) : _parcelList.getComposeYCoord(lineSlug);
if (_curParaFormat.direction == Direction.LTR)
{
run += curLine.lineOffset;
}
else
{
run += curLine.outerTargetWidth-curLine.lineOffset-curLine.targetWidth;
if (curLine.outerTargetWidth == TextLine.MAX_LINE_WIDTH && curLine.location&TextFlowLineLocation.FIRST) // doing measurement ignore
{
run += curLine.paragraph.computedFormat.textIndent;
}
}
_curLineLeading = curLine.getLineLeading(_blockProgression,_curElement,_curElementStart);
_curLineLeadingModel = _curParaElement.getEffectiveLeadingModel();
var containerAttrs:ITextLayoutFormat = _parcelList.controller.computedFormat;
var baselineType:Object = BaselineOffset.LINE_HEIGHT;
if (_parcelList.isColumnStart())
{
// If we're at the top of the column, we need to check the container properties to see
// what the firstBaselineOffset should be. This tells us how to treat the line.
// However, when vertical alignment is center or bottom, ignore the firstBaselineOffset setting
// and treat them as the BaselineOffset.AUTO case
if (containerAttrs.firstBaselineOffset != BaselineOffset.AUTO && containerAttrs.verticalAlign != VerticalAlign.BOTTOM && containerAttrs.verticalAlign != VerticalAlign.MIDDLE)
{
baselineType = containerAttrs.firstBaselineOffset;
// The first line's offset is specified relative firstBaselineOffsetBasis, which used to be, but no longer is, a container-level property
// Now it is implicitly deduced based on the container-level locale in the following manner:
// IDEOGRAPHIC_BOTTOM for ja and zh locales (this is the same locale set for which the default LeadingModel is IDEOGRAPHIC_TOP_DOWN)
// ROMAN for all other locales
var firstBaselineOffsetBasis:String = LocaleUtil.leadingModel(containerAttrs.locale) == LeadingModel.IDEOGRAPHIC_TOP_DOWN ? flash.text.engine.TextBaseline.IDEOGRAPHIC_BOTTOM : flash.text.engine.TextBaseline.ROMAN;
lineHeight -= curTextLine.getBaselinePosition(firstBaselineOffsetBasis);
}
else
{
if (_curLineLeadingModel == LeadingModel.APPROXIMATE_TEXT_FIELD)
{
// Reinterpret AUTO when APPROXIMATE_TEXT_FIELD leading model is used.
// Align the "enhanced ascent" (an approximation of TextField's notion of ascent baseline,
// which differs from FTEs notion of the same by an amount equal to the line's descent) with the container top inset
lineHeight += Math.round(curTextLine.descent) + Math.round(curTextLine.ascent)
// Ensure Roman baseline will fall at an integer position. This is desirable for all leading models,
// but only APPROXIMATE_TEXT_FIELD requires it now. In a future release, this code can be moved below and lineX/lineY rounded off directly.
if (_blockProgression == BlockProgression.TB)
lineHeight = Math.round(rise + lineHeight) - rise;
else
lineHeight = rise - Math.round(rise - lineHeight);
baselineType = 0; // No further adjustments
}
else
{
// The AUTO case requires aligning line top to container top inset. This efect can be achieved by using firstBaselineOffset=ASCENT
// and firstBaselineOffsetBasis=ROMAN
baselineType = BaselineOffset.ASCENT;
if(curTextLine.hasGraphicElement)
{
var firstLineAdjustment:LeadingAdjustment = getLineAdjustmentForInline(curLine, _curLineLeadingModel, true);
if(firstLineAdjustment != null)
{
if(_blockProgression == BlockProgression.RL)
{
firstLineAdjustment.rise = -(firstLineAdjustment.rise);
}
_curLineLeading += firstLineAdjustment.leading;
rise += firstLineAdjustment.rise;
}
}
lineHeight -= curTextLine.getBaselinePosition(flash.text.engine.TextBaseline.ROMAN);
}
}
}
else
{
// handle space before by adjusting y position of line
if (curLine.spaceBefore != 0 || _spaceCarried != 0)
{
var spaceAdjust:Number = Math.max(curLine.spaceBefore, _spaceCarried);
rise += _blockProgression == BlockProgression.RL ? -spaceAdjust :spaceAdjust;
}
}
//getTextLineTypographicAscent
if (baselineType == BaselineOffset.ASCENT)
{
// CONFIG::debug { assert(_curElement == _textFlow.findLeaf(curLine.absoluteStart),"Bad _curElement"); }
CONFIG::debug { assert(_curElementStart == _textFlow.findLeaf(curLine.absoluteStart).getAbsoluteStart(), "Bad _curElementStart"); }
lineHeight += curLine.getLineTypographicAscent(_curElement,_curElementStart);
}
else
{
if (baselineType == BaselineOffset.LINE_HEIGHT)
{
if (_curLineLeadingModel == LeadingModel.APPROXIMATE_TEXT_FIELD)
{
// Position the "enhanced ascent" (see above) at a distance of leading from the previous line's descent
lineHeight += Math.round(_lastLineDescent) + Math.round(curTextLine.ascent) + Math.round(curTextLine.descent) + Math.round(_curLineLeading);
}
else if (_curLineLeadingModel == LeadingModel.ASCENT_DESCENT_UP)
{
lineHeight += _lastLineDescent + curTextLine.ascent + _curLineLeading;
}
else
{
// Leading direction is irrelevant for the first line. Treat it as (UP, UP)
// TODO-9/3/2008-It may be better to handle Middle/Last lines separately because we know that the previous line also belongs in the same para
var curLeadingDirectionUp:Boolean = _parcelList.isColumnStart() ? true : ParagraphElement.useUpLeadingDirection(_curLineLeadingModel);
var prevLeadingDirectionUp:Boolean = _parcelList.isColumnStart() || _lastLineLeadingModel == "" ? true :
ParagraphElement.useUpLeadingDirection(_lastLineLeadingModel);
var prevLineFirstElement:FlowLeafElement;
if (curLeadingDirectionUp)
{
//TODO-9/12/2008-The above behavior is the InDesign behavior but raises some questions about selection shapes.
//Should selection code associate leading with the influencing line? That would be weird. InDesign only
//supports alternate leading directions in the J feature set, where leading is never included in selection,
//so this question does not arise. We take the unambiguous route: ignore leading DOWN at the end of a para
lineHeight += _curLineLeading;
}
else
{
if (!prevLeadingDirectionUp)
{
// Same leading directions; use previous line's leading setting.
lineHeight += _lastLineLeading;
}
else
{
// Make NO leading adjustments. Set lines solid.
lineHeight += _lastLineDescent + curTextLine.ascent;
}
}
}
}
else
lineHeight += Number(baselineType); // fixed offset
}
//don't know why, but ascent only needs to be removed from horizontal text. Hmm, that seems
//odd to me - gak 12.15.09
rise += _blockProgression == BlockProgression.RL ? -(lineHeight) : lineHeight - curTextLine.ascent;
//baselineType will be BaselineOffset.ASCENT for fixed leading
if(curTextLine.hasGraphicElement && baselineType != BaselineOffset.ASCENT)
{
var adjustment:LeadingAdjustment = getLineAdjustmentForInline(curLine, _curLineLeadingModel, false);
if(adjustment != null)
{
if(_blockProgression == BlockProgression.RL)
{
adjustment.rise = -(adjustment.rise);
}
_curLineLeading += adjustment.leading;
rise += adjustment.rise;
}
}
if(_blockProgression == BlockProgression.TB)
curLine.setXYAndHeight(run,rise,lineHeight);
else
curLine.setXYAndHeight(rise,run,lineHeight);
if(isNewLine)
curLine.createAdornments(_blockProgression,_curElement,_curElementStart);
}
// Calculate the text alignment of the current line we're composing. If alignment is required, the adjustment will be made in
// applyTextAlign, after we've calculated the width of the parcel (it may be based on measurement).
// TODO: optimization possibility - do the alignment here when not doing measurement
private function calculateTextAlign(curLine:TextFlowLine, curTextLine:TextLine):AlignData
{
// Adjust the coordinates of the line for center/right. The line is always left aligned. TextBlock handles justified cases
// If we're on the last line of a justified paragraph, use the textAlignLast value
var textAlignment:String = _curParaFormat.textAlign;
if (textAlignment == TextAlign.JUSTIFY && _curParaFormat.textAlignLast != null)
{
var location:int = curLine.location;
if (location == TextFlowLineLocation.LAST || location == TextFlowLineLocation.ONLY)
textAlignment = _curParaFormat.textAlignLast;
}
switch(textAlignment)
{
case TextAlign.START:
textAlignment = (_curParaFormat.direction == Direction.LTR) ? TextAlign.LEFT : TextAlign.RIGHT;
break;
case TextAlign.END:
textAlignment = (_curParaFormat.direction == Direction.LTR) ? TextAlign.RIGHT : TextAlign.LEFT;
break;
}
var createAlignData:Boolean = textAlignment == TextAlign.CENTER || textAlignment == TextAlign.RIGHT;
// in argo lines that have tabs must be either START or JUSTIFY
if (Configuration.playerEnablesArgoFeatures)
{
if (curTextLine["hasTabs"])
{
if (_curParaFormat.direction == Direction.LTR)
{
createAlignData = false; // don't align it - let it be left align
}
else
{
createAlignData = true;
textAlignment = TextAlign.RIGHT;
}
}
}
if (createAlignData)
{
var alignData:AlignData = new AlignData();
alignData.textLine = curTextLine;
alignData.center = (textAlignment == TextAlign.CENTER);
if (!_alignLines)
_alignLines = [];
_alignLines.push(alignData);
return alignData;
}
return null;
}
private function applyTextAlign(effectiveParcelWidth:Number):void
{
var textLine:TextLine;
var line:TextFlowLine;
var alignData:AlignData;
var coord:Number;
var delta:Number;
var adjustedLogicalRight:Number;
var extraSpace:Number;
var leftSideGap:Number;
var rightSideGap:Number;
if (_blockProgression == BlockProgression.TB)
{
for each (alignData in _alignLines)
{
textLine = alignData.textLine;
rightSideGap = alignData.rightSidePadding;
leftSideGap = alignData.leftSidePadding;
leftSideGap += alignData.leftSideIndent;
rightSideGap += alignData.rightSideIndent;
line = textLine.userData as TextFlowLine;
extraSpace = effectiveParcelWidth - leftSideGap - rightSideGap - textLine.textWidth;
delta = leftSideGap + (alignData.center ? extraSpace / 2 : extraSpace);
coord = _curParcel.left + delta;
if (line)
line.x = coord;
textLine.x = coord;
adjustedLogicalRight = alignData.lineWidth + coord + Math.max(rightSideGap, 0);
_parcelRight = Math.max(adjustedLogicalRight , _parcelRight);
}
}
else
{
for each (alignData in _alignLines)
{
textLine = alignData.textLine;
rightSideGap = alignData.rightSidePadding;
leftSideGap = alignData.leftSidePadding;
leftSideGap += alignData.leftSideIndent;
rightSideGap += alignData.rightSideIndent;
line = textLine.userData as TextFlowLine;
extraSpace = effectiveParcelWidth - leftSideGap - rightSideGap - textLine.textWidth;
delta = leftSideGap + (alignData.center ? extraSpace / 2 : extraSpace);
coord = _curParcel.top + delta;
if (line)
line.y = coord;
textLine.y = coord;
adjustedLogicalRight = alignData.lineWidth + coord + Math.max(rightSideGap, 0);
_parcelBottom = Math.max(adjustedLogicalRight,_parcelBottom);
}
}
_alignLines.length = 0;
}
protected function commitLastLineState(curLine:TextFlowLine):void
{
// Remember leading-related state that may be used for laying out the next line
_lastLineDescent = curLine.descent;
_lastLineLeading = _curLineLeading;
_lastLineLeadingModel = _curLineLeadingModel;
}
protected function doVerticalAlignment(canVerticalAlign:Boolean,nextParcel:Parcel):Boolean
{
// stub for required override
CONFIG::debug { assert(false, "override in derived class"); }
return false;
}
protected function finalParcelAdjustment(controller:ContainerController):void
{
// stub for required override
CONFIG::debug { assert(false, "finalParcelAdjustment missing override in derived class"); }
}
protected function finishParcel(controller:ContainerController,nextParcel:Parcel):Boolean
{
if (_curParcelStart == _curElementStart+_curElementOffset) // empty parcel -- nothing composed into it
{
CONFIG::debug { assert(_contentLogicalExtent == 0,"bad contentlogicalextent on empty container"); }
return false;
}
// We're only going to align the lines in measurement mode if there's only one parcel
var doTextAlign:Boolean = (_alignLines && _alignLines.length > 0);
// Figure out the contents bounds information for the parcel we just finished composing
// Content logical height is parcel depth, plus descenders of last line
var totalDepth:Number = _parcelList.totalDepth;
if (_textFlow.configuration.overflowPolicy == OverflowPolicy.FIT_DESCENDERS && !isNaN(_lastLineDescent))
totalDepth += _lastLineDescent;
// Initialize the parcel bounds
// note we can later optimize away the adjustements
if (_blockProgression == BlockProgression.TB)
{
_parcelLeft = _curParcel.left;
_parcelTop = _curParcel.top;
_parcelRight = _contentCommittedExtent+_curParcel.left;
_parcelBottom = totalDepth+_curParcel.top;
}
else
{
// Push the values up to the controller running min/max, if they are bigger
_parcelLeft = _curParcel.right-totalDepth;
_parcelTop = _curParcel.top;
_parcelRight = _curParcel.right;
_parcelBottom = _contentCommittedExtent+_curParcel.top;
}
if (doTextAlign)
{
var effectiveParcelWidth:Number;
if (_blockProgression == BlockProgression.TB)
effectiveParcelWidth = controller.measureWidth ? _contentLogicalExtent : _curParcel.width;
else
effectiveParcelWidth = controller.measureHeight ? _contentLogicalExtent : _curParcel.height;
applyTextAlign(effectiveParcelWidth);
}
// If we're measuring, then don't do vertical alignment
var canVerticalAlign:Boolean = false;
if (_blockProgression == BlockProgression.TB)
{
if (!controller.measureHeight && (!_curParcel.fitAny || _curElementStart + _curElementOffset >= _textFlow.textLength))
canVerticalAlign = true;
}
else
{
if (!controller.measureWidth && (!_curParcel.fitAny || _curElementStart + _curElementOffset >= _textFlow.textLength))
canVerticalAlign = true;
}
// need to always call this function because internal variables may need resetting
if (doVerticalAlignment(canVerticalAlign,nextParcel))
doTextAlign = true;
// This last adjustment is for two issues
// 1) inline graphics that extend above the top (any ILGS I expect)
// 2) negative first line indents (stil a worry here?)
// If neither of these are present it can be skipped - TODO optimization
// trace("BEF finalParcelAdjustment",_parcelLeft,_parcelRight,_parcelTop,_parcelBottom);
finalParcelAdjustment(controller);
// trace("AFT finalParcelAdjustment",_parcelLeft,_parcelRight,_parcelTop,_parcelBottom);
_contentLogicalExtent = 0;
_contentCommittedExtent = 0;
return true;
}
/** apply vj and adjust the parcel bounds */
protected function applyVerticalAlignmentToColumn(controller:ContainerController,vjType:String,lines:Array,beginIndex:int,numLines:int):void
{
var firstLine:IVerticalJustificationLine = lines[beginIndex];
var lastLine:IVerticalJustificationLine = lines[beginIndex+numLines-1]
var firstLineCoord:Number;
var lastLineCoord:Number
if (_blockProgression == BlockProgression.TB)
{
firstLineCoord = firstLine.y;
lastLineCoord = lastLine.y;
}
else
{
firstLineCoord = firstLine.x;
lastLineCoord = lastLine.x;
}
VerticalJustifier.applyVerticalAlignmentToColumn(controller,vjType,lines,beginIndex,numLines);
if (_blockProgression == BlockProgression.TB)
{
_parcelTop += firstLine.y-firstLineCoord;
_parcelBottom += lastLine.y-lastLineCoord;
}
else
{
_parcelRight += firstLine.x-firstLineCoord;
_parcelLeft += lastLine.x-lastLineCoord;
}
}
private function finishController(controller:ContainerController):void
{
var controllerTextLength:int = _curElementStart + _curElementOffset - controller.absoluteStart;
if (controllerTextLength != 0)
{
// Leave room for the padding. If the content overlaps the padding, don't count the padding twice.
var paddingLeft:Number = controller.effectivePaddingLeft;
var paddingTop:Number = controller.effectivePaddingTop;
var paddingRight:Number = controller.effectivePaddingRight;
var paddingBottom:Number = controller.effectivePaddingBottom;
if (_blockProgression == BlockProgression.TB)
{
if (_controllerLeft > 0)
{
if (_controllerLeft < paddingLeft)
_controllerLeft = 0;
else
_controllerLeft -= paddingLeft;
}
if (_controllerTop > 0)
{
if (_controllerTop < paddingTop)
_controllerTop = 0;
else
_controllerTop -= paddingTop;
}
if (isNaN(controller.compositionWidth))
_controllerRight += paddingRight;
else if (_controllerRight < controller.compositionWidth)
{
if (_controllerRight > controller.compositionWidth - paddingRight)
_controllerRight = controller.compositionWidth;
else
_controllerRight += paddingRight;
}
_controllerBottom += paddingBottom;
}
else
{
_controllerLeft -= paddingLeft;
if (_controllerTop > 0)
{
if (_controllerTop < paddingTop)
_controllerTop = 0;
else
_controllerTop -= paddingTop;
}
if (_controllerRight < 0)
{
if (_controllerRight > -paddingRight)
{
_controllerRight = 0;
}
else
_controllerRight += paddingRight;
}
if (isNaN(controller.compositionHeight))
_controllerBottom += paddingBottom;
else if (_controllerBottom < controller.compositionHeight)
{
if (_controllerBottom > controller.compositionHeight - paddingBottom)
_controllerBottom = controller.compositionHeight;
else
_controllerBottom += paddingBottom;
}
}
controller.setContentBounds(_controllerLeft, _controllerTop, _controllerRight-_controllerLeft, _controllerBottom-_controllerTop);
}
else
controller.setContentBounds(0,0,0,0);
controller.setTextLength(controllerTextLength);
}
private function clearControllers(oldController:ContainerController, newController:ContainerController):void
{
// any controller between oldController and up to and including newController gets cleared
var firstToClear:int = oldController ? _flowComposer.getControllerIndex(oldController)+1 : 0;
var lastToClear:int = newController ? _flowComposer.getControllerIndex(newController) : _flowComposer.numControllers-1;
while (firstToClear <= lastToClear)
{
var controllerToClear:ContainerController = ContainerController(_flowComposer.getControllerAt(firstToClear));
controllerToClear.setContentBounds(0, 0, 0, 0);
controllerToClear.setTextLength(0);
firstToClear++;
}
}
/** This is called when the parcel has changed
* @param oldParcel - the parcel we had before (you can get the new parcel from the parcel list)
*/
protected function parcelHasChanged(newParcel:Parcel):void
{
var oldController:ContainerController = _curParcel ? ContainerController(_curParcel.controller) : null;
var newController:ContainerController = newParcel ? ContainerController(newParcel.controller) : null;
/* if (newParcel)
trace("parcelHasChanged newParcel: ",newParcel.clone().toString()); */
if (_curParcel != null)
{
if (finishParcel(oldController,newParcel))
{
if (_parcelLeft < _controllerLeft)
_controllerLeft = _parcelLeft;
if (_parcelRight > _controllerRight)
_controllerRight = _parcelRight;
if (_parcelTop < _controllerTop)
_controllerTop = _parcelTop;
if (_parcelBottom > _controllerBottom)
_controllerBottom = _parcelBottom;
}
}
// update parcel data
if (oldController != newController) // we're going on to the next controller in the chain
{
if (oldController)
finishController(oldController);
resetControllerBounds();
if (_flowComposer.numControllers > 1)
{
if (oldController == null && _startController)
clearControllers(_startController, newController);
else
clearControllers(oldController, newController);
}
// Parcel list will set totalDepth to newController's paddingTop
}
_curParcel = newParcel;
_curParcelStart = _curElementStart;
}
/** @private */
private function getLineAdjustmentForInline(curLine:TextFlowLine, curLeadingDir:String, isFirstLine:Boolean):LeadingAdjustment
{
var adjustment:LeadingAdjustment = null;
var curTextLine:TextLine = curLine.getTextLine();
var para:ParagraphElement = curLine.paragraph;
var flowElem:FlowLeafElement = _curElement; //the first element included in this line
var curPos:int = flowElem.getAbsoluteStart();
var largestPointSize:Number = flowElem.getEffectiveFontSize();
var largestImg:Number = 0;
//walk
while(flowElem && curPos < curLine.absoluteStart + curLine.textLength)
{
if(curPos >= curLine.absoluteStart || curPos + flowElem.textLength >= curLine.absoluteStart)
{
if(flowElem is InlineGraphicElement)
{
var inlineImg:InlineGraphicElement = flowElem as InlineGraphicElement;
//we can ignore TCY for leading adjustments
if(!(_blockProgression == BlockProgression.RL && (flowElem.parent is TCYElement)))
{
//if the largest found img is smaller than the current image, we need new data
if(largestImg < inlineImg.getEffectiveFontSize())
{
largestImg = inlineImg.getEffectiveFontSize();
//only get this if the img is as large or larger than the largest found text
if(largestImg >= largestPointSize)
{
largestImg = largestImg;
var domBaseline:String = flowElem.computedFormat.dominantBaseline;
if(domBaseline == FormatValue.AUTO)
domBaseline = LocaleUtil.dominantBaseline(para.computedFormat.locale);
//we are only making the adjustment for ideo-center, all others are to be ignored...
if(domBaseline == TextBaseline.IDEOGRAPHIC_CENTER)
{
var elemLeading:Number = TextLayoutFormat.lineHeightProperty.computeActualPropertyValue(inlineImg.computedFormat.lineHeight, inlineImg.getEffectiveFontSize());
var curAdjustment:LeadingAdjustment = calculateLinePlacementAdjustment(curTextLine, domBaseline, curLeadingDir, inlineImg, isFirstLine);
if(!adjustment || Math.abs(curAdjustment.rise) > Math.abs(adjustment.rise) || Math.abs(curAdjustment.leading) > Math.abs(adjustment.leading))
{
if(adjustment)
{
adjustment.rise = curAdjustment.rise;
adjustment.leading = curAdjustment.leading;
}
else
adjustment = curAdjustment;
}
}
}
}
}
}
else
{
var tempSize:Number = flowElem.getEffectiveFontSize();
if(largestPointSize <= tempSize)
{
largestPointSize = tempSize;
}
//if the largest image is smaller than this element, zero out the adjustment
if(adjustment && largestImg < largestPointSize)
{
adjustment.leading = 0;
adjustment.rise = 0;
}
}
}
//advance the position and get the next element
curPos += flowElem.textLength;
flowElem = flowElem.getNextLeaf(para);
}
return adjustment;
}
public function get swfContext():ISWFContext
{
var composerContext:ISWFContext = _flowComposer.swfContext;
return composerContext ? composerContext : GlobalSWFContext.globalSWFContext;
}
/** @private */
private function calculateLinePlacementAdjustment(curTextLine:TextLine, domBaseline:String, curLeadingDir:String, inlineImg:InlineGraphicElement, isFirstLine:Boolean):LeadingAdjustment
{
var curAdjustment:LeadingAdjustment = new LeadingAdjustment();
//get the leading height for the img
var imgHeight:Number = TextLayoutFormat.lineHeightProperty.computeActualPropertyValue(inlineImg.computedFormat.lineHeight, inlineImg.getEffectiveFontSize());
//get the leading as if the line contains no imgs. We'll need this to adjust the total adjustments
var lineLeading:Number = TextLayoutFormat.lineHeightProperty.computeActualPropertyValue(inlineImg.computedFormat.lineHeight, curTextLine.textHeight)
//this is a redundant check, but will be needed in the future, so we're leaving it in. - gak 12.16.09
if(domBaseline == TextBaseline.IDEOGRAPHIC_CENTER)
{
if(!isFirstLine)
{
//for non-first lines, we want to offset the rise of the line
curAdjustment.rise += (imgHeight - lineLeading)/2;
}
else
{
//for the first line, the offset will be right, but hte leading wrong.
curAdjustment.leading -= (imgHeight - lineLeading)/2;
}
}
return curAdjustment;
}
}
}
import flash.text.engine.TextLine;
import flashx.textLayout.compose.ISWFContext;
import flashx.textLayout.debug.Debugging;
import flashx.textLayout.tlf_internal;
use namespace tlf_internal;
class AlignData
{
public var textLine:TextLine;
public var lineWidth:Number;
public var leftSidePadding:Number;
public var rightSidePadding:Number;
public var center:Boolean;
public var leftSideIndent:Number;
public var rightSideIndent:Number;
}
class GlobalSWFContext implements ISWFContext
{
static public const globalSWFContext:GlobalSWFContext = new GlobalSWFContext();
public function GlobalSWFContext()
{ }
public function callInContext(fn:Function, thisArg:Object, argsArray:Array, returns:Boolean=true):*
{
CONFIG::debug
{
var rslt:*
try
{
if (returns)
rslt = fn.apply(thisArg, argsArray);
else
fn.apply(thisArg, argsArray);
if (thisArg)
{
var traceArgs:Array;
// later make this table driven
if (thisArg.hasOwnProperty("createTextLine") && fn == thisArg["createTextLine"])
{
traceArgs = [rslt,thisArg,"createTextLine"]
traceArgs.push.apply(traceArgs, argsArray);
Debugging.traceFTECall.apply(null,traceArgs);
}
else if (thisArg.hasOwnProperty("recreateTextLine") && fn == thisArg["recreateTextLine"])
{
traceArgs = [rslt,thisArg,"recreateTextLine"]
traceArgs.push.apply(traceArgs, argsArray);
Debugging.traceFTECall.apply(null,traceArgs);
}
}
}
catch(e:Error)
{
// trace(e);
throw(e);
}
return rslt;
}
CONFIG::release
{
if (returns)
return fn.apply(thisArg, argsArray);
fn.apply(thisArg, argsArray);
}
}
}
class LeadingAdjustment
{
public var rise:Number = 0;
public var leading:Number = 0;
public var lineHeight:Number = 0;
}