blob: 69b827b001d16ae5f38f4fe0442d9589f6b2d8a5 [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 UnitTest.Tests
{
import UnitTest.ExtendedClasses.TestDescriptor;
import UnitTest.ExtendedClasses.TestSuiteExtended;
import UnitTest.ExtendedClasses.VellumTestCase;
import UnitTest.Fixtures.TestConfig;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.DisplayObjectContainer;
import flash.display.Sprite;
import flash.geom.Rectangle;
import flash.text.engine.FontDescription;
import flash.text.engine.FontWeight;
import flash.text.engine.TextLine;
import flash.utils.ByteArray;
import flash.utils.getTimer;
import flashx.textLayout.compose.StandardFlowComposer;
import flashx.textLayout.compose.TextFlowLine;
import flashx.textLayout.container.ContainerController;
import flashx.textLayout.edit.EditManager;
import flashx.textLayout.edit.IEditManager;
import flashx.textLayout.elements.DivElement;
import flashx.textLayout.elements.FlowElement;
import flashx.textLayout.elements.FlowLeafElement;
import flashx.textLayout.elements.GlobalSettings;
import flashx.textLayout.elements.InlineGraphicElement;
import flashx.textLayout.elements.LinkElement;
import flashx.textLayout.elements.ListElement;
import flashx.textLayout.elements.ListItemElement;
import flashx.textLayout.elements.ParagraphElement;
import flashx.textLayout.elements.SpanElement;
import flashx.textLayout.elements.TCYElement;
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.formats.BlockProgression;
import flashx.textLayout.formats.Direction;
import flashx.textLayout.formats.FormatValue;
import flashx.textLayout.formats.ITextLayoutFormat;
import flashx.textLayout.formats.ListStyleType;
import flashx.textLayout.formats.TextAlign;
import flashx.textLayout.formats.TextLayoutFormat;
import flashx.textLayout.property.*;
import flashx.textLayout.tlf_internal;
import flashx.undo.IUndoManager;
import flashx.undo.UndoManager;
import mx.core.FTETextField;
import mx.utils.LoaderUtil;
use namespace tlf_internal;
public class FlowModelTest extends VellumTestCase
{
public function FlowModelTest(methodName:String, testID:String, testConfig:TestConfig, testXML:XML = null)
{
super(methodName, testID, testConfig);
// Note: These must correspond to a Watson product area (case-sensitive)
metaData.productArea = "Text Container";
metaData.productSubArea = "Flow";
}
public static function suite(testConfig:TestConfig, ts:TestSuiteExtended):void
{
if (testConfig.writingDirection[0] == BlockProgression.TB && testConfig.writingDirection[1] == Direction.LTR)
{
ts.addTestDescriptor (new TestDescriptor (FlowModelTest, "basicAPITest1", testConfig ) );
ts.addTestDescriptor (new TestDescriptor (FlowModelTest, "basicAPITest2", testConfig ) );
ts.addTestDescriptor (new TestDescriptor (FlowModelTest, "basicAPITest3", testConfig ) );
ts.addTestDescriptor (new TestDescriptor (FlowModelTest, "basicAPITest4", testConfig ) );
ts.addTestDescriptor (new TestDescriptor (FlowModelTest, "basicAPITest5", testConfig ) );
ts.addTestDescriptor (new TestDescriptor (FlowModelTest, "textFlowHostCharacterFormat", testConfig ) );
ts.addTestDescriptor (new TestDescriptor (FlowModelTest, "iterateParagraphForward", testConfig ) );
ts.addTestDescriptor (new TestDescriptor (FlowModelTest, "iterateParagraphBackward", testConfig ) );
ts.addTestDescriptor (new TestDescriptor (FlowModelTest, "cascadeValidation", testConfig ) );
ts.addTestDescriptor (new TestDescriptor (FlowModelTest, "elementMovingTest", testConfig ) );
ts.addTestDescriptor (new TestDescriptor (FlowModelTest, "emptyTextFlowTests", testConfig ) );
ts.addTestDescriptor (new TestDescriptor (FlowModelTest, "emptyElementCopyTests", testConfig ) );
ts.addTestDescriptor (new TestDescriptor (FlowModelTest, "fontMappingTest", testConfig ) );
ts.addTestDescriptor (new TestDescriptor (FlowModelTest, "insertLinkNoUpdateAPI", testConfig ) );
ts.addTestDescriptor (new TestDescriptor (FlowModelTest, "insertLinkNoUpdateViaEditManager", testConfig ) );
ts.addTestDescriptor (new TestDescriptor (FlowModelTest, "testUndo", testConfig ) );
ts.addTestDescriptor (new TestDescriptor (FlowModelTest, "testFindControllerIndexAtPosition", testConfig ) );
ts.addTestDescriptor (new TestDescriptor (FlowModelTest, "testFTETextField", testConfig ) );
ts.addTestDescriptor (new TestDescriptor (FlowModelTest, "listItemInsertion", testConfig ) );
ts.addTestDescriptor (new TestDescriptor (FlowModelTest, "replaceChildrenTest", testConfig ) );
ts.addTestDescriptor (new TestDescriptor (FlowModelTest, "resolveFontLookupTest", testConfig ) );
ts.addTestDescriptor (new TestDescriptor (FlowModelTest, "bindableSpan", testConfig ) );
}
}
private var beginTime:int;
private function beginAPITest():void
{
beginTime = getTimer();
SelManager.selectRange(-1,-1);
}
private function endAPITest():void
{
SelManager.selectRange(0,0);
SelManager.textFlow.flowComposer.updateAllControllers();
}
/**
* Direct usage of the flow APIs. No validation - just look for crashes.
* This test changes the color of the entire text.
*/
public function basicAPITest1():void
{
// set the color on the entire flow
beginAPITest();
SelManager.textFlow.color = 0xff0000;
endAPITest();
}
/**
* Direct usage of the flow APIs. No validation - just look for crashes.
* This test deletes every other character in the entire text.
*/
public function basicAPITest2():void
{
// delete every other character in the textflow
beginAPITest();
var idx:int = 0;
while (idx < SelManager.textFlow.textLength)
{
var span:SpanElement = SelManager.textFlow.findLeaf(idx) as SpanElement;
if (span)
{
var spanStart:int = span.getAbsoluteStart();
span.replaceText(idx-spanStart,idx-spanStart+1,null);
}
idx++;
}
endAPITest();
}
/**
* Direct usage of the flow APIs. No validation - just look for crashes.
* This test adds another paragraph to the flow containing the text "Hello World"
* in 24 point font.
*/
public function basicAPITest3():void
{
// add a paragraph
beginAPITest();
var p:ParagraphElement = new ParagraphElement();
var s:SpanElement = new SpanElement();
s.text = "Hello world";
s.fontSize = 24;
p.replaceChildren(0,0,s);
SelManager.textFlow.replaceChildren(SelManager.textFlow.numChildren,SelManager.textFlow.numChildren,p);
endAPITest();
}
/**
* This test performs a series of changes and validates the changes after they're performed.
*/
public function basicAPITest4():void
{
// more comprehensive set of tests - several manipulations to the flow and then display it
// generic begin to an API test
beginAPITest();
// get the textflow
var textFlow:TextFlow = SelManager.textFlow;
// remove all the textflow children
textFlow.replaceChildren(0,textFlow.numChildren);
assertTrue("expected no elements on the flow, but found " + textFlow.numChildren,
textFlow.numChildren == 0);
// create a paragraph
var p:ParagraphElement = new ParagraphElement();
// create a span
var s:SpanElement = new SpanElement();
s.text = "Hello world. ";
s.fontSize = 24;
// split the span
var nextSpan:SpanElement = s.splitAtPosition(6) as SpanElement;
// set the color - color can be a string or an unsigned integer
nextSpan.color = "0xff";
assertTrue("expected that the color would be 255, but found " + uint(nextSpan.format.color),
uint(nextSpan.format.color) == 255);
// put the two spans in the paragraph
p.replaceChildren(0,0,s);
CONFIG::debug { assertTrue("debugCheckFlowElement() failed after adding one span",
p.debugCheckFlowElement() == 0); }
p.replaceChildren(1,1,nextSpan);
CONFIG::debug { assertTrue("debugCheckFlowElement() failed after adding second span",
p.debugCheckFlowElement() == 0); }
assertTrue("expected the element count to be 2, but it was " + p.numChildren,
p.numChildren == 2);
// add another span
s = new SpanElement();
s.text = "Start:"
p.replaceChildren(0,0,s);
CONFIG::debug { assertTrue("debugCheckFlowElement() failed after adding third span",
p.debugCheckFlowElement() == 0); }
// put the paragraph in the TextFlow
textFlow.replaceChildren(0,0,p);
assertTrue("text flow should have one element but has " + textFlow.numChildren,
textFlow.numChildren == 1);
CONFIG::debug { assertTrue("debugCheckFlowElement() failed after adding para to flow",
textFlow.debugCheckFlowElement() == 0) }
// make another paragraph
p = new ParagraphElement();
s = new SpanElement();
p.replaceChildren(0,0,s);
s.text="NEW FIRST PARAGRAPH";
// set the paragraph attributes directly
p.textIndent = 20;
// set the paragraph attributes via clone and set
var pa:TextLayoutFormat = new TextLayoutFormat(p.format);
pa.textAlign = TextAlign.RIGHT;
p.format = pa;
// into the textFlow at the beginning
textFlow.replaceChildren(0,0,p);
CONFIG::debug {assertTrue("debugCheckFlowElement() failed after adding para to beginning",
textFlow.debugCheckFlowElement() == 0); }
// generic end to an API test
endAPITest();
}
/**
* This test inserts an FE in the middle of a paragraph
*/
public function basicAPITest5():void
{
// more comprehensive set of tests - several manipulations to the flow and then display it
// generic begin to an API test
beginAPITest();
// get the textflow
var textFlow:TextFlow = SelManager.textFlow;
// create a paragraph empty the flow and insert it
var p:ParagraphElement = new ParagraphElement();
textFlow.replaceChildren(0,textFlow.numChildren,p);
// create a span
var s:SpanElement = new SpanElement();
s.text = "Hello world. ";
s.fontSize = 24;
// put it in the paragraph
p.replaceChildren(0,0,s);
// split the span
var nextSpanElement:SpanElement = s.splitAtPosition(6) as SpanElement;
assertTrue("Incorrect elementCount after split", p.numChildren == 2);
// create and insert an image between the spans
var image:InlineGraphicElement = new InlineGraphicElement();
image.width = 19;
image.height = 19;
image.source = LoaderUtil.createAbsoluteURL(baseURL,"../../test/testFiles/assets/surprised.png");
p.replaceChildren(1,1,image);
assertTrue("failed length on new InlineGraphicElement", image.textLength == 1);
assertTrue("failed elementCount after image insert", p.numChildren == 3);
assertTrue("bad first child",p.getChildAt(0) is SpanElement);
assertTrue("bad first child",p.getChildAt(1) is InlineGraphicElement);
assertTrue("bad first child",p.getChildAt(2) is SpanElement);
// set a userStyle on a ContainerController
var saveFormat:ITextLayoutFormat = ContainerController.containerControllerInitialFormat;
try
{
ContainerController.containerControllerInitialFormat = null;
var controller:ContainerController = new ContainerController(new Sprite());
controller.setStyle("foo","blah");
}
catch (e:Error)
{
ContainerController.containerControllerInitialFormat = saveFormat;
throw(e);
}
ContainerController.containerControllerInitialFormat = saveFormat;
// generic end to an API test
endAPITest();
}
/** Test setting hostCharacterFormat on the TextFlow */
public function textFlowHostCharacterFormat():void
{
// generic begin to an API test
beginAPITest();
// get the textflow
var textFlow:TextFlow = SelManager.textFlow;
var leaf:FlowLeafElement = textFlow.getFirstLeaf();
// make it red
var cf:TextLayoutFormat = new TextLayoutFormat();
cf.color = 0xff0000;
textFlow.hostFormat = cf;
assertTrue("host character format set color failed",leaf.computedFormat.color == 0xff0000);
// make it blue
textFlow.color = 0xff;
assertTrue("textFlow character format color override failed",leaf.computedFormat.color == 0xff);
// clear the blue
textFlow.color = undefined;
assertTrue("textFlow color clear failed",leaf.computedFormat.color == 0xff0000);
// clear the hostCharacterFormat
textFlow.hostFormat = null;
assertTrue("host character format clear failed",leaf.computedFormat.color == 0);
endAPITest();
}
private function initTextFlowAAA():TextFlow
{
var textFlow:TextFlow = new TextFlow();
var p:ParagraphElement = new ParagraphElement();
var span:SpanElement = new SpanElement();
span.text = "aaa";
p.replaceChildren(0, 0, span);
textFlow.replaceChildren(0, 0, p);
return textFlow;
}
// Insert a link to a paragraph that hasn't ever been updated.
public function insertLinkNoUpdateAPI():void
{
var textFlow:TextFlow = new TextFlow();
var p:ParagraphElement = new ParagraphElement();
var link:LinkElement = new LinkElement();
link.href = "http://www.cnn.com";
link.target = "_self";
var span:SpanElement = new SpanElement();
span.text = "aaa";
link.replaceChildren(0, 0, span);
p.replaceChildren(0, 0, link);
textFlow.replaceChildren(0, 0, p);
}
// Insert a link to a paragraph that hasn't ever been updated.
public function insertLinkNoUpdateViaEditManager():void
{
var textFlow:TextFlow = initTextFlowAAA();
var editManager:IEditManager = new EditManager();
textFlow.interactionManager = editManager;
editManager.selectRange(1,2);
editManager.applyLink("http://livedocs.adobe.com/", "_self", true);
}
// undo in a flow that has no controllers
public function testUndo():void
{
var textFlow:TextFlow = initTextFlowAAA();
var undoManager:IUndoManager = new UndoManager();
var editManager:IEditManager = new EditManager(undoManager);
textFlow.interactionManager = editManager;
editManager.selectRange(1,2);
var format:TextLayoutFormat = new TextLayoutFormat();
format.fontWeight = FontWeight.BOLD;
editManager.applyLeafFormat(format);
undoManager.undo();
}
private function createParaIterationModel():TextFlow
{
// Creates 3 divs, each have 4 paras
const paraTotal:int = 4;
var paraCount:int;
const divTotal:int = 3;
var divCount:int;
var flow:TextFlow = new TextFlow();
for (var j:int = 0; j < divTotal; j++)
{
var div:DivElement = new DivElement();
for (var i:int = 0; i < paraTotal; i++)
{
var para:ParagraphElement = new ParagraphElement();
var span:SpanElement = new SpanElement();
span.text = paraCount.toString();
para.addChild(span);
div.addChild(para);
paraCount++;
}
flow.addChild(div);
}
return flow;
}
public function iterateParagraphForward():void
{
var flow:TextFlow = createParaIterationModel();
var para:ParagraphElement = flow.getFirstLeaf().getParagraph();
var i:int = 0;
while (para != null)
{
var cStr:String = para.getText();
assertTrue("Text not as expected", int(cStr) == i);
para = para.getNextParagraph();
i++;
}
assertTrue("Unexpected paragraph count", i == 12);
}
public function iterateParagraphBackward():void
{
//const kParaTerminator:String = '\u2029';
var flow:TextFlow = createParaIterationModel();
var para:ParagraphElement = flow.getLastLeaf().getParagraph();
var i:int = 11;
//These two tests are no longer valid due to PARB changes to removed the terminator
//parameter from getText on a paragraph element.
//
//var terminatorTestStr:String = para.getText("\n");
//assertTrue("Should have newline as terminator", terminatorTestStr.substr(terminatorTestStr.length - 1, 1) == '\n');
//terminatorTestStr = para.getText();
//assertTrue("Should have paragraph terminator as terminator", terminatorTestStr.substr(terminatorTestStr.length - 1, 1) == kParaTerminator);
while (para != null)
{
var cStr:String = para.getText();
assertTrue("Text not as expected", int(cStr) == i);
para = para.getPreviousParagraph();
i--;
}
assertTrue("Unexpected paragraph count", i == -1);
}
public function cascadeValidation():void
{
var flow:TextFlow = new TextFlow();
var para:ParagraphElement = new ParagraphElement();
var span:SpanElement = new SpanElement();
flow.addChild(para);
para.addChild(span);
flow.backgroundColor = 0xff;
assertTrue("backgroundColor should not inherit",TextLayoutFormat.backgroundColorProperty.inherited == false);
assertTrue("bad flow backGroundColor", flow.computedFormat.backgroundColor == 0xff);
assertTrue("bad para backGroundColor", para.computedFormat.backgroundColor == TextLayoutFormat.backgroundColorProperty.defaultValue);
assertTrue("bad span backGroundColor", span.computedFormat.backgroundColor == TextLayoutFormat.backgroundColorProperty.defaultValue);
}
// tests api change to automatically remove a flowelements children when using replaceChildren
public function elementMovingTest():void
{
var lengthBefore:int;
// this flow should have two paragraphs as children
var flow:TextFlow = SelManager.textFlow.deepCopy() as TextFlow; // clone the flow
var firstPara:FlowElement = flow.getChildAt(0);
lengthBefore = flow.textLength;
//firstPara.parent.removeChild(firstPara); // soon to no longer be needed
//assertTrue("elementMovingTest: removing para incorrect lengths",flow.textLength == lengthBefore-firstPara.textLength);
lengthBefore = SelManager.textFlow.textLength;
SelManager.textFlow.addChild(firstPara);
assertTrue("elementMovingTest: adding para incorrect lengths",SelManager.textFlow.textLength == lengthBefore+firstPara.textLength);
var lastLeaf:FlowElement = flow.getLastLeaf();
var lastLeafLength:int = lastLeaf.textLength;
//lastLeaf.parent.removeChild(lastLeaf); // soon to no longer be needed
lengthBefore = SelManager.textFlow.textLength;
SelManager.textFlow.getLastLeaf().parent.addChild(lastLeaf);
assertTrue("elementMovingTest: adding span incorrect lengths",SelManager.textFlow.textLength == lengthBefore+lastLeafLength-1);
}
// Empty Flow Tests - verify that empty/partially empty TextFlow's are normalized correctly
public function emptyTextFlowTests():void
{
var tf:TextFlow;
// just an empty TextFlow
tf = new TextFlow();
tf.flowComposer.addController(new ContainerController(new Sprite()));
tf.flowComposer.updateAllControllers();
// empty TextFlow with paragraph
tf = new TextFlow();
tf.addChild(new ParagraphElement());
tf.flowComposer.addController(new ContainerController(new Sprite()));
tf.flowComposer.updateAllControllers();
// empty TextFlow with paragraph and ILG
tf = new TextFlow();
var p:ParagraphElement = new ParagraphElement();
p.addChild(new InlineGraphicElement());
tf.addChild(p);
tf.flowComposer.addController(new ContainerController(new Sprite()));
tf.flowComposer.updateAllControllers();
// empty TextFlow with paragraph and empty LinkElement
tf = new TextFlow();
p = new ParagraphElement();
p.addChild(new LinkElement());
tf.addChild(p);
tf.flowComposer.addController(new ContainerController(new Sprite()));
tf.flowComposer.updateAllControllers();
// empty TextFlow with paragraph and empty TCYElement
tf = new TextFlow();
p = new ParagraphElement();
p.addChild(new TCYElement());
tf.addChild(p);
tf.flowComposer.addController(new ContainerController(new Sprite()));
tf.flowComposer.updateAllControllers();
// a more complex example
tf = new TextFlow();
p = new ParagraphElement();
var tcy:TCYElement = new TCYElement();
tcy.addChild(new InlineGraphicElement());
tcy.addChild(new SpanElement());
p.addChild(tcy);
tf.addChild(p);
tcy.removeChildAt(1);
tf.flowComposer.addController(new ContainerController(new Sprite()));
tf.flowComposer.updateAllControllers();
// several empty spans
tf = new TextFlow();
p = new ParagraphElement();
tf.addChild(p);
p.addChild(new SpanElement());
p.addChild(new SpanElement());
p.addChild(new SpanElement());
tf.flowComposer.addController(new ContainerController(new Sprite()));
tf.flowComposer.updateAllControllers();
}
public function emptyElementCopyTests():void
{
var elemList:Array = GeneralFunctionsTest.childParentTable[0];
for (var idx:int = 1; idx < elemList.length; idx++)
{
var elem:FlowElement = new elemList[idx];
elem.shallowCopy();
elem.deepCopy();
}
}
private function validateBitmap(actual:Bitmap, expected:Bitmap):Boolean
{
actual.bitmapData.draw(expected, null, null, "difference");
var bounds:Rectangle = new Rectangle(0, 0, actual.width, actual.height);
var diffPixels:ByteArray = actual.bitmapData.getPixels(bounds);
diffPixels.position = 0;
while (diffPixels.bytesAvailable > 0)
{
if (diffPixels.readByte() > 0)
return false;
}
return true;
}
private function myFontMapper(fd:FontDescription):void
{
if (fd.fontName == "Arial Black")
{
fd.fontName = "Arial";
fd.fontWeight = FontWeight.BOLD;
}
}
/** Tests fontMapping */
public function fontMappingTest():void
{
var textFlow:TextFlow = SelManager.textFlow;
var container:DisplayObjectContainer = DisplayObjectContainer(textFlow.flowComposer.getControllerAt(0).container);
textFlow.fontFamily = "Arial";
textFlow.fontWeight = FontWeight.BOLD;
textFlow.flowComposer.updateAllControllers();
var arialBoldBits:BitmapData = new BitmapData(container.width,container.height);
arialBoldBits.draw(container as DisplayObjectContainer);
var arialBoldData:Bitmap = new Bitmap(arialBoldBits);
textFlow.fontFamily = "Arial Black";
textFlow.fontWeight = undefined;
textFlow.flowComposer.updateAllControllers();
var arialBlackBits:BitmapData = new BitmapData(container.width,container.height);
arialBlackBits.draw(container as DisplayObjectContainer);
var arialBlackData:Bitmap = new Bitmap(arialBlackBits);
GlobalSettings.fontMapperFunction = myFontMapper;
textFlow.invalidateAllFormats();
try
{
textFlow.flowComposer.updateAllControllers();
}
finally
{
GlobalSettings.fontMapperFunction = null;
textFlow.invalidateAllFormats();
}
var mappedBits:BitmapData = new BitmapData(container.width,container.height);
mappedBits.draw(container as DisplayObjectContainer);
var mappedData:Bitmap = new Bitmap(mappedBits);
assertTrue("font mapping failed", validateBitmap(mappedData, arialBoldData));
textFlow.flowComposer.updateAllControllers();
var mappingClearedBits:BitmapData = new BitmapData(container.width,container.height);
mappingClearedBits.draw(container as DisplayObjectContainer);
var mappingClearedData:Bitmap = new Bitmap(mappingClearedBits);
assertTrue("clearing font mapping failed", validateBitmap(mappingClearedData, arialBlackData));
}
/** test the binary search algorithm which in findControllerIndexAtPosition - tricky bits wrt handling of zero length containers */
public function testFindControllerIndexAtPosition():void
{
var s:Sprite = new Sprite(); // scratch
var controller:ContainerController; // scratch
var composer:StandardFlowComposer = new StandardFlowComposer();
// ideally shouldn't need TextFlow but because containercontrollers find their owning composer via the textflow its needed
var textFlow:TextFlow = new TextFlow();
textFlow.flowComposer = composer;
textFlow.mxmlChildren = [ "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678" ];
controller = new ContainerController(s);
controller.verticalScrollPolicy = "off";
composer.addController(controller);
controller.setTextLength(100);
controller.verticalScrollPolicy = "off";
assertTrue("Bad result in findControllerIndexAtPosition 9",composer.findControllerIndexAtPosition(0)== 0);
assertTrue("Bad result in findControllerIndexAtPosition 10",composer.findControllerIndexAtPosition(100,true)== 0);
assertTrue("Bad result in findControllerIndexAtPosition 11",composer.findControllerIndexAtPosition(100,false)== -1);
for (var idx1:int = 0; idx1 < 4; idx1++)
{
for (var idx2:int = 0; idx2 < 4 ; idx2++)
{
var idx:int;
composer = new StandardFlowComposer();
textFlow = new TextFlow();
textFlow.flowComposer = composer;
textFlow.mxmlChildren = [ "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678" ];
// add some empties
for (idx = 0; idx < idx1; idx++)
composer.addController(new ContainerController(s));
// add one of length one
controller = new ContainerController(s);
controller.verticalScrollPolicy = "off"; // scrolling confuses it
composer.addController(controller);
controller.setTextLength(100); // internal API
// add some empties
for (idx = 0; idx < idx2; idx++)
composer.addController(new ContainerController(s));
assertTrue("Bad result in findControllerIndexAtPosition 1",composer.findControllerIndexAtPosition(0)== 0);
assertTrue("Bad result in findControllerIndexAtPosition 2",composer.findControllerIndexAtPosition(0,true)== 0);
assertTrue("Bad result in findControllerIndexAtPosition 3",composer.findControllerIndexAtPosition(100,true)== idx1);
assertTrue("Bad result in findControllerIndexAtPosition 4",composer.findControllerIndexAtPosition(100,false)== -1);
// add one with some length
textFlow.mxmlChildren = [ "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678" ];
controller.setTextLength(100);
controller = new ContainerController(s);
controller.verticalScrollPolicy = "off"; // scrolling confuses it
composer.addController(controller);
controller.setTextLength(100); // internal API
assertTrue("Bad result in findControllerIndexAtPosition 5",composer.findControllerIndexAtPosition(100,true)== idx1);
assertTrue("Bad result in findControllerIndexAtPosition 6",composer.findControllerIndexAtPosition(100,false)== idx1+1);
assertTrue("Bad result in findControllerIndexAtPosition 7",composer.findControllerIndexAtPosition(200,true)== idx1+idx2+1);
assertTrue("Bad result in findControllerIndexAtPosition 8",composer.findControllerIndexAtPosition(200,false)== -1);
}
}
}
private static function checkListLines(textFlow:TextFlow,numLines:int,prefix:String):void
{
for (var idx:int = 0; idx < numLines; idx++)
{
var tfl:TextFlowLine = textFlow.flowComposer.getLineAt(idx);
assertTrue(prefix+": Missing TextFlowLine: "+idx,tfl != null);
var textLine:TextLine = tfl.getTextLine();
assertTrue(prefix+": Missing TextLine: "+idx,textLine != null);
var numberLine:TextLine = textLine.getChildAt(0) as TextLine;
assertTrue(prefix+": Missing NumberLine: "+idx,numberLine != null);
/* var numberString:String = numberLine.userData as String;
var expectedString:String = (idx+1).toString() + "."; // for numeric lists
assertTrue(prefix+": NumberLine missing userData: "+idx,numberString != null);
assertTrue(prefix+": Incorrect NumberLine userData: "+idx,numberLine.userData as String == expectedString);
assertTrue(prefix+": Incorrect NumberLine rawTextLength: "+idx,numberString.length+1 == numberLine.rawTextLength); */
}
}
public function listItemInsertion():void
{
// some validations that ensure ListElement is correctly setup for list processing
// every ListStyleType must have an entry in ListElement.listSuffixes
var handler:EnumPropertyHandler = TextLayoutFormat.listStyleTypeProperty.findHandler(EnumPropertyHandler) as EnumPropertyHandler;
assertTrue("listItemInsertion: missing handler for ListStyleType", handler != null && handler.range != null);
var range:Object = handler.range;
var value:String;
var numberedListStyles:Object = { };
for (value in ListElement.algorithmicListStyles)
{
assertTrue("listItemInsertion: table entry duplicated",numberedListStyles[value] === undefined);
numberedListStyles[value] = ListElement.algorithmicListStyles[value];
}
for (value in ListElement.numericListStyles)
{
assertTrue("listItemInsertion: table entry duplicated",numberedListStyles[value] === undefined);
numberedListStyles[value] = ListElement.numericListStyles[value];
}
for (value in ListElement.alphabeticListStyles)
{
assertTrue("listItemInsertion: table entry duplicated",numberedListStyles[value] === undefined);
numberedListStyles[value] = ListElement.alphabeticListStyles[value];
}
for (value in range)
{
if (value != FormatValue.INHERIT)
{
// must be a numbered list or an unnumbered list but not both
assertTrue("listItemInsertion: listStyleType must be numbered or unnumbered but not both: "+value,
numberedListStyles[value] !== undefined && ListElement.constantListStyles[value] === undefined
|| numberedListStyles[value] === undefined && ListElement.constantListStyles[value] !== undefined)
// numbered lists must have a suffix
if ( ListElement.constantListStyles[value] === undefined)
assertTrue("listItemInsertion: missing suffix property: " + value,ListElement.listSuffixes[value] !== undefined);
}
}
// verify that all constantListStyles are in range
for (value in ListElement.constantListStyles)
assertTrue("listItemInsertion: invalid value in constantListStyles: " + value, range[value] !== undefined);
// verify that all numberedListStyles are in range
for (value in numberedListStyles)
assertTrue("listItemInsertion: invalid value in numberedListStyles: " + value, range[value] !== undefined);
// verify that all listSuffixes are in range
for (value in ListElement.listSuffixes)
assertTrue("listItemInsertion: invalid value in listSuffixes: " + value, range[value] !== undefined);
SelManager.selectRange(0,0);
// remove all the children and put in a list
var textFlow:TextFlow = SelManager.textFlow;
textFlow.replaceChildren(0,textFlow.numChildren);
var list:ListElement = new ListElement();
list.listStyleType = ListStyleType.DECIMAL;
textFlow.addChild(list);
var item:ListItemElement = new ListItemElement();
list.addChild(item);
textFlow.flowComposer.updateAllControllers();
assertTrue("listItemInsertion: incorrect normalize",textFlow.findAbsoluteParagraph(0).parent == item);
// append two items and compose
list.addChild(new ListItemElement());
list.addChild(new ListItemElement());
textFlow.flowComposer.updateAllControllers();
// check the three textlines
checkListLines(textFlow,3,"listItemInsertion1");
// now insert a brand new ListItem at the head and verify
list.replaceChildren(0,0,new ListItemElement());
textFlow.flowComposer.updateAllControllers();
// check four textlines
checkListLines(textFlow,4,"listItemInsertion2");
// remove a list item
list.removeChildAt(1);
textFlow.flowComposer.updateAllControllers();
// check three textLines
checkListLines(textFlow,3,"listItemInsertion3");
// add another list in the first ListItemElement
item = list.getChildAt(0) as ListItemElement;
var newList:ListElement = new ListElement();
item.addChild(newList);
textFlow.flowComposer.updateAllControllers();
// assert the empty list is deleted
assertTrue("listItemInsertion: newList not normalized",newList.numChildren == 1);
}
public function testFTETextField():void
{
// use the TextFlow's container
var fieldParent:Sprite = SelManager.textFlow.flowComposer.getControllerAt(0).container;
// remove the controller so the the textFlow isn't displayed in it
SelManager.textFlow.flowComposer.removeControllerAt(0);
var field:FTETextField = new FTETextField();
field.htmlText = "Hello world";
fieldParent.addChild(field);
}
public function replaceChildrenTest():void
{
SelManager.selectRange(0,0);
// remove all the children
var textFlow:TextFlow = SelManager.textFlow;
textFlow.replaceChildren(0,textFlow.numChildren);
var p:ParagraphElement = new ParagraphElement();
textFlow.addChild(p);
var link:LinkElement = new LinkElement();
link.href = "XXX";
p.addChild(link);
var span:SpanElement = new SpanElement();
span.text = "Hello, ";
span.fontSize = 24;
link.addChild(span);
var link2:LinkElement = new LinkElement();
link2.href = "YYY";
p.addChild(link2);
var span2:SpanElement = new SpanElement();
span2.text = "world ";
span2.fontSize = 24;
link2.addChild(span2);
textFlow.flowComposer.updateAllControllers();
link.replaceChildren(link2.numChildren,link2.numChildren,link2.mxmlChildren);
p.removeChild(link2);
textFlow.flowComposer.updateAllControllers();
assertTrue("replaceChildrenTest: extra line - look for extra terminator",textFlow.flowComposer.numLines == 1);
}
private function myFontLookup(context:mySwfContext, tlf:ITextLayoutFormat):Function
{
return myFontLookup;
}
public function resolveFontLookupTest():void
{
var textFlow:TextFlow = SelManager.textFlow;
textFlow.fontFamily = "Arial";
textFlow.fontWeight = FontWeight.BOLD;
textFlow.fontLookup = "device";
textFlow.flowComposer.updateAllControllers();
var swfContext:mySwfContext = new mySwfContext();
try
{
GlobalSettings.resolveFontLookupFunction = myFontLookup(swfContext, textFlow.format);
textFlow.flowComposer.updateAllControllers();
assertTrue ("fontLookup not matched.", textFlow.fontLookup = swfContext.myFontlookup);
}
finally
{
GlobalSettings.resolveFontLookupFunction = null;
}
}
public function bindableSpan():void
{
// Bindable span should not lose its formatting
var textFlow:TextFlow = new TextFlow();
var paragraph:ParagraphElement = new ParagraphElement();
var span1:SpanElement = new SpanElement();
var span2:SpanElement = new SpanElement();
var format:TextLayoutFormat = new TextLayoutFormat();
format.fontWeight = FontWeight.BOLD;
span2.format = format;
paragraph.mxmlChildren = [ span1, span2 ];
textFlow.mxmlChildren = [ paragraph ];
textFlow.flowComposer.addController(new ContainerController(new Sprite()));
textFlow.flowComposer.compose(); // force normalize
assertTrue("Spans should not be merged!", span2.parent == span1.parent && paragraph.numChildren == 2);
assertTrue("Formatting on second span should be preserved!", span2.fontWeight == FontWeight.BOLD);
}
}
}
import flash.text.engine.FontLookup;
import flashx.textLayout.compose.ISWFContext;
import flashx.textLayout.elements.FlowElement;
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.formats.TextLayoutFormat;
class mySwfContext implements ISWFContext
{
public var myFontlookup:String = FontLookup.EMBEDDED_CFF;
public function callInContext(fn:Function, thisArg:Object, argsArray:Array, returns:Boolean=true):*
{
var tf:TextFlow = thisArg as TextFlow;
tf.fontLookup = FontLookup.EMBEDDED_CFF;
if (returns)
return fn.apply(thisArg, argsArray);
fn.apply(thisArg, argsArray);
}
}