blob: 220e6c83ee575cab47d59f1be03158484f320d97 [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.factory
{
import flash.display.Shape;
import flash.display.Sprite;
import flash.geom.Rectangle;
import flash.text.engine.TextBlock;
import flash.text.engine.TextLine;
import flash.text.engine.TextLineValidity;
import flashx.textLayout.compose.FloatCompositionData;
import flashx.textLayout.compose.IFlowComposer;
import flashx.textLayout.compose.SimpleCompose;
import flashx.textLayout.compose.StandardFlowComposer;
import flashx.textLayout.container.ContainerController;
import flashx.textLayout.container.ScrollPolicy;
import flashx.textLayout.debug.Debugging;
import flashx.textLayout.debug.assert;
import flashx.textLayout.elements.FlowGroupElement;
import flashx.textLayout.elements.FlowLeafElement;
import flashx.textLayout.elements.OverflowPolicy;
import flashx.textLayout.elements.ParagraphElement;
import flashx.textLayout.elements.SpanElement;
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.compose.TextFlowLine;
import flashx.textLayout.formats.BlockProgression;
import flashx.textLayout.formats.Float;
import flashx.textLayout.formats.ITextLayoutFormat;
import flashx.textLayout.formats.LineBreak;
import flashx.textLayout.formats.TextLayoutFormat;
import flashx.textLayout.tlf_internal;
use namespace tlf_internal;
/**
* The TextFlowTextLineFactory class provides a simple way to create TextLines for displaying text from a text flow.
*
* <p>The text lines are static and created fit in a single bounding rectangle, but can have multiple paragraphs and formats as well as
* inline graphics. To create TextLine objects directly from a string, use StringTextLineFactory.</p>
*
* <p><b>Note:</b> When using inline graphics, the <code>source</code> property of the InlineGraphicElement object
* must either be an instance of a DisplayObject or a Class object representing an embedded asset.
* URLRequest objects cannot be used. The width and height of the inline graphic at the time the line
* is created is used to compose the flow. </p>
*
* @includeExample examples\TextFlowTextLineFactory_example.as
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flashx.textLayout.elements.TextFlow TextFlow
* @see flashx.textLayout.factory.StringTextLineFactory StringTextLineFactory
*/
public class TextFlowTextLineFactory extends TextLineFactoryBase
{
/**
* Creates a TextFlowTextLineFactory object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function TextFlowTextLineFactory()
{
super();
}
/**
* Creates TextLine objects from the specified text flow.
*
* <p>The text lines are composed to fit the bounds assigned to the <code>compositionBounds</code> property.
* As each line is created, the factory calls the function specified in the
* <code>callback</code> parameter. This function is passed the TextLine object and
* is responsible for displaying the line. If a line has a background color, the factory also calls the
* callback function with a Shape object containing a rectangle of the background color.</p>
*
* <p>Note that the scroll policies of the factory will control how many lines are generated.</p>
*
* @param callback function to call with each generated TextLine object.
* The callback will be called with a Shape object representing any background color (if present),
* and with TextLine objects for the text.
* @param textFlow The TextFlow from which the lines are created.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
*/
public function createTextLines(callback:Function,textFlow:TextFlow):void
{
var saved:SimpleCompose = TextLineFactoryBase.beginFactoryCompose();
try
{
createTextLinesInternal(callback,textFlow);
}
finally
{
textFlow.changeFlowComposer(null,false);
_factoryComposer._lines.splice(0);
if (_pass0Lines)
_pass0Lines.splice(0);
TextLineFactoryBase.endFactoryCompose(saved);
}
}
private function createTextLinesInternal(callback:Function,textFlow:TextFlow):void
{
var measureWidth:Boolean = isNaN(compositionBounds.width);
var bp:String = textFlow.computedFormat.blockProgression;
var helper:IFlowComposer = createFlowComposer();
helper.swfContext = swfContext;
helper.addController(containerController);
textFlow.flowComposer = helper;
textFlow.clearBackgroundManager();
// this assertion is for TCM. It's valid in other cases to use the factory but Links won't work and graphics may not load properly
CONFIG::debug { assert(!this.hasOwnProperty("tcm") || textFlow.mustUseComposer() == false,"Factory composing in TCM when interaction needed"); }
_isTruncated = false;
// compose
containerController.setCompositionSize(compositionBounds.width, compositionBounds.height);
containerController.verticalScrollPolicy = truncationOptions ? ScrollPolicy.OFF : verticalScrollPolicy;
containerController.horizontalScrollPolicy = truncationOptions ? ScrollPolicy.OFF : horizontalScrollPolicy;
textFlow.normalize();
textFlow.applyUpdateElements(true);
helper.compose();
// Need truncation if all the following are true
// - truncation options exist
// - content doesn't fit
if (truncationOptions && !doesComposedTextFit(truncationOptions.lineCountLimit, textFlow.textLength, bp))
{
_isTruncated = true;
var somethingFit:Boolean = false; // were we able to fit something?
computeLastAllowedLineIndex (truncationOptions.lineCountLimit);
if (_truncationLineIndex >= 0)
{
// Create a span for the truncation indicator
var truncationIndicatorSpan:SpanElement = new SpanElement();
truncationIndicatorSpan.text = truncationOptions.truncationIndicator;
truncationIndicatorSpan.id = "truncationIndicator"; // prevents merging with other spans
if (truncationOptions.truncationIndicatorFormat)
truncationIndicatorSpan.format = truncationOptions.truncationIndicatorFormat;
var hostFormat:ITextLayoutFormat = textFlow.hostFormat;
// Initial truncation position: end of the last allowed line
var line:TextLine = _factoryComposer._lines[_truncationLineIndex] as TextLine;
var truncateAtCharPosition:int = line.userData + line.rawTextLength;
// Save off the original lines: used in getNextTruncationPosition
// Note that for the original lines to be valid when used, the containing text block should not be modified
// Cloning the text flow before modifying it ensures that
if (!_pass0Lines)
_pass0Lines = new Array();
_pass0Lines = _factoryComposer.swapLines(_pass0Lines);
// The following loop executes repeatedly composing text until it fits
// In each iteration, an atoms's worth of characters of original content is dropped
do
{
// Clone the part of the flow before the truncation position
textFlow = textFlow.deepCopy(0, truncateAtCharPosition) as TextFlow;
// TODO-2/18/2009-deepCopy does not copy hostTextLayoutFormat
if (hostFormat)
textFlow.hostFormat = hostFormat;
// Find a parent for the truncation span
var parent:FlowGroupElement;
var lastLeaf:FlowLeafElement = textFlow.getLastLeaf();
if (lastLeaf)
{
parent = lastLeaf.parent;
// Set format to match the leaf if none specified
if (!truncationOptions.truncationIndicatorFormat)
truncationIndicatorSpan.format = lastLeaf.format;
}
else
{
parent = new ParagraphElement();
textFlow.addChild(parent);
}
// Append the truncation span (after severing it from the previous flow)
if (truncationIndicatorSpan.parent)
truncationIndicatorSpan.parent.removeChild(truncationIndicatorSpan);
parent.addChild(truncationIndicatorSpan);
textFlow.flowComposer = helper;
textFlow.normalize();
helper.compose();
if (doesComposedTextFit(truncationOptions.lineCountLimit, textFlow.textLength, bp))
{
somethingFit = true;
break;
}
if (truncateAtCharPosition == 0)
break; // no original content left to make room for truncation indicator
// Try again by truncating at the beginning of the preceding atom
truncateAtCharPosition = getNextTruncationPosition(truncateAtCharPosition, true);
} while (true);
}
if (_truncatedTextFlowCallback != null)
_truncatedTextFlowCallback (somethingFit ? textFlow : null);
if (!somethingFit)
_factoryComposer._lines.splice(0); // return no lines
}
var xadjust:Number = compositionBounds.x;
// toptobottom sets zero to the right edge - adjust the locations
var controllerBounds:Rectangle = containerController.getContentBounds();
if (bp == BlockProgression.RL)
xadjust += (measureWidth ? controllerBounds.width : compositionBounds.width);
// apply x and y adjustment to the bounds
controllerBounds.left += xadjust;
controllerBounds.right += xadjust;
controllerBounds.top += compositionBounds.y;
controllerBounds.bottom += compositionBounds.y;
if (textFlow.backgroundManager)
processBackgroundColors(textFlow,callback,xadjust,compositionBounds.y,containerController.compositionWidth,containerController.compositionHeight);
callbackWithTextLines(callback,xadjust,compositionBounds.y);
setContentBounds(controllerBounds);
containerController.clearCompositionResults();
}
/** @private - documented in base class */
override protected function callbackWithTextLines(callback:Function,delx:Number,dely:Number):void
{
super.callbackWithTextLines(callback, delx, dely);
// Handle floats and inlines as well
var numFloats:int = containerController.numFloats;
for (var i:int = 0; i < numFloats; ++i)
{
var floatInfo:FloatCompositionData = containerController.getFloatAt(i);
var inlineHolder:Sprite = new Sprite(); // NO PMD
inlineHolder.alpha = floatInfo.alpha;
if (floatInfo.matrix)
inlineHolder.transform.matrix = floatInfo.matrix;
inlineHolder.x += floatInfo.x;
inlineHolder.y += floatInfo.y;
inlineHolder.addChild(floatInfo.graphic);
if (floatInfo.floatType == Float.NONE)
floatInfo.parent.addChild(inlineHolder);
else
{
inlineHolder.x += delx;
inlineHolder.y += dely;
callback(inlineHolder);
}
}
}
/** @private
* Test hook: Sets a callback function for getting the truncated text flow.
* This function is only called if truncation is performed. It takes a single parameter which will have the following value:
* null, if nothing fits
* A text flow representing the truncated text (containing inital text from the original text flow followed by the truncation indicator), otherwise
*/
tlf_internal function set truncatedTextFlowCallback(val:Function):void
{ _truncatedTextFlowCallback = val; }
private var _truncatedTextFlowCallback:Function;
}
}