blob: 8a5da0f30b59033452efd5917b9096dd9b879f1f [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.
//
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
// This file contains content from Ethan Brand by Nathaniel Hawthorne,
// now in the public domain.
//
////////////////////////////////////////////////////////////////////////////////
package UnitTest.Tests
{
import UnitTest.ExtendedClasses.VellumTestCase;
import UnitTest.Fixtures.MeasureConstants;
import UnitTest.Fixtures.TestConfig;
import UnitTest.Validation.BoundsChecker;
import flash.display.Graphics;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.geom.Rectangle;
import flashx.textLayout.compose.StandardFlowComposer;
import flashx.textLayout.container.ContainerController;
import flashx.textLayout.container.ScrollPolicy;
import flashx.textLayout.conversion.TextConverter;
import flashx.textLayout.edit.EditManager;
import flashx.textLayout.edit.IEditManager;
import flashx.textLayout.elements.InlineGraphicElementStatus;
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.events.StatusChangeEvent;
import flashx.textLayout.factory.StringTextLineFactory;
import flashx.textLayout.factory.TextFlowTextLineFactory;
import flashx.textLayout.formats.BlockProgression;
import flashx.textLayout.formats.Direction;
import flashx.textLayout.formats.TextLayoutFormat;
import mx.containers.Canvas;
import org.flexunit.asserts.assertTrue;
import org.flexunit.asserts.fail;
[TestCase(order=16)]
[RunWith("org.flexunit.runners.Parameterized")]
public class BoundsAndAlignmentTest extends VellumTestCase implements IEventDispatcher
{
// Creation Types
private static const USE_FLOW:String = "textFlow";
private static const USE_FACTORY_FLOW:String = "factoryTF";
private static var textAlignArray:Array = ["left", "center", "right", "start", "end" ];
private static var verticalAlignArray:Array = ["top", "middle", "bottom"];
private static var stringFactory:StringTextLineFactory = null;
private static var textFlowFactory:TextFlowTextLineFactory = null;
private static var labelFactory:StringTextLineFactory = null;
public static var data:Array = [
[
{measureType: MeasureConstants.MEASURE_WIDTH, blockProgression: BlockProgression.TB, direction: Direction.LTR}
],
[
{measureType: MeasureConstants.MEASURE_WIDTH, blockProgression: BlockProgression.TB, direction: Direction.RTL}
],
[
{measureType: MeasureConstants.MEASURE_WIDTH, blockProgression: BlockProgression.RL, direction: Direction.LTR}
],
[
{measureType: MeasureConstants.MEASURE_WIDTH, blockProgression: BlockProgression.RL, direction: Direction.RTL}
],
[
{measureType: MeasureConstants.MEASURE_HEIGHT, blockProgression: BlockProgression.TB, direction: Direction.LTR}
],
[
{measureType: MeasureConstants.MEASURE_HEIGHT, blockProgression: BlockProgression.TB, direction: Direction.RTL}
],
[
{measureType: MeasureConstants.MEASURE_HEIGHT, blockProgression: BlockProgression.RL, direction: Direction.LTR}
],
[
{measureType: MeasureConstants.MEASURE_HEIGHT, blockProgression: BlockProgression.RL, direction: Direction.RTL}
],
[
{measureType: MeasureConstants.MEASURE_BOTH, blockProgression: BlockProgression.TB, direction: Direction.LTR}
],
[
{measureType: MeasureConstants.MEASURE_BOTH, blockProgression: BlockProgression.TB, direction: Direction.RTL}
],
[
{measureType: MeasureConstants.MEASURE_BOTH, blockProgression: BlockProgression.RL, direction: Direction.LTR}
],
[
{measureType: MeasureConstants.MEASURE_BOTH, blockProgression: BlockProgression.RL, direction: Direction.RTL}
]
];
private var width:Number = 100;
private var height:Number = 100;
private var _blockProgression:String;
private var _direction:String;
private var _creationType:String;
private var _lineBreak:String;
private var _measureType:String;
private var eventDispatcher:EventDispatcher;
// bounds and format of last sprite for comparison function
private var compareBounds:Rectangle;
private var tFlowBounds:Rectangle;
private var notReadyGraphicsCount:int;
private var scrollPolicy:String = ScrollPolicy.ON;
private var sprite:Sprite;
private var testCanvas:Canvas;
private var editManager:IEditManager;
public function BoundsAndAlignmentTest()
{
super("", "BoundsAndAlignmentTest", TestConfig.getInstance(), null);
eventDispatcher = new EventDispatcher();
editManager = new EditManager();
//reset containerType to avoid assert in tearDown
containerType = "custom";
_blockProgression = TestConfig.getInstance().writingDirection[0];
_direction = TestConfig.getInstance().writingDirection[1];
//reset ID to include more variables
TestID = TestID + ":" + _measureType + ":" + _lineBreak;
// enables snapshots for the measurementgridtest - DO NOT SUBMIT ENABLED - It takes too long!
// TestData["bitmapSnapshot"] = "true";
// Note: These must correspond to a Watson product area (case-sensitive)
metaData = {};
metaData.productArea = "Text Composition";
}
[BeforeClass]
public static function setUpClass():void
{
stringFactory = new StringTextLineFactory();
textFlowFactory = new TextFlowTextLineFactory();
labelFactory = new StringTextLineFactory();
var labelFormat:TextLayoutFormat = new TextLayoutFormat();
labelFormat.fontSize = 12;
labelFactory.spanFormat = labelFormat;
}
[AfterClass]
public static function tearDownClass():void
{
stringFactory = null;
textFlowFactory = null;
labelFactory = null;
}
[Before]
override public function setUpTest():void
{
super.setUpTest();
cleanUpTestApp();
TestDisplayObject = testApp.getDisplayObject();
if (!TestDisplayObject)
{
fail("Did not get a blank canvas to work with");
}
}
[After]
override public function tearDownTest():void
{
super.tearDownTest();
}
/********************** Tests Start Here ***************************/
[Test]
public function simpleMultiParagraph():void
{
// This is a subset of simple.xml
// Exposed Watson bug 2559210
var markup:String = '<flow:TextFlow xmlns:flow="http://ns.adobe.com/textLayout/2008" fontSize="14" textIndent="15" paddingTop="4" paddingLeft="4" fontFamily="Times New Roman">' +
'<flow:p paragraphSpaceAfter="15"><flow:span>There are many </flow:span><flow:span fontStyle="italic">such</flow:span><flow:span> lime-kilns in that tract of country, for the purpose of burning the white marble which composes a large part of the substance of the hills. Some of them, built years ago, and long deserted, with weeds growing in the vacant round of the interior, which is open to the sky, and grass and wild-flowers rooting themselves into the chinks of the stones, look already like relics of antiquity, and may yet be overspread with the lichens of centuries to come. Sentences removed.</flow:span></flow:p>' +
'<flow:p paragraphSpaceAfter="15"><flow:span>The man who now watched the fire was of a different order, and troubled himself with no thoughts save the very few that were requisite to his business. At frequent intervals, he flung back the clashing weight of the iron door, and, turning his face from the insufferable glare, thrust in huge logs of oak, or stirred the immense brands with a long pole. Sentences removed.</flow:span></flow:p>' +
'</flow:TextFlow>';
runSingleTest(markup, insertText);
}
[Test]
public function simpleMultiParagraphNoTextIndent():void
{
// This is a subset of simple.xml, and has NO first line indent applied to the paragraphs.
var markup:String = '<flow:TextFlow xmlns:flow="http://ns.adobe.com/textLayout/2008" fontSize="14" paddingTop="4" paddingLeft="4" fontFamily="Times New Roman">' +
'<flow:p paragraphSpaceAfter="15"><flow:span>There are many </flow:span><flow:span fontStyle="italic">such</flow:span><flow:span> lime-kilns in that tract of country, for the purpose of burning the white marble which composes a large part of the substance of the hills. Some of them, built years ago, and long deserted, with weeds growing in the vacant round of the interior, which is open to the sky, and grass and wild-flowers rooting themselves into the chinks of the stones, look already like relics of antiquity, and may yet be overspread with the lichens of centuries to come. Sentences removed.</flow:span></flow:p>' +
'<flow:p paragraphSpaceAfter="15"><flow:span>The man who now watched the fire was of a different order, and troubled himself with no thoughts save the very few that were requisite to his business. At frequent intervals, he flung back the clashing weight of the iron door, and, turning his face from the insufferable glare, thrust in huge logs of oak, or stirred the immense brands with a long pole. Sentences removed.</flow:span></flow:p>' +
'</flow:TextFlow>';
runSingleTest(markup);
}
[Test]
public function longSimpleMultiParagraph():void
{
// This is a longer version of simple.xml, so the text overflows the visible area and scrolls
// Exposed Watson bug 2559210
var markup:String = '<flow:TextFlow xmlns:flow="http://ns.adobe.com/textLayout/2008" fontSize="14" textIndent="15" paddingTop="4" paddingLeft="4" fontFamily="Times New Roman">' +
'<flow:p paragraphSpaceAfter="15"><flow:span>There are many </flow:span><flow:span fontStyle="italic">such</flow:span><flow:span> lime-kilns in that tract of country, for the purpose of burning the white marble which composes a large part of the substance of the hills. Some of them, built years ago, and long deserted, with weeds growing in the vacant round of the interior, which is open to the sky, and grass and wild-flowers rooting themselves into the chinks of the stones, look already like relics of antiquity, and may yet be overspread with the lichens of centuries to come. Sentences removed.</flow:span></flow:p>' +
'<flow:p paragraphSpaceAfter="15"><flow:span>The man who now watched the fire was of a different order, and troubled himself with no thoughts save the very few that were requisite to his business. At frequent intervals, he flung back the clashing weight of the iron door, and, turning his face from the insufferable glare, thrust in huge logs of oak, or stirred the immense brands with a long pole. Sentences removed.</flow:span></flow:p>' +
'<flow:p paragraphSpaceAfter="15"><flow:span>The man who now watched the fire was of a different order, and troubled himself with no thoughts save the very few that were requisite to his business. At frequent intervals, he flung back the clashing weight of the iron door, and, turning his face from the insufferable glare, thrust in huge logs of oak, or stirred the immense brands with a long pole. Sentences removed.</flow:span></flow:p>' +
'<flow:p paragraphSpaceAfter="15"><flow:span>The man who now watched the fire was of a different order, and troubled himself with no thoughts save the very few that were requisite to his business. At frequent intervals, he flung back the clashing weight of the iron door, and, turning his face from the insufferable glare, thrust in huge logs of oak, or stirred the immense brands with a long pole. Sentences removed.</flow:span></flow:p>' +
'<flow:p paragraphSpaceAfter="15"><flow:span>The man who now watched the fire was of a different order, and troubled himself with no thoughts save the very few that were requisite to his business. At frequent intervals, he flung back the clashing weight of the iron door, and, turning his face from the insufferable glare, thrust in huge logs of oak, or stirred the immense brands with a long pole. Sentences removed.</flow:span></flow:p>' +
'<flow:p paragraphSpaceAfter="15"><flow:span>The man who now watched the fire was of a different order, and troubled himself with no thoughts save the very few that were requisite to his business. At frequent intervals, he flung back the clashing weight of the iron door, and, turning his face from the insufferable glare, thrust in huge logs of oak, or stirred the immense brands with a long pole. Sentences removed.</flow:span></flow:p>' +
'<flow:p paragraphSpaceAfter="15"><flow:span>The man who now watched the fire was of a different order, and troubled himself with no thoughts save the very few that were requisite to his business. At frequent intervals, he flung back the clashing weight of the iron door, and, turning his face from the insufferable glare, thrust in huge logs of oak, or stirred the immense brands with a long pole. Sentences removed.</flow:span></flow:p>' +
'</flow:TextFlow>';
runSingleTest(markup, insertText);
}
[Test]
public function simpleMultiParagraphNegTextIndent():void
{
// This is a subset of simple.xml, and has NO first line indent applied to the paragraphs.
var markup:String = '<flow:TextFlow xmlns:flow="http://ns.adobe.com/textLayout/2008" fontSize="14" textIndent="15" paddingTop="4" paddingLeft="4" fontFamily="Times New Roman">' +
'<flow:p paragraphSpaceAfter="30"><flow:span>There are many </flow:span><flow:span fontStyle="italic">such</flow:span><flow:span> lime-kilns in that tract of country, for the purpose of burning the white marble which composes a large part of the substance of the hills. Some of them, built years ago, and long deserted, with weeds growing in the vacant round of the interior, which is open to the sky, and grass and wild-flowers rooting themselves into the chinks of the stones, look already like relics of antiquity, and may yet be overspread with the lichens of centuries to come. Sentences removed.</flow:span></flow:p>' +
'<flow:p paragraphSpaceAfter="30"><flow:span>The man who now watched the fire was of a different order, and troubled himself with no thoughts save the very few that were requisite to his business. At frequent intervals, he flung back the clashing weight of the iron door, and, turning his face from the insufferable glare, thrust in huge logs of oak, or stirred the immense brands with a long pole. Sentences removed.</flow:span></flow:p>' +
'</flow:TextFlow>';
runSingleTest(markup);
}
[Test]
public function simpleWithPaddingTopLeft():void
{
// This is a subset of simple.xml, and has NO first line indent applied to the paragraphs.
var markup:String = '<flow:TextFlow xmlns:flow="http://ns.adobe.com/textLayout/2008" fontSize="14" paddingTop="40" paddingLeft="20">' +
'<flow:p paragraphSpaceAfter="15"><flow:span>There are many </flow:span><flow:span fontStyle="italic">such</flow:span><flow:span> lime-kilns in that tract of country, for the purpose of burning the white marble which composes a large part of the substance of the hills. Some of them, built years ago, and long deserted, with weeds growing in the vacant round of the interior, which is open to the sky, and grass and wild-flowers rooting themselves into the chinks of the stones, look already like relics of antiquity, and may yet be overspread with the lichens of centuries to come. Sentences removed.</flow:span></flow:p>' +
'<flow:p paragraphSpaceAfter="15"><flow:span>The man who now watched the fire was of a different order, and troubled himself with no thoughts save the very few that were requisite to his business. At frequent intervals, he flung back the clashing weight of the iron door, and, turning his face from the insufferable glare, thrust in huge logs of oak, or stirred the immense brands with a long pole. Sentences removed.</flow:span></flow:p>' +
'</flow:TextFlow>';
runSingleTest(markup);
}
[Test]
public function simpleWithPaddingBottomRight():void
{
// This is a subset of simple.xml, and has NO first line indent applied to the paragraphs.
var markup:String = '<flow:TextFlow xmlns:flow="http://ns.adobe.com/textLayout/2008" fontSize="14" paddingBottom="40" paddingRight="20">' +
'<flow:p><flow:span>There are many </flow:span><flow:span fontStyle="italic">such</flow:span><flow:span> lime-kilns in that tract of country, for the purpose of burning the white marble which composes a large part of the substance of the hills. Some of them, built years ago, and long deserted, with weeds growing in the vacant round of the interior, which is open to the sky, and grass and wild-flowers rooting themselves into the chinks of the stones, look already like relics of antiquity, and may yet be overspread with the lichens of centuries to come. Sentences removed.</flow:span></flow:p>' +
'<flow:p><flow:span>The man who now watched the fire was of a different order, and troubled himself with no thoughts save the very few that were requisite to his business. At frequent intervals, he flung back the clashing weight of the iron door, and, turning his face from the insufferable glare, thrust in huge logs of oak, or stirred the immense brands with a long pole. Sentences removed.</flow:span></flow:p>' +
'</flow:TextFlow>';
runSingleTest(markup);
}
public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void
{
eventDispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
}
public function dispatchEvent(evt:Event):Boolean
{
return eventDispatcher.dispatchEvent(evt);
}
public function hasEventListener(type:String):Boolean
{
return eventDispatcher.hasEventListener(type);
}
public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void
{
eventDispatcher.removeEventListener(type, listener, useCapture);
}
public function willTrigger(type:String):Boolean
{
return eventDispatcher.willTrigger(type);
}
// end of IEventDispatcher functions
private function insertText(textFlow:TextFlow):void
{
textFlow.interactionManager = editManager;
editManager.selectRange(textFlow.textLength, textFlow.textLength);
editManager.insertText("INSERTED TEXT");
var controller:ContainerController = textFlow.flowComposer.getControllerAt(0);
controller.verticalScrollPosition = int.MAX_VALUE;
textFlow.flowComposer.updateAllControllers();
}
private function addToCanvas(sprite:Sprite):void
{
TestDisplayObject = testApp.getDisplayObject();
if (TestDisplayObject)
{
testCanvas = Canvas(TestDisplayObject);
testCanvas.rawChildren.addChild(sprite);
}
}
// Track the completion of loading inlines, dispatch a completion event when its done
private function statusChangeHandler(obj:Event):void
{
var event:StatusChangeEvent = StatusChangeEvent(obj);
var textFlow:TextFlow = event.element.getTextFlow();
switch (event.status)
{
case InlineGraphicElementStatus.LOADING:
case InlineGraphicElementStatus.SIZE_PENDING:
break;
case InlineGraphicElementStatus.READY:
notReadyGraphicsCount--;
if (notReadyGraphicsCount <= 0)
{
textFlow.removeEventListener(StatusChangeEvent.INLINE_GRAPHIC_STATUS_CHANGE, statusChangeHandler);
if (_creationType == USE_FLOW)
{
this.dispatchEvent(new Event("textFlowsReady"));
}
else if (_creationType == USE_FACTORY_FLOW)
{
this.dispatchEvent(new Event("flowFactsReady"));
}
}
break;
default:
assertTrue("unexpected StatusChangeEvent status: " + event.status, false);
break;
}
}
private function addTextFlowSprite(parentSprite:Sprite, x:Number, y:Number, width:Number, height:Number, textFlow:TextFlow):Sprite
{
sprite = new Sprite();
sprite.x = x;
sprite.y = y;
textFlow.interactionManager = new EditManager();
var controller:ContainerController = new ContainerController(sprite, width, height);
controller.verticalScrollPolicy = scrollPolicy;
controller.horizontalScrollPolicy = scrollPolicy;
// controller.format = format; Test adding padding directly to the container
// trace(x,y,controller.compositionWidth,controller.compositionHeight,scrollPolicy);
// trace(TextConverter.export(textFlow,TextConverter.TEXT_LAYOUT_FORMAT,ConversionType.STRING_TYPE));
textFlow.flowComposer.removeAllControllers();
textFlow.flowComposer.addController(controller);
textFlow.flowComposer.updateAllControllers();
parentSprite.addChild(sprite);
drawFlowComposerBounds(parentSprite, textFlow);
// trace(controller.getContentBounds());
// trace("addTextFlowSprite is running");
return sprite;
}
private function drawFlowComposerBounds(parentSprite:Sprite, textFlow:TextFlow):void
{
// composition bounds in black
var controller:ContainerController = textFlow.flowComposer.getControllerAt(0);
var controllerSprite:Sprite = controller.container;
var scrollx:Number = controllerSprite.scrollRect ? controllerSprite.scrollRect.x : 0;
var scrolly:Number = controllerSprite.scrollRect ? controllerSprite.scrollRect.y : 0;
sprite = new Sprite(); // controller.container as Sprite;
sprite.x = controllerSprite.x;
sprite.y = controllerSprite.y;
parentSprite.addChild(sprite);
var g:Graphics = sprite.graphics;
g.clear();
drawCircle(g, 0xff00, 0, 0, 3);
strokeRect(g, 1, 0x0, 0, 0, width, height);
// contentBounds in red
compareBounds = controller.getContentBounds();
strokeRect(g, 1, 0xFF0000, compareBounds.x - scrollx, compareBounds.y - scrolly, compareBounds.width, compareBounds.height);
tFlowBounds = controller.getContentBounds();
tFlowBounds.x = compareBounds.x - scrollx;
tFlowBounds.y = compareBounds.y - scrolly;
}
private function strokeRect(g:Graphics, stroke:Number, color:uint, x:Number, y:Number, width:Number, height:Number):void
{
if (width <= 0 || height <= 0)
return;
g.lineStyle(stroke, color);
g.moveTo(x, y);
g.lineTo(x + width, y);
g.lineTo(x + width, y + height);
g.lineTo(x, y + height);
g.lineTo(x, y);
}
private function drawCircle(g:Graphics, color:uint, x:Number, y:Number, radius:Number):void
{
g.beginFill(color);
g.drawCircle(x, y, radius);
g.endFill();
}
/** Run a single markup description in vertical alignment (top, middle, bottom) * horizontal alignment (left, center, right) in
* both the full compose using ContainerController and a TextFlow Factory case. Compare the results to make sure the text falls
* in the correct area of the container, that the content bounds is no smaller than the inked bounds, and that the full compose
* content bounds matches the factory content bounds (or has only fractional differences). Note that the inked bounds may be smaller
* than the content bounds because (for example) padding or indents have been applied.
*/
public function runSingleTest(markup:String, manipulateText:Function = null):void
{
var textFlow:TextFlow = TextConverter.importToFlow(markup, TextConverter.TEXT_LAYOUT_FORMAT);
textFlow.blockProgression = _blockProgression;
textFlow.direction = _direction;
var verticalAlign:String;
var textAlign:String;
var contentBounds:Rectangle;
var compositionBounds:Rectangle = new Rectangle(0, 0, width, height);
var parentSprite:Sprite = new Sprite();
addToCanvas(parentSprite);
for each (verticalAlign in verticalAlignArray)
{
textFlow.verticalAlign = verticalAlign;
for each (textAlign in textAlignArray)
{
textFlow.textAlign = textAlign;
while (parentSprite.numChildren > 0)
parentSprite.removeChildAt(0);
addTextFlowSprite(parentSprite, compositionBounds.left, compositionBounds.top, compositionBounds.width, compositionBounds.height, textFlow);
BoundsChecker.validateAll(textFlow, parentSprite);
// Try doing some editing
if (manipulateText != null)
{
addTextFlowSprite(parentSprite, compositionBounds.left, compositionBounds.top, compositionBounds.width, compositionBounds.height, textFlow);
manipulateText(textFlow);
BoundsChecker.validateAll(textFlow, parentSprite);
}
textFlow.flowComposer = new StandardFlowComposer(); // we may have lost it while generating via the factory
}
}
parentSprite.parent.removeChild(parentSprite);
}
// Ideographic baseline examples needed
}
}